@diffenderfer-games/hub 2.0.0-next.0 → 2.0.0-next.10
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/CHANGELOG.md +163 -0
- package/LICENSE +21 -21
- package/README.md +203 -139
- package/dist/auto.d.ts +8 -8
- package/dist/auto.js +3 -3
- package/dist/avatar-pixi.d.ts +165 -0
- package/dist/avatar-pixi.d.ts.map +1 -0
- package/dist/avatar-pixi.js +299 -0
- package/dist/avatar-pixi.js.map +1 -0
- package/dist/avatar-svg.d.ts +4 -0
- package/dist/avatar-svg.js +5 -0
- package/dist/avatar-three.d.ts +69 -0
- package/dist/avatar-three.d.ts.map +1 -0
- package/dist/avatar-three.js +49 -0
- package/dist/avatar-three.js.map +1 -0
- package/dist/avatar.d.ts +7 -0
- package/dist/avatar.js +500 -0
- package/dist/avatar.js.map +1 -0
- package/dist/daily.d.ts +2 -2
- package/dist/doctor/cli.mjs +119 -28
- package/dist/doctor/cli.mjs.map +1 -1
- package/dist/index.d.ts +8 -8
- package/dist/index.js +3 -3
- package/dist/input.d.ts +2 -2
- package/dist/input.js +2 -2
- package/dist/mp.d.ts +3 -3
- package/dist/race.d.ts +1 -1
- package/dist/rooms.d.ts +3 -3
- package/dist/rooms.js +1 -1
- package/dist/shared/animator.d.ts +104 -0
- package/dist/shared/animator.d.ts.map +1 -0
- package/dist/shared/art.js +1946 -0
- package/dist/shared/art.js.map +1 -0
- package/dist/shared/atlas.d.ts +138 -0
- package/dist/shared/atlas.d.ts.map +1 -0
- package/dist/shared/avatar.d.ts +123 -0
- package/dist/shared/avatar.d.ts.map +1 -0
- package/dist/shared/bridge.js +18 -4
- package/dist/shared/bridge.js.map +1 -1
- package/dist/shared/client-version.gen.js +1 -1
- package/dist/shared/daily.d.ts +145 -19
- package/dist/shared/daily.d.ts.map +1 -1
- package/dist/shared/daily.js +138 -25
- package/dist/shared/daily.js.map +1 -1
- package/dist/shared/defineProperty.js +39 -0
- package/dist/shared/describe.d.ts +32 -0
- package/dist/shared/describe.d.ts.map +1 -0
- package/dist/shared/describe.js +109 -0
- package/dist/shared/describe.js.map +1 -0
- package/dist/shared/globals.js +9 -42
- package/dist/shared/globals.js.map +1 -1
- package/dist/shared/index.d.ts +1122 -46
- package/dist/shared/index.d.ts.map +1 -1
- package/dist/shared/input.d.ts +74 -14
- package/dist/shared/input.d.ts.map +1 -1
- package/dist/shared/input.js +179 -25
- package/dist/shared/input.js.map +1 -1
- package/dist/shared/json-guards.js +48 -0
- package/dist/shared/json-guards.js.map +1 -0
- package/dist/shared/latest-state.js +1 -1
- package/dist/shared/live-canvas.js +62 -0
- package/dist/shared/live-canvas.js.map +1 -0
- package/dist/shared/mp.js +10 -2
- package/dist/shared/mp.js.map +1 -1
- package/dist/shared/multiplayer-types.d.ts +15 -2
- package/dist/shared/multiplayer-types.d.ts.map +1 -1
- package/dist/shared/overlays.js +800 -0
- package/dist/shared/overlays.js.map +1 -0
- package/dist/shared/page-stack.js +2 -1
- package/dist/shared/page-stack.js.map +1 -1
- package/dist/shared/pose-key.js +249 -0
- package/dist/shared/pose-key.js.map +1 -0
- package/dist/shared/race-types.d.ts +1 -1
- package/dist/shared/race.js +1 -1
- package/dist/shared/room-types.d.ts +28 -3
- package/dist/shared/room-types.d.ts.map +1 -1
- package/dist/shared/rooms.d.ts.map +1 -1
- package/dist/shared/rooms.js +23 -4
- package/dist/shared/rooms.js.map +1 -1
- package/dist/shared/runtime-opener.js +1 -1
- package/dist/shared/scene.d.ts +90 -0
- package/dist/shared/scene.d.ts.map +1 -0
- package/dist/shared/social.d.ts +90 -9
- package/dist/shared/social.d.ts.map +1 -1
- package/dist/shared/social.js +6 -1
- package/dist/shared/social.js.map +1 -1
- package/dist/shared/src.js +4563 -667
- package/dist/shared/src.js.map +1 -1
- package/dist/shared/svg-markup.js +2354 -0
- package/dist/shared/svg-markup.js.map +1 -0
- package/dist/shared/svg.js +27 -0
- package/dist/shared/svg.js.map +1 -0
- package/dist/shared/types.d.ts +174 -0
- package/dist/shared/types.d.ts.map +1 -0
- package/dist/shared/wire-types.d.ts +6 -2
- package/dist/shared/wire-types.d.ts.map +1 -1
- package/dist/social.d.ts +5 -5
- package/dist/social.js +2 -2
- package/dist/turns-rules.d.ts +2 -0
- package/dist/turns-rules.js +1 -0
- package/dist/turns.d.ts +61 -0
- package/dist/turns.d.ts.map +1 -0
- package/dist/turns.js +183 -0
- package/dist/turns.js.map +1 -0
- package/package.json +125 -89
- package/src/auto.ts +39 -39
- package/src/avatar/adapters/pixi-avatar.ts +73 -0
- package/src/avatar/adapters/pixi-crowd.ts +329 -0
- package/src/avatar/adapters/pixi-types.ts +84 -0
- package/src/avatar/adapters/three.ts +126 -0
- package/src/avatar/animator/animator.ts +285 -0
- package/src/avatar/animator/reactions.ts +44 -0
- package/src/avatar/animator/schedule.ts +73 -0
- package/src/avatar/art/beard.ts +49 -0
- package/src/avatar/art/glasses.ts +67 -0
- package/src/avatar/art/hair.ts +254 -0
- package/src/avatar/art/headwear.ts +75 -0
- package/src/avatar/art/index.ts +21 -0
- package/src/avatar/art/shape.ts +87 -0
- package/src/avatar/art-store.ts +36 -0
- package/src/avatar/atlas.ts +246 -0
- package/src/avatar/catalog-view.ts +14 -0
- package/src/avatar/colour.ts +109 -0
- package/src/avatar/describe.ts +79 -0
- package/src/avatar/face.ts +187 -0
- package/src/avatar/geom.ts +173 -0
- package/src/avatar/layout.ts +47 -0
- package/src/avatar/look-like.ts +52 -0
- package/src/avatar/outputs/bitmap.ts +75 -0
- package/src/avatar/outputs/canvas-paint.ts +177 -0
- package/src/avatar/outputs/canvas.ts +62 -0
- package/src/avatar/outputs/custom-element.ts +140 -0
- package/src/avatar/outputs/element.ts +58 -0
- package/src/avatar/outputs/live-canvas.ts +57 -0
- package/src/avatar/outputs/overlay-svg.ts +22 -0
- package/src/avatar/outputs/pose-key.ts +30 -0
- package/src/avatar/outputs/svg-markup.ts +113 -0
- package/src/avatar/outputs/svg.ts +41 -0
- package/src/avatar/overlays.ts +128 -0
- package/src/avatar/render/body.ts +116 -0
- package/src/avatar/render/colours.ts +81 -0
- package/src/avatar/render/compose.ts +320 -0
- package/src/avatar/render/context.ts +117 -0
- package/src/avatar/render/eyes.ts +78 -0
- package/src/avatar/render/eyewear.ts +118 -0
- package/src/avatar/render/frame.ts +73 -0
- package/src/avatar/render/head.ts +81 -0
- package/src/avatar/render/mouth.ts +86 -0
- package/src/avatar/render/props.ts +91 -0
- package/src/avatar/render/types.ts +93 -0
- package/src/avatar/render/walker-parts.ts +121 -0
- package/src/avatar/render/walker.ts +199 -0
- package/src/avatar/scene.ts +119 -0
- package/src/avatar-pixi.ts +20 -0
- package/src/avatar-svg.ts +21 -0
- package/src/avatar-three.ts +18 -0
- package/src/avatar.ts +63 -0
- package/src/core/avatar/avatar-cache.ts +95 -0
- package/src/core/avatar/avatar-ref.ts +34 -0
- package/src/core/avatar/avatar-types.ts +43 -0
- package/src/core/avatar/hub-avatar.ts +308 -0
- package/src/core/connection.ts +408 -299
- package/src/core/errors.ts +160 -147
- package/src/core/game-data.ts +40 -38
- package/src/core/hub-economy.ts +216 -179
- package/src/core/hub-progress.ts +269 -205
- package/src/core/hub.ts +352 -131
- package/src/core/menu-control.ts +61 -61
- package/src/core/offline/cloud-saves.ts +62 -0
- package/src/core/offline/events.ts +50 -50
- package/src/core/offline/favorites.ts +21 -21
- package/src/core/offline/hub-kinds/avatar-kind.ts +42 -0
- package/src/core/offline/hub-kinds/daily-kind.ts +68 -0
- package/src/core/offline/hub-kinds/economy-kinds.ts +86 -0
- package/src/core/offline/hub-kinds/game-fields.ts +2 -0
- package/src/core/offline/hub-kinds/index.ts +52 -0
- package/src/core/offline/hub-kinds/legacy-ops.ts +37 -0
- package/src/core/offline/hub-kinds/progress-kinds.ts +165 -0
- package/src/core/offline/hub-kinds/resume-kind.ts +108 -0
- package/src/core/offline/hub-kinds/saved-kind.ts +93 -0
- package/src/core/offline/kind-adapters.ts +48 -0
- package/src/core/offline/local-inventory.ts +64 -0
- package/src/core/offline/local-store.ts +304 -261
- package/src/core/offline/offline-api.ts +65 -0
- package/src/core/offline/outbox-entry.ts +59 -0
- package/src/core/offline/outbox-kinds.ts +78 -0
- package/src/core/offline/outbox-merge.ts +39 -0
- package/src/core/offline/outbox-replayer.ts +141 -0
- package/src/core/offline/outbox-store.ts +85 -0
- package/src/core/offline/outbox.ts +232 -0
- package/src/core/offline/queue.ts +126 -171
- package/src/core/offline/replay-lease.ts +87 -0
- package/src/core/offline/replay-policy.ts +52 -0
- package/src/core/page-account.ts +121 -0
- package/src/core/page.ts +87 -74
- package/src/core/rest/endpoint-types.ts +22 -22
- package/src/core/rest/rest-client.ts +163 -152
- package/src/core/rest/routes.gen.ts +144 -88
- package/src/core/resume/hub-resume.ts +286 -0
- package/src/core/resume/local-resume.ts +130 -0
- package/src/core/resume/resume-list.ts +65 -0
- package/src/core/resume/resume-request.ts +52 -0
- package/src/core/resume/resume-types.ts +64 -0
- package/src/core/saved/daily-saves.ts +196 -0
- package/src/core/saved/daily-seed.ts +16 -0
- package/src/core/saved/hub-saved.ts +221 -0
- package/src/core/saved/local-saved.ts +122 -0
- package/src/core/saved/saved-list.ts +129 -0
- package/src/core/saved/saved-open.ts +104 -0
- package/src/core/saved/saved-order.ts +49 -0
- package/src/core/saved/saved-types.ts +24 -0
- package/src/core/storage/json-guards.ts +80 -74
- package/src/core/storage/key-value.ts +66 -66
- package/src/core/ticket.ts +43 -43
- package/src/core/turns/hub-turns.ts +161 -0
- package/src/core/turns/turn-game.ts +510 -0
- package/src/core/turns/your-turn-buzz.ts +69 -0
- package/src/core/types.ts +134 -134
- package/src/core/version/client-version.gen.ts +2 -2
- package/src/core/version/hub-version.ts +62 -62
- package/src/core/version/semver.ts +97 -97
- package/src/daily/daily-system.ts +276 -161
- package/src/daily/page-daily.ts +36 -36
- package/src/daily/types.ts +105 -67
- package/src/daily.ts +10 -10
- package/src/doctor/checks.ts +86 -76
- package/src/doctor/cli.ts +101 -106
- package/src/doctor/hub-version-fetch.ts +43 -0
- package/src/doctor/tsconfig.json +12 -12
- package/src/doctor/world-check.ts +61 -0
- package/src/globals.ts +115 -110
- package/src/index.ts +68 -50
- package/src/input/device-state.ts +146 -146
- package/src/input/edges.ts +55 -55
- package/src/input/focus-ring.ts +13 -0
- package/src/input/gamepad.ts +61 -61
- package/src/input/input-events.ts +32 -32
- package/src/input/input-registry.ts +171 -164
- package/src/input/input-system.ts +406 -393
- package/src/input/legacy-controls.ts +64 -64
- package/src/input/modality.ts +122 -0
- package/src/input/nav-state.ts +200 -191
- package/src/input/navigator.ts +182 -171
- package/src/input/on-screen-controls.ts +101 -101
- package/src/input/page-control.ts +43 -43
- package/src/input/page-input.ts +193 -193
- package/src/input/player-remaps.ts +77 -77
- package/src/input/source-values.ts +116 -116
- package/src/input/spatial.ts +47 -47
- package/src/input/types.ts +331 -331
- package/src/input/zero-config.ts +164 -177
- package/src/input.ts +26 -22
- package/src/mp.ts +10 -10
- package/src/overlay/controls-panel.ts +163 -163
- package/src/overlay/dom.ts +46 -46
- package/src/overlay/layout-clusters.ts +162 -162
- package/src/overlay/layout-geometry.ts +136 -136
- package/src/overlay/layout.ts +203 -203
- package/src/overlay/listener-scope.ts +23 -23
- package/src/overlay/on-screen-keyboard.ts +137 -137
- package/src/overlay/overlay.ts +234 -208
- package/src/overlay/styles.ts +13 -12
- package/src/overlay/virtual-controls.ts +262 -262
- package/src/race.ts +10 -10
- package/src/realtime/bridge.ts +126 -121
- package/src/realtime/cross-bundle.ts +29 -29
- package/src/realtime/fallback-notice.ts +48 -48
- package/src/realtime/hub-link.ts +65 -65
- package/src/realtime/latest-state.ts +33 -33
- package/src/realtime/multiplayer-forwarder.ts +40 -28
- package/src/realtime/multiplayer-types.ts +66 -52
- package/src/realtime/page-runtime.ts +60 -60
- package/src/realtime/race-forwarder.ts +59 -59
- package/src/realtime/race-types.ts +132 -132
- package/src/realtime/room-types.ts +151 -125
- package/src/realtime/rooms-forwarder.ts +104 -79
- package/src/realtime/runtime-api.ts +74 -49
- package/src/realtime/runtime-opener.ts +61 -61
- package/src/realtime/social-forwarder.ts +77 -72
- package/src/realtime/social-types.ts +86 -78
- package/src/realtime/types.ts +54 -53
- package/src/realtime/wire-types.ts +79 -72
- package/src/realtime/world-types.ts +20 -0
- package/src/rooms.ts +10 -10
- package/src/social.ts +19 -18
- package/src/turns-kit/check-rules.ts +281 -0
- package/src/turns-rules.ts +12 -0
- package/src/turns.ts +14 -0
- package/src/types.ts +13 -13
- package/src/uistack/page-stack.ts +45 -45
- package/src/uistack/play-suspension.ts +82 -82
- package/src/uistack/types.ts +76 -76
- package/src/uistack/ui-stack.ts +144 -144
package/src/core/types.ts
CHANGED
|
@@ -1,134 +1,134 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The core client's public types, under the v1 client's names. Each one is
|
|
3
|
-
* the contract's shape (so the contract stays the one source of truth),
|
|
4
|
-
* widened where the offline cache can only know less than the hub (a local
|
|
5
|
-
* user has no role) or made generic over a game's own JSON (`Save<MySave>`).
|
|
6
|
-
*/
|
|
7
|
-
import type { Board, GameInfo as ContractGameInfo, Json, Metrics, SessionUser, SortDir } from '@diffenderfer-games/hub-contract';
|
|
8
|
-
import type { GamePayload } from './game-data';
|
|
9
|
-
import type { EndpointReply } from './rest/endpoint-types';
|
|
10
|
-
|
|
11
|
-
export type { GamePayload } from './game-data';
|
|
12
|
-
|
|
13
|
-
export type { Board, GameLeaderboards, GameRecords, Inventory, Json, LeaderboardEntry, Metrics, SortDir, Stats } from '@diffenderfer-games/hub-contract';
|
|
14
|
-
|
|
15
|
-
/** The signed-in player. Offline (a locally stored user) only `id`, `username` and `guest` are known. */
|
|
16
|
-
export type User = Pick<SessionUser, 'id' | 'username'> & Partial<Omit<SessionUser, 'id' | 'username'>>;
|
|
17
|
-
|
|
18
|
-
/** One game the player has a profile in. */
|
|
19
|
-
export interface ProfileSummary {
|
|
20
|
-
/** The game's slug. */
|
|
21
|
-
readonly game: string;
|
|
22
|
-
/** The player's display name there, or `null`. */
|
|
23
|
-
readonly displayName: string | null;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
/** What `hub.me()` answers: the current user (or `null`) and their game profiles. */
|
|
27
|
-
export interface MeResponse {
|
|
28
|
-
/** The signed-in player, or `null`. */
|
|
29
|
-
readonly user: User | null;
|
|
30
|
-
/** The games the player has a profile in. */
|
|
31
|
-
readonly profiles: ProfileSummary[];
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/** A player's profile in one game, with the game's own data typed as `TData`. */
|
|
35
|
-
export interface Profile<TData = Json> {
|
|
36
|
-
/** The game's slug. */
|
|
37
|
-
readonly game: string;
|
|
38
|
-
/** Shown to other players; `null` when never set. */
|
|
39
|
-
readonly displayName: string | null;
|
|
40
|
-
/** The game's own profile data. */
|
|
41
|
-
readonly data: TData;
|
|
42
|
-
/** Epoch ms. */
|
|
43
|
-
readonly updatedAt: number;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/** A save slot's metadata (no payload). */
|
|
47
|
-
export interface SaveMeta {
|
|
48
|
-
/** The slot key. */
|
|
49
|
-
readonly slot: string;
|
|
50
|
-
/** The slot's label, or `null`. */
|
|
51
|
-
readonly label: string | null;
|
|
52
|
-
/** Epoch ms. */
|
|
53
|
-
readonly updatedAt: number;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/** A save slot with its payload typed as `TData`. */
|
|
57
|
-
export interface Save<TData = Json> extends SaveMeta {
|
|
58
|
-
/** The stored JSON. */
|
|
59
|
-
readonly data: TData;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/** Options for `hub.submitScore()`, with the score's metadata typed as `TMeta`. */
|
|
63
|
-
export interface SubmitScoreOptions<TMeta extends GamePayload = Json> {
|
|
64
|
-
/** The board's title; only used when the board key is first created. */
|
|
65
|
-
readonly title?: string;
|
|
66
|
-
/** The sort direction; only used on board creation. Default `desc`. */
|
|
67
|
-
readonly sort?: SortDir;
|
|
68
|
-
/** Any JSON stored with the entry (a character, a run seed). */
|
|
69
|
-
readonly meta?: TMeta;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
/** What `hub.submitScore()` answers: the board, the player's best and rank (offline: rank 1). */
|
|
73
|
-
export interface SubmitScoreResult {
|
|
74
|
-
/** The board. */
|
|
75
|
-
readonly board: Board;
|
|
76
|
-
/** The player's best score, or `null` when it did not count (a zero on a board that hides zeros). */
|
|
77
|
-
readonly best: number | null;
|
|
78
|
-
/** The player's rank, or `null`. */
|
|
79
|
-
readonly rank: number | null;
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
/** One board's ranked entries (`hub.getLeaderboard()`). */
|
|
83
|
-
export type LeaderboardResult = EndpointReply<'getLeaderboard'>;
|
|
84
|
-
|
|
85
|
-
/** A board with its top entries. */
|
|
86
|
-
export type BoardWithTop = EndpointReply<'listTopOfEveryLeaderboard'>['games'][number]['boards'][number];
|
|
87
|
-
|
|
88
|
-
/** One game's play counts, with its slug (`hub.analytics()`). */
|
|
89
|
-
export type GameAnalytics = EndpointReply<'getGameAnalytics'>;
|
|
90
|
-
|
|
91
|
-
/** Catalog-wide play counts with a per-game breakdown (`hub.globalAnalytics()`). */
|
|
92
|
-
export interface GlobalAnalytics {
|
|
93
|
-
/** Across every game. */
|
|
94
|
-
readonly global: Metrics;
|
|
95
|
-
/** Per game. */
|
|
96
|
-
readonly games: (Metrics & { readonly slug: string; readonly title: string })[];
|
|
97
|
-
}
|
|
98
|
-
|
|
99
|
-
/** A listed game (`hub.listGames()`). */
|
|
100
|
-
export type GameInfo = ContractGameInfo;
|
|
101
|
-
|
|
102
|
-
/** What `GET /me/snapshot` answers; `hub.prefetchAll()` warms the offline cache for every game from it. */
|
|
103
|
-
export type SnapshotResponse = EndpointReply<'getMyOfflineSnapshot'>;
|
|
104
|
-
|
|
105
|
-
/** A player's holdings in one game, as the inventory calls answer. */
|
|
106
|
-
export type InventorySnapshot = EndpointReply<'getMyInventory'>;
|
|
107
|
-
|
|
108
|
-
/** The connection and sync status `hub.onStatus()` reports. */
|
|
109
|
-
export interface HubStatus {
|
|
110
|
-
/** The hub is reachable. */
|
|
111
|
-
readonly online: boolean;
|
|
112
|
-
/** Calls are being served from the offline cache. */
|
|
113
|
-
readonly offline: boolean;
|
|
114
|
-
/** Queued writes plus buffered events waiting to sync. */
|
|
115
|
-
readonly pending: number;
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
/** How a client created with `createHub()` / `new Hub()` behaves. */
|
|
119
|
-
export interface HubOptions {
|
|
120
|
-
/** The game's slug. Default: `window.__HUB__.slug`, then the first URL path segment. */
|
|
121
|
-
readonly slug?: string;
|
|
122
|
-
/** The API base. Default: `window.__HUB__.apiBase`, then `/_api`. */
|
|
123
|
-
readonly apiBase?: string;
|
|
124
|
-
/** A custom fetch (tests, non-browser code). Default: the global fetch. */
|
|
125
|
-
readonly fetch?: typeof fetch;
|
|
126
|
-
/**
|
|
127
|
-
* What to do when the hub can't be reached:
|
|
128
|
-
* - `auto` (default): try the network; on a connection failure fall back to
|
|
129
|
-
* the offline cache in `localStorage`, and sync when the hub is back;
|
|
130
|
-
* - `always`: never touch the network (develop a game without a hub);
|
|
131
|
-
* - `never`: never fall back; every call throws when the hub is unreachable.
|
|
132
|
-
*/
|
|
133
|
-
readonly offline?: 'auto' | 'always' | 'never';
|
|
134
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The core client's public types, under the v1 client's names. Each one is
|
|
3
|
+
* the contract's shape (so the contract stays the one source of truth),
|
|
4
|
+
* widened where the offline cache can only know less than the hub (a local
|
|
5
|
+
* user has no role) or made generic over a game's own JSON (`Save<MySave>`).
|
|
6
|
+
*/
|
|
7
|
+
import type { Board, GameInfo as ContractGameInfo, Json, Metrics, SessionUser, SortDir } from '@diffenderfer-games/hub-contract';
|
|
8
|
+
import type { GamePayload } from './game-data';
|
|
9
|
+
import type { EndpointReply } from './rest/endpoint-types';
|
|
10
|
+
|
|
11
|
+
export type { GamePayload } from './game-data';
|
|
12
|
+
|
|
13
|
+
export type { Board, GameLeaderboards, GameRecords, Inventory, Json, LeaderboardEntry, Metrics, SortDir, Stats } from '@diffenderfer-games/hub-contract';
|
|
14
|
+
|
|
15
|
+
/** The signed-in player. Offline (a locally stored user) only `id`, `username` and `guest` are known. */
|
|
16
|
+
export type User = Pick<SessionUser, 'id' | 'username'> & Partial<Omit<SessionUser, 'id' | 'username'>>;
|
|
17
|
+
|
|
18
|
+
/** One game the player has a profile in. */
|
|
19
|
+
export interface ProfileSummary {
|
|
20
|
+
/** The game's slug. */
|
|
21
|
+
readonly game: string;
|
|
22
|
+
/** The player's display name there, or `null`. */
|
|
23
|
+
readonly displayName: string | null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/** What `hub.me()` answers: the current user (or `null`) and their game profiles. */
|
|
27
|
+
export interface MeResponse {
|
|
28
|
+
/** The signed-in player, or `null`. */
|
|
29
|
+
readonly user: User | null;
|
|
30
|
+
/** The games the player has a profile in. */
|
|
31
|
+
readonly profiles: ProfileSummary[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/** A player's profile in one game, with the game's own data typed as `TData`. */
|
|
35
|
+
export interface Profile<TData = Json> {
|
|
36
|
+
/** The game's slug. */
|
|
37
|
+
readonly game: string;
|
|
38
|
+
/** Shown to other players; `null` when never set. */
|
|
39
|
+
readonly displayName: string | null;
|
|
40
|
+
/** The game's own profile data. */
|
|
41
|
+
readonly data: TData;
|
|
42
|
+
/** Epoch ms. */
|
|
43
|
+
readonly updatedAt: number;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/** A save slot's metadata (no payload). */
|
|
47
|
+
export interface SaveMeta {
|
|
48
|
+
/** The slot key. */
|
|
49
|
+
readonly slot: string;
|
|
50
|
+
/** The slot's label, or `null`. */
|
|
51
|
+
readonly label: string | null;
|
|
52
|
+
/** Epoch ms. */
|
|
53
|
+
readonly updatedAt: number;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/** A save slot with its payload typed as `TData`. */
|
|
57
|
+
export interface Save<TData = Json> extends SaveMeta {
|
|
58
|
+
/** The stored JSON. */
|
|
59
|
+
readonly data: TData;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/** Options for `hub.submitScore()`, with the score's metadata typed as `TMeta`. */
|
|
63
|
+
export interface SubmitScoreOptions<TMeta extends GamePayload = Json> {
|
|
64
|
+
/** The board's title; only used when the board key is first created. */
|
|
65
|
+
readonly title?: string;
|
|
66
|
+
/** The sort direction; only used on board creation. Default `desc`. */
|
|
67
|
+
readonly sort?: SortDir;
|
|
68
|
+
/** Any JSON stored with the entry (a character, a run seed). */
|
|
69
|
+
readonly meta?: TMeta;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/** What `hub.submitScore()` answers: the board, the player's best and rank (offline: rank 1). */
|
|
73
|
+
export interface SubmitScoreResult {
|
|
74
|
+
/** The board. */
|
|
75
|
+
readonly board: Board;
|
|
76
|
+
/** The player's best score, or `null` when it did not count (a zero on a board that hides zeros). */
|
|
77
|
+
readonly best: number | null;
|
|
78
|
+
/** The player's rank, or `null`. */
|
|
79
|
+
readonly rank: number | null;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** One board's ranked entries (`hub.getLeaderboard()`). */
|
|
83
|
+
export type LeaderboardResult = EndpointReply<'getLeaderboard'>;
|
|
84
|
+
|
|
85
|
+
/** A board with its top entries. */
|
|
86
|
+
export type BoardWithTop = EndpointReply<'listTopOfEveryLeaderboard'>['games'][number]['boards'][number];
|
|
87
|
+
|
|
88
|
+
/** One game's play counts, with its slug (`hub.analytics()`). */
|
|
89
|
+
export type GameAnalytics = EndpointReply<'getGameAnalytics'>;
|
|
90
|
+
|
|
91
|
+
/** Catalog-wide play counts with a per-game breakdown (`hub.globalAnalytics()`). */
|
|
92
|
+
export interface GlobalAnalytics {
|
|
93
|
+
/** Across every game. */
|
|
94
|
+
readonly global: Metrics;
|
|
95
|
+
/** Per game. */
|
|
96
|
+
readonly games: (Metrics & { readonly slug: string; readonly title: string })[];
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A listed game (`hub.listGames()`). */
|
|
100
|
+
export type GameInfo = ContractGameInfo;
|
|
101
|
+
|
|
102
|
+
/** What `GET /me/snapshot` answers; `hub.prefetchAll()` warms the offline cache for every game from it. */
|
|
103
|
+
export type SnapshotResponse = EndpointReply<'getMyOfflineSnapshot'>;
|
|
104
|
+
|
|
105
|
+
/** A player's holdings in one game, as the inventory calls answer. */
|
|
106
|
+
export type InventorySnapshot = EndpointReply<'getMyInventory'>;
|
|
107
|
+
|
|
108
|
+
/** The connection and sync status `hub.onStatus()` reports. */
|
|
109
|
+
export interface HubStatus {
|
|
110
|
+
/** The hub is reachable. */
|
|
111
|
+
readonly online: boolean;
|
|
112
|
+
/** Calls are being served from the offline cache. */
|
|
113
|
+
readonly offline: boolean;
|
|
114
|
+
/** Queued writes plus buffered events waiting to sync. */
|
|
115
|
+
readonly pending: number;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/** How a client created with `createHub()` / `new Hub()` behaves. */
|
|
119
|
+
export interface HubOptions {
|
|
120
|
+
/** The game's slug. Default: `window.__HUB__.slug`, then the first URL path segment. */
|
|
121
|
+
readonly slug?: string;
|
|
122
|
+
/** The API base. Default: `window.__HUB__.apiBase`, then `/_api`. */
|
|
123
|
+
readonly apiBase?: string;
|
|
124
|
+
/** A custom fetch (tests, non-browser code). Default: the global fetch. */
|
|
125
|
+
readonly fetch?: typeof fetch;
|
|
126
|
+
/**
|
|
127
|
+
* What to do when the hub can't be reached:
|
|
128
|
+
* - `auto` (default): try the network; on a connection failure fall back to
|
|
129
|
+
* the offline cache in `localStorage`, and sync when the hub is back;
|
|
130
|
+
* - `always`: never touch the network (develop a game without a hub);
|
|
131
|
+
* - `never`: never fall back; every call throws when the hub is unreachable.
|
|
132
|
+
*/
|
|
133
|
+
readonly offline?: 'auto' | 'always' | 'never';
|
|
134
|
+
}
|
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
// Generated by tools/hub-client/generate.ts. Do not edit; run `npm run gen:hub-client
|
|
1
|
+
// Generated by tools/hub-client/generate.ts. Do not edit; run `npm run gen:hub-client`.
|
|
2
2
|
/** This client's package version, sent as `x-hub-client` on REST calls and `client` in `hello`. */
|
|
3
|
-
export const HUB_CLIENT_VERSION = '2.0.0-next.
|
|
3
|
+
export const HUB_CLIENT_VERSION = '2.0.0-next.10';
|
|
@@ -1,62 +1,62 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The hub's `GET /_api/version`, read at most once per page and API base
|
|
3
|
-
* (plan §4.3.3), and what it says about this client. Nothing reads it on
|
|
4
|
-
* load: it costs a request only when a game (or `hub-doctor`) asks.
|
|
5
|
-
*/
|
|
6
|
-
import type { HubVersion } from '@diffenderfer-games/hub-contract';
|
|
7
|
-
import { HubError, UNSUPPORTED_BY_HUB } from '../errors';
|
|
8
|
-
import type { RestClient } from '../rest/rest-client';
|
|
9
|
-
import { HUB_CLIENT_VERSION } from './client-version.gen';
|
|
10
|
-
import { satisfiesRange } from './semver';
|
|
11
|
-
|
|
12
|
-
/** How a hub regards a client version. */
|
|
13
|
-
export type ClientSupport =
|
|
14
|
-
/** In `supported` and not `deprecated`. */
|
|
15
|
-
| 'supported'
|
|
16
|
-
/** Still supported, but `deprecated`: update soon. */
|
|
17
|
-
| 'deprecated'
|
|
18
|
-
/** Outside `supported`: update now. */
|
|
19
|
-
| 'unsupported'
|
|
20
|
-
/** The hub predates versioning (the old hub): nothing to compare. */
|
|
21
|
-
| 'unknown';
|
|
22
|
-
|
|
23
|
-
/** How `version` fares against a hub's answer (`null`: a hub from before versioning). */
|
|
24
|
-
export function clientSupport(hubVersion: HubVersion | null, version: string = HUB_CLIENT_VERSION): ClientSupport {
|
|
25
|
-
if (hubVersion === null) {
|
|
26
|
-
return 'unknown';
|
|
27
|
-
}
|
|
28
|
-
if (!satisfiesRange(version, hubVersion.supported)) {
|
|
29
|
-
return 'unsupported';
|
|
30
|
-
}
|
|
31
|
-
return satisfiesRange(version, hubVersion.deprecated) ? 'deprecated' : 'supported';
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
const versionsByApiBase = new Map<string, Promise<HubVersion | null>>();
|
|
35
|
-
|
|
36
|
-
/**
|
|
37
|
-
* The hub's version, fetched once per page and API base. Resolves `null` for
|
|
38
|
-
* a hub from before versioning (it has no `/version` route); a failed
|
|
39
|
-
* request is not cached, so a later call tries again.
|
|
40
|
-
*/
|
|
41
|
-
export function readHubVersion(rest: RestClient): Promise<HubVersion | null> {
|
|
42
|
-
const cached = versionsByApiBase.get(rest.apiBase);
|
|
43
|
-
if (cached !== undefined) {
|
|
44
|
-
return cached;
|
|
45
|
-
}
|
|
46
|
-
const pending = fetchHubVersion(rest);
|
|
47
|
-
versionsByApiBase.set(rest.apiBase, pending);
|
|
48
|
-
return pending;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
/** One `GET /version`; `null` when the hub has no such route. */
|
|
52
|
-
async function fetchHubVersion(rest: RestClient): Promise<HubVersion | null> {
|
|
53
|
-
try {
|
|
54
|
-
return await rest.call('getHubVersion');
|
|
55
|
-
} catch (failure) {
|
|
56
|
-
if (failure instanceof HubError && failure.code === UNSUPPORTED_BY_HUB) {
|
|
57
|
-
return null;
|
|
58
|
-
}
|
|
59
|
-
versionsByApiBase.delete(rest.apiBase);
|
|
60
|
-
throw failure;
|
|
61
|
-
}
|
|
62
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* The hub's `GET /_api/version`, read at most once per page and API base
|
|
3
|
+
* (plan §4.3.3), and what it says about this client. Nothing reads it on
|
|
4
|
+
* load: it costs a request only when a game (or `hub-doctor`) asks.
|
|
5
|
+
*/
|
|
6
|
+
import type { HubVersion } from '@diffenderfer-games/hub-contract';
|
|
7
|
+
import { HubError, UNSUPPORTED_BY_HUB } from '../errors';
|
|
8
|
+
import type { RestClient } from '../rest/rest-client';
|
|
9
|
+
import { HUB_CLIENT_VERSION } from './client-version.gen';
|
|
10
|
+
import { satisfiesRange } from './semver';
|
|
11
|
+
|
|
12
|
+
/** How a hub regards a client version. */
|
|
13
|
+
export type ClientSupport =
|
|
14
|
+
/** In `supported` and not `deprecated`. */
|
|
15
|
+
| 'supported'
|
|
16
|
+
/** Still supported, but `deprecated`: update soon. */
|
|
17
|
+
| 'deprecated'
|
|
18
|
+
/** Outside `supported`: update now. */
|
|
19
|
+
| 'unsupported'
|
|
20
|
+
/** The hub predates versioning (the old hub): nothing to compare. */
|
|
21
|
+
| 'unknown';
|
|
22
|
+
|
|
23
|
+
/** How `version` fares against a hub's answer (`null`: a hub from before versioning). */
|
|
24
|
+
export function clientSupport(hubVersion: HubVersion | null, version: string = HUB_CLIENT_VERSION): ClientSupport {
|
|
25
|
+
if (hubVersion === null) {
|
|
26
|
+
return 'unknown';
|
|
27
|
+
}
|
|
28
|
+
if (!satisfiesRange(version, hubVersion.supported)) {
|
|
29
|
+
return 'unsupported';
|
|
30
|
+
}
|
|
31
|
+
return satisfiesRange(version, hubVersion.deprecated) ? 'deprecated' : 'supported';
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const versionsByApiBase = new Map<string, Promise<HubVersion | null>>();
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* The hub's version, fetched once per page and API base. Resolves `null` for
|
|
38
|
+
* a hub from before versioning (it has no `/version` route); a failed
|
|
39
|
+
* request is not cached, so a later call tries again.
|
|
40
|
+
*/
|
|
41
|
+
export function readHubVersion(rest: RestClient): Promise<HubVersion | null> {
|
|
42
|
+
const cached = versionsByApiBase.get(rest.apiBase);
|
|
43
|
+
if (cached !== undefined) {
|
|
44
|
+
return cached;
|
|
45
|
+
}
|
|
46
|
+
const pending = fetchHubVersion(rest);
|
|
47
|
+
versionsByApiBase.set(rest.apiBase, pending);
|
|
48
|
+
return pending;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/** One `GET /version`; `null` when the hub has no such route. */
|
|
52
|
+
async function fetchHubVersion(rest: RestClient): Promise<HubVersion | null> {
|
|
53
|
+
try {
|
|
54
|
+
return await rest.call('getHubVersion');
|
|
55
|
+
} catch (failure) {
|
|
56
|
+
if (failure instanceof HubError && failure.code === UNSUPPORTED_BY_HUB) {
|
|
57
|
+
return null;
|
|
58
|
+
}
|
|
59
|
+
versionsByApiBase.delete(rest.apiBase);
|
|
60
|
+
throw failure;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
@@ -1,97 +1,97 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Just enough semver for version ranges the hub sends (`>=2.0.0-0 <3.0.0`,
|
|
3
|
-
* `<2.2.0`): comparators separated by spaces must all hold, `||` separates
|
|
4
|
-
* alternatives. Versions compare in plain semver order, prereleases included
|
|
5
|
-
* (`2.0.0-next.1 < 2.0.0`); unlike npm, a prerelease is not excluded from a
|
|
6
|
-
* range of another release line, which is why the hub writes `-0` floors.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
/** A parsed `major.minor.patch[-prerelease]` version. */
|
|
10
|
-
export interface ParsedVersion {
|
|
11
|
-
/** `[major, minor, patch]`. */
|
|
12
|
-
readonly release: readonly [number, number, number];
|
|
13
|
-
/** The dot-separated prerelease identifiers (empty for a release). */
|
|
14
|
-
readonly prerelease: readonly string[];
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
const VERSION_PATTERN = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
18
|
-
|
|
19
|
-
/** Parse a version (`2`, `2.1`, `2.1.0`, `2.1.0-next.3`); `undefined` when it is not one. */
|
|
20
|
-
export function parseVersion(text: string): ParsedVersion | undefined {
|
|
21
|
-
const match = VERSION_PATTERN.exec(text.trim());
|
|
22
|
-
if (match === null) {
|
|
23
|
-
return undefined;
|
|
24
|
-
}
|
|
25
|
-
const [, major = '0', minor = '0', patch = '0', prerelease] = match;
|
|
26
|
-
return { release: [Number(major), Number(minor), Number(patch)], prerelease: prerelease === undefined ? [] : prerelease.split('.') };
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** Compare two prerelease identifiers: numbers by value and before words, words as text. */
|
|
30
|
-
function compareIdentifiers(first: string, second: string): number {
|
|
31
|
-
const isFirstNumeric = /^\d+$/.test(first);
|
|
32
|
-
const isSecondNumeric = /^\d+$/.test(second);
|
|
33
|
-
if (isFirstNumeric && isSecondNumeric) {
|
|
34
|
-
return Number(first) - Number(second);
|
|
35
|
-
}
|
|
36
|
-
if (isFirstNumeric !== isSecondNumeric) {
|
|
37
|
-
return isFirstNumeric ? -1 : 1;
|
|
38
|
-
}
|
|
39
|
-
return first < second ? -1 : first > second ? 1 : 0;
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
/** Negative when `first` is older, positive when newer, 0 when equal. */
|
|
43
|
-
export function compareVersions(first: ParsedVersion, second: ParsedVersion): number {
|
|
44
|
-
for (let i = 0; i < 3; i += 1) {
|
|
45
|
-
const difference = (first.release[i] ?? 0) - (second.release[i] ?? 0);
|
|
46
|
-
if (difference !== 0) {
|
|
47
|
-
return difference;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (first.prerelease.length === 0 || second.prerelease.length === 0) {
|
|
51
|
-
return second.prerelease.length - first.prerelease.length;
|
|
52
|
-
}
|
|
53
|
-
const length = Math.max(first.prerelease.length, second.prerelease.length);
|
|
54
|
-
for (let i = 0; i < length; i += 1) {
|
|
55
|
-
const firstIdentifier = first.prerelease[i];
|
|
56
|
-
const secondIdentifier = second.prerelease[i];
|
|
57
|
-
if (firstIdentifier === undefined || secondIdentifier === undefined) {
|
|
58
|
-
return firstIdentifier === undefined ? -1 : 1;
|
|
59
|
-
}
|
|
60
|
-
const difference = compareIdentifiers(firstIdentifier, secondIdentifier);
|
|
61
|
-
if (difference !== 0) {
|
|
62
|
-
return difference;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
return 0;
|
|
66
|
-
}
|
|
67
|
-
|
|
68
|
-
const COMPARATOR_PATTERN = /^(>=|<=|>|<|=)?\s*(.+)$/;
|
|
69
|
-
|
|
70
|
-
/** Whether `version` meets one comparator (`>=2.0.0`); a malformed comparator never holds. */
|
|
71
|
-
function meetsComparator(version: ParsedVersion, comparator: string): boolean {
|
|
72
|
-
const match = COMPARATOR_PATTERN.exec(comparator);
|
|
73
|
-
const bound = match?.[2] === undefined ? undefined : parseVersion(match[2]);
|
|
74
|
-
if (match === null || bound === undefined) {
|
|
75
|
-
return false;
|
|
76
|
-
}
|
|
77
|
-
const order = compareVersions(version, bound);
|
|
78
|
-
switch (match[1]) {
|
|
79
|
-
case '>=': return order >= 0;
|
|
80
|
-
case '<=': return order <= 0;
|
|
81
|
-
case '>': return order > 0;
|
|
82
|
-
case '<': return order < 0;
|
|
83
|
-
default: return order === 0;
|
|
84
|
-
}
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
/** Whether `versionText` is in `range`; a malformed version or empty range never is. */
|
|
88
|
-
export function satisfiesRange(versionText: string, range: string): boolean {
|
|
89
|
-
const version = parseVersion(versionText);
|
|
90
|
-
if (version === undefined) {
|
|
91
|
-
return false;
|
|
92
|
-
}
|
|
93
|
-
return range.split('||').some((alternative) => {
|
|
94
|
-
const comparators = alternative.trim().split(/\s+/).filter((comparator) => comparator !== '');
|
|
95
|
-
return comparators.length > 0 && comparators.every((comparator) => meetsComparator(version, comparator));
|
|
96
|
-
});
|
|
97
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Just enough semver for version ranges the hub sends (`>=2.0.0-0 <3.0.0`,
|
|
3
|
+
* `<2.2.0`): comparators separated by spaces must all hold, `||` separates
|
|
4
|
+
* alternatives. Versions compare in plain semver order, prereleases included
|
|
5
|
+
* (`2.0.0-next.1 < 2.0.0`); unlike npm, a prerelease is not excluded from a
|
|
6
|
+
* range of another release line, which is why the hub writes `-0` floors.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
/** A parsed `major.minor.patch[-prerelease]` version. */
|
|
10
|
+
export interface ParsedVersion {
|
|
11
|
+
/** `[major, minor, patch]`. */
|
|
12
|
+
readonly release: readonly [number, number, number];
|
|
13
|
+
/** The dot-separated prerelease identifiers (empty for a release). */
|
|
14
|
+
readonly prerelease: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const VERSION_PATTERN = /^v?(\d+)(?:\.(\d+))?(?:\.(\d+))?(?:-([0-9A-Za-z.-]+))?(?:\+[0-9A-Za-z.-]+)?$/;
|
|
18
|
+
|
|
19
|
+
/** Parse a version (`2`, `2.1`, `2.1.0`, `2.1.0-next.3`); `undefined` when it is not one. */
|
|
20
|
+
export function parseVersion(text: string): ParsedVersion | undefined {
|
|
21
|
+
const match = VERSION_PATTERN.exec(text.trim());
|
|
22
|
+
if (match === null) {
|
|
23
|
+
return undefined;
|
|
24
|
+
}
|
|
25
|
+
const [, major = '0', minor = '0', patch = '0', prerelease] = match;
|
|
26
|
+
return { release: [Number(major), Number(minor), Number(patch)], prerelease: prerelease === undefined ? [] : prerelease.split('.') };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** Compare two prerelease identifiers: numbers by value and before words, words as text. */
|
|
30
|
+
function compareIdentifiers(first: string, second: string): number {
|
|
31
|
+
const isFirstNumeric = /^\d+$/.test(first);
|
|
32
|
+
const isSecondNumeric = /^\d+$/.test(second);
|
|
33
|
+
if (isFirstNumeric && isSecondNumeric) {
|
|
34
|
+
return Number(first) - Number(second);
|
|
35
|
+
}
|
|
36
|
+
if (isFirstNumeric !== isSecondNumeric) {
|
|
37
|
+
return isFirstNumeric ? -1 : 1;
|
|
38
|
+
}
|
|
39
|
+
return first < second ? -1 : first > second ? 1 : 0;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Negative when `first` is older, positive when newer, 0 when equal. */
|
|
43
|
+
export function compareVersions(first: ParsedVersion, second: ParsedVersion): number {
|
|
44
|
+
for (let i = 0; i < 3; i += 1) {
|
|
45
|
+
const difference = (first.release[i] ?? 0) - (second.release[i] ?? 0);
|
|
46
|
+
if (difference !== 0) {
|
|
47
|
+
return difference;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
if (first.prerelease.length === 0 || second.prerelease.length === 0) {
|
|
51
|
+
return second.prerelease.length - first.prerelease.length;
|
|
52
|
+
}
|
|
53
|
+
const length = Math.max(first.prerelease.length, second.prerelease.length);
|
|
54
|
+
for (let i = 0; i < length; i += 1) {
|
|
55
|
+
const firstIdentifier = first.prerelease[i];
|
|
56
|
+
const secondIdentifier = second.prerelease[i];
|
|
57
|
+
if (firstIdentifier === undefined || secondIdentifier === undefined) {
|
|
58
|
+
return firstIdentifier === undefined ? -1 : 1;
|
|
59
|
+
}
|
|
60
|
+
const difference = compareIdentifiers(firstIdentifier, secondIdentifier);
|
|
61
|
+
if (difference !== 0) {
|
|
62
|
+
return difference;
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
return 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
const COMPARATOR_PATTERN = /^(>=|<=|>|<|=)?\s*(.+)$/;
|
|
69
|
+
|
|
70
|
+
/** Whether `version` meets one comparator (`>=2.0.0`); a malformed comparator never holds. */
|
|
71
|
+
function meetsComparator(version: ParsedVersion, comparator: string): boolean {
|
|
72
|
+
const match = COMPARATOR_PATTERN.exec(comparator);
|
|
73
|
+
const bound = match?.[2] === undefined ? undefined : parseVersion(match[2]);
|
|
74
|
+
if (match === null || bound === undefined) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const order = compareVersions(version, bound);
|
|
78
|
+
switch (match[1]) {
|
|
79
|
+
case '>=': return order >= 0;
|
|
80
|
+
case '<=': return order <= 0;
|
|
81
|
+
case '>': return order > 0;
|
|
82
|
+
case '<': return order < 0;
|
|
83
|
+
default: return order === 0;
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** Whether `versionText` is in `range`; a malformed version or empty range never is. */
|
|
88
|
+
export function satisfiesRange(versionText: string, range: string): boolean {
|
|
89
|
+
const version = parseVersion(versionText);
|
|
90
|
+
if (version === undefined) {
|
|
91
|
+
return false;
|
|
92
|
+
}
|
|
93
|
+
return range.split('||').some((alternative) => {
|
|
94
|
+
const comparators = alternative.trim().split(/\s+/).filter((comparator) => comparator !== '');
|
|
95
|
+
return comparators.length > 0 && comparators.every((comparator) => meetsComparator(version, comparator));
|
|
96
|
+
});
|
|
97
|
+
}
|