@gamecore-api/sdk 0.17.0 → 0.18.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/client.d.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { Announcement, AnnouncementBar, CartItem, CatalogSection, Category, CategoryInfo, CheckoutRequest, CheckoutResponse, CompleteWithBalanceResult, Conversation, ConversationDetail, CouponResult, ExchangeRates, Favorite, Game, GameDetail, GiftCard, LegalDocument, LevelStatus, Notification, Order, PagedGamesResponse, PlatformInfo, PaginatedResponse, PaymentInfo, PaymentMethod, Product, ProductFilters, ReferralCommission, ReferralLink, ReferralPerformance, ReferralStats, Review, ReviewCreateResult, ReviewStats, SearchResult, SiteConfig, SiteStats, SiteUIConfig, TelegramAuthResponse, TelegramBotLoginOptions, TelegramInitResponse, TelegramWidgetRenderOptions, TelegramWidgetUser, TopupMethod, TopupResponse, TopupStatus, Transaction, User, UserBalance, WebPushSubscriptionInput } from "./types";
1
+ import type { Announcement, AnnouncementBar, CartItem, CashbackPreview, CatalogSection, Category, CategoryInfo, CheckoutRequest, CheckoutResponse, CompleteWithBalanceResult, Conversation, ConversationDetail, CouponResult, DailyBonusClaimResult, DailyBonusStatus, ExchangeRates, Favorite, Game, GameDetail, GiftCard, LegalDocument, LevelStatus, Notification, Order, PagedGamesResponse, PlatformInfo, PaginatedResponse, PaymentInfo, PaymentMethod, Product, ProductFilters, PublicCoupon, Quest, QuestCompleteResult, ReferralCommission, ReferralLink, ReferralPerformance, ReferralStats, Review, ReviewCreateResult, ReviewProof, ReviewStats, ScreenshotsResponse, SearchResult, SiteConfig, SiteStats, SiteUIConfig, SystemRequirementsResponse, TelegramAuthResponse, TelegramBotLoginOptions, TelegramInitResponse, TelegramWidgetRenderOptions, TelegramWidgetUser, TopupMethod, TopupResponse, TopupStatus, Transaction, User, UserBalance, WebPushSubscriptionInput } from "./types";
2
2
  export interface GameCoreOptions {
3
3
  /** Site API key (gc_live_xxx or gc_test_xxx) */
4
4
  apiKey: string;
@@ -39,6 +39,17 @@ export declare class GameCoreClient {
39
39
  getTranslations: (locale?: string) => Promise<Record<string, string>>;
40
40
  /** Get site UI config (header, footer, nav, trust pills) */
41
41
  getUIConfig: () => Promise<SiteUIConfig>;
42
+ /**
43
+ * Per-site allowlist of partner origins permitted to iframe the
44
+ * storefront's checkout/widget pages. Wave 5 #58, available
45
+ * since gamecore-api 2026-05-01.
46
+ *
47
+ * Storefronts feed this list into their own
48
+ * `Content-Security-Policy: frame-ancestors` directive — this
49
+ * SDK call only fetches the data, it does not set the header
50
+ * itself (the storefront's edge / Next.js middleware does).
51
+ */
52
+ getEmbedAllowlist: () => Promise<string[]>;
42
53
  /** Get cookie consent config */
43
54
  getCookieConsent: () => Promise<{
44
55
  enabled: boolean;
@@ -279,6 +290,18 @@ export declare class GameCoreClient {
279
290
  page?: number;
280
291
  limit?: number;
281
292
  locale?: string;
293
+ /**
294
+ * Legacy coarse-grained filter: `"topup"` → mobile donate
295
+ * (keyhub + vendoria), `"cdkey"` → Steam keys (keyhub).
296
+ * Still accepted for backward-compat with early storefront
297
+ * builds.
298
+ *
299
+ * @deprecated Prefer `platform: "mobile_game"` instead of
300
+ * `type: "topup"` and `platform: "steam"` instead of
301
+ * `type: "cdkey"`. Canonical platforms give you facet
302
+ * counts via `getPlatforms()` and survive supplier
303
+ * reshuffles.
304
+ */
282
305
  type?: string;
283
306
  deliveryType?: string;
284
307
  include?: string;
@@ -331,6 +354,26 @@ export declare class GameCoreClient {
331
354
  * server-side.
332
355
  */
333
356
  getRecommendations: (gameSlug: string, limit?: number) => Promise<Game[]>;
357
+ /**
358
+ * Steam-sourced PC system requirements for a canonical game.
359
+ * Designed for `/games/<slug>/system-requirements` SEO landings.
360
+ * Returns `{ hasData: false }` for non-Steam games (and for
361
+ * Steam games Steam didn't return data for) so the frontend
362
+ * can branch without try/catch.
363
+ *
364
+ * Cached 7 days server-side; the very first request for a game
365
+ * fetches synchronously, subsequent stale reads serve cached
366
+ * data and refresh in the background. Available since
367
+ * gamecore-api 2026-05-01 (Wave 5 #50).
368
+ */
369
+ getGameSystemRequirements: (gameSlug: string) => Promise<SystemRequirementsResponse>;
370
+ /**
371
+ * Steam-sourced screenshot gallery + trailers for a canonical
372
+ * game. Same `hasData` semantics as
373
+ * `getGameSystemRequirements`. Available since gamecore-api
374
+ * 2026-05-01 (Wave 5 #50).
375
+ */
376
+ getGameScreenshots: (gameSlug: string) => Promise<ScreenshotsResponse>;
334
377
  /** Get categories for a game with product counts and delivery metadata */
335
378
  getCategories: (gameSlug: string) => Promise<Category[]>;
336
379
  /** Get products for a game with optional filters */
@@ -425,6 +468,29 @@ export declare class GameCoreClient {
425
468
  remove: (id: number) => Promise<void>;
426
469
  /** Clear all cart items */
427
470
  clear: () => Promise<void>;
471
+ /**
472
+ * Cashback preview for the current cart (Wave 5 #51, available
473
+ * since gamecore-api 2026-05-01). Returns the credit the
474
+ * authenticated user would receive after the cart is paid for,
475
+ * given the user's current loyalty level + per-site cashback
476
+ * rate.
477
+ *
478
+ * **Requires authentication** — the endpoint sits behind the
479
+ * same auth gate as the rest of `/cart/*`; calling it without
480
+ * a session throws `GameCoreError(401, 'UNAUTHORIZED')`. Show
481
+ * a "Войдите, чтобы получать кэшбэк" hint on the storefront
482
+ * and skip the request for anonymous users instead of
483
+ * catching the 401.
484
+ *
485
+ * For tenants that have not configured cashback rates the
486
+ * response is `totalAmount: 0` with a UX hint in
487
+ * `rateExplanation` rather than an error.
488
+ *
489
+ * Storefronts: call this near the checkout button to render a
490
+ * "+N бонусов" trigger. Cheap (1 SQL aggregate); call freely
491
+ * after add/remove without caching.
492
+ */
493
+ getCashbackPreview: () => Promise<CashbackPreview>;
428
494
  };
429
495
  checkout: {
430
496
  /**
@@ -507,6 +573,51 @@ export declare class GameCoreClient {
507
573
  markRead: (id: number) => Promise<void>;
508
574
  /** Mark all notifications as read */
509
575
  markAllRead: () => Promise<void>;
576
+ /**
577
+ * Submit a screenshot of a review the user posted on a third-
578
+ * party platform (T-Bank, Otzyvru, Google Play, …). Lands in
579
+ * the moderator queue; once approved, the user gets a fixed
580
+ * payout to their balance.
581
+ *
582
+ * Available since gamecore-api 2026-05-01 (Wave 5 #56).
583
+ */
584
+ submitReviewProof: (input: {
585
+ platform: string;
586
+ screenshotUrl: string;
587
+ reviewUrl?: string;
588
+ userNote?: string;
589
+ }) => Promise<ReviewProof>;
590
+ /** List the current user's review-proof submissions, newest first. */
591
+ getReviewProofs: () => Promise<ReviewProof[]>;
592
+ /**
593
+ * Daily-bonus status: whether the user can claim, current
594
+ * streak, projected reward, and `nextAvailableAt` cooldown
595
+ * timestamp. Available since gamecore-api 2026-05-01
596
+ * (Wave 5 #49).
597
+ */
598
+ getDailyBonus: () => Promise<DailyBonusStatus>;
599
+ /**
600
+ * Claim the daily bonus. Atomic — concurrent claims race on a
601
+ * CAS check and only one wins; the loser sees a
602
+ * `cooldown_active` error. Reward formula:
603
+ * `base(10₽) * min(1 + streak*0.1, 3.0) * (0.5 + Math.random())`.
604
+ */
605
+ claimDailyBonus: () => Promise<DailyBonusClaimResult>;
606
+ /**
607
+ * Quest catalog with the caller's progress folded in. Pass
608
+ * filters to narrow by `type` (daily/weekly/one_time) or
609
+ * lifecycle `status`.
610
+ */
611
+ getQuests: (filter?: {
612
+ type?: "daily" | "weekly" | "one_time";
613
+ status?: "available" | "in_progress" | "completed" | "claimed";
614
+ }) => Promise<Quest[]>;
615
+ /**
616
+ * Claim a quest reward. Eligibility is rechecked server-side
617
+ * before crediting. Idempotent — a second claim returns
618
+ * `alreadyClaimed: true` with rewardAmount=0.
619
+ */
620
+ completeQuest: (questCode: string) => Promise<QuestCompleteResult>;
510
621
  /** Delete user account */
511
622
  deleteAccount: () => Promise<void>;
512
623
  /** Export user data (GDPR) */
@@ -602,6 +713,21 @@ export declare class GameCoreClient {
602
713
  validate: (code: string, gameId?: string) => Promise<CouponResult>;
603
714
  /** Get user's currently active coupon */
604
715
  getActive: () => Promise<CouponResult | null>;
716
+ /**
717
+ * Public listing of active coupons applicable to a specific
718
+ * game. Designed for programmatic-SEO landings such as
719
+ * `/promocode/<gameSlug>`. Works without authentication; the
720
+ * api-key still has to be present so the response is scoped to
721
+ * the calling site.
722
+ *
723
+ * Each row carries either a code (when `isPublic === true` and
724
+ * the operator chose to expose it) or a hidden offer
725
+ * (`code: null, isPublic: false`) for cases where the storefront
726
+ * wants to advertise the discount without leaking the code.
727
+ *
728
+ * Available since gamecore-api 2026-05-01 (Wave 5 #54).
729
+ */
730
+ getActiveForGame: (gameSlug: string) => Promise<PublicCoupon[]>;
605
731
  };
606
732
  referrals: {
607
733
  /** Get referral dashboard stats */
package/dist/index.js CHANGED
@@ -81,6 +81,7 @@ class GameCoreClient {
81
81
  getThemeConfig: () => this.request("GET", "/site/theme"),
82
82
  getTranslations: (locale = "ru") => this.request("GET", `/site/translations?locale=${locale}`),
83
83
  getUIConfig: () => this.request("GET", "/site/ui-config"),
84
+ getEmbedAllowlist: () => this.request("GET", "/site/embed-allowlist"),
84
85
  getCookieConsent: () => this.request("GET", "/site/cookie-consent"),
85
86
  getCatalogSections: () => this.request("GET", "/site/catalog-sections"),
86
87
  getBanners: () => this.request("GET", "/site/banners"),
@@ -254,6 +255,8 @@ class GameCoreClient {
254
255
  return this.request("GET", `/catalog/games/${slug}${qs}`);
255
256
  },
256
257
  getRecommendations: (gameSlug, limit = 8) => this.request("GET", `/catalog/games/${gameSlug}/recommendations?limit=${limit}`),
258
+ getGameSystemRequirements: (gameSlug) => this.request("GET", `/catalog/games/${gameSlug}/system-requirements`),
259
+ getGameScreenshots: (gameSlug) => this.request("GET", `/catalog/games/${gameSlug}/screenshots`),
257
260
  getCategories: (gameSlug) => this.request("GET", `/catalog/games/${gameSlug}/categories`),
258
261
  getProducts: (gameSlug, filters) => {
259
262
  const qs = new URLSearchParams;
@@ -290,7 +293,8 @@ class GameCoreClient {
290
293
  sync: (items) => this.request("POST", "/cart/sync", { items }),
291
294
  merge: (items) => this.request("POST", "/cart/merge", { items }),
292
295
  remove: (id) => this.request("DELETE", `/cart/${id}`),
293
- clear: () => this.request("DELETE", "/cart")
296
+ clear: () => this.request("DELETE", "/cart"),
297
+ getCashbackPreview: () => this.request("GET", "/cart/cashback-preview")
294
298
  };
295
299
  checkout = {
296
300
  create: (data, idempotencyKey) => this.request("POST", "/checkout", data, {
@@ -329,6 +333,20 @@ class GameCoreClient {
329
333
  getUnreadCount: () => this.request("GET", "/profile/notifications/unread-count"),
330
334
  markRead: (id) => this.request("POST", `/profile/notifications/${id}/read`),
331
335
  markAllRead: () => this.request("POST", "/profile/notifications/read-all"),
336
+ submitReviewProof: (input) => this.request("POST", "/profile/review-proofs", input),
337
+ getReviewProofs: () => this.request("GET", "/profile/review-proofs"),
338
+ getDailyBonus: () => this.request("GET", "/profile/daily-bonus"),
339
+ claimDailyBonus: () => this.request("POST", "/profile/daily-bonus/claim"),
340
+ getQuests: (filter) => {
341
+ const qs = new URLSearchParams;
342
+ if (filter?.type)
343
+ qs.set("type", filter.type);
344
+ if (filter?.status)
345
+ qs.set("status", filter.status);
346
+ const q = qs.toString();
347
+ return this.request("GET", `/profile/quests${q ? `?${q}` : ""}`);
348
+ },
349
+ completeQuest: (questCode) => this.request("POST", `/profile/quests/${encodeURIComponent(questCode)}/complete`),
332
350
  deleteAccount: () => this.request("POST", "/profile/delete-account"),
333
351
  exportData: () => this.request("GET", "/profile/export"),
334
352
  getSettings: () => this.request("GET", "/profile/settings"),
@@ -368,7 +386,8 @@ class GameCoreClient {
368
386
  apply: (code) => this.request("POST", "/coupons/apply", { code }),
369
387
  remove: () => this.request("DELETE", "/coupons/active"),
370
388
  validate: (code, gameId) => this.request("POST", "/coupons/validate", { code, gameId }),
371
- getActive: () => this.request("GET", "/profile/active-coupon")
389
+ getActive: () => this.request("GET", "/profile/active-coupon"),
390
+ getActiveForGame: (gameSlug) => this.request("GET", `/coupons/active?game=${encodeURIComponent(gameSlug)}`)
372
391
  };
373
392
  referrals = {
374
393
  getStats: () => this.request("GET", "/referral/stats"),
package/dist/types.d.ts CHANGED
@@ -197,6 +197,17 @@ export interface Game {
197
197
  type?: string;
198
198
  deliveryTypes?: string[];
199
199
  categories?: Category[];
200
+ /**
201
+ * Distribution platforms (`steam`, `mobile_game`, `xbox`, `playstation`,
202
+ * `nintendo`, `epic`). Same slugs that `?platform=` filter accepts and
203
+ * that `getPlatforms()` enumerates — single source of truth.
204
+ *
205
+ * Always present in list endpoints (`getGames`, `getHomepageGames`,
206
+ * `getGamesFull`, `getRecentGames`, `getRecommendations`, `search`)
207
+ * and `getGame` since gamecore-api 2026-04-30 — empty array if no
208
+ * platform metadata. Type stays optional for backward-compat with
209
+ * pre-0.17 backends. Treat as `string[]` in modern code.
210
+ */
200
211
  platforms?: string[];
201
212
  regions?: string[];
202
213
  }
@@ -366,6 +377,23 @@ export interface Product {
366
377
  deliveryDataSchema: unknown[];
367
378
  region?: string;
368
379
  platform?: string;
380
+ /**
381
+ * Per-SKU variant metadata for the multi-SKU product picker.
382
+ * Available since gamecore-api 2026-05-01 (Wave 5 #52). All four
383
+ * fields are nullable — products without an operator-configured
384
+ * variant grouping leave them at `null` and the storefront falls
385
+ * back to single-SKU rendering.
386
+ *
387
+ * `riskTier` drives the badge ("Безопасно" / "Нужен логин" /
388
+ * "Полный доступ"); `warningMessage` is short copy under the
389
+ * variant tabs; `variantGroup` is an opaque key shared by SKUs
390
+ * that should be folded into one radio-group; `variantLabel` is
391
+ * the short tab title within that group.
392
+ */
393
+ riskTier?: "safe" | "moderate" | "requires_credentials" | null;
394
+ warningMessage?: string | null;
395
+ variantGroup?: string | null;
396
+ variantLabel?: string | null;
369
397
  }
370
398
  export interface ProductFilters {
371
399
  deliveryType?: string;
@@ -411,6 +439,81 @@ export interface CartItem {
411
439
  */
412
440
  productIcon?: string | null;
413
441
  }
442
+ /**
443
+ * Per-cart cashback preview surfaced by `gc.cart.getCashbackPreview()`.
444
+ * `totalAmount` is the credit (in site balance units) the
445
+ * authenticated user would receive after the cart is paid for at the
446
+ * current state.
447
+ *
448
+ * The endpoint requires authentication — `gc.cart.getCashbackPreview()`
449
+ * throws `GameCoreError(401)` for anonymous callers. Storefronts
450
+ * should skip the request when the user is not signed in instead of
451
+ * relying on a graceful empty response. For tenants whose cashback
452
+ * rates are not configured the API returns `totalAmount: 0` with a
453
+ * UX hint in `rateExplanation`.
454
+ *
455
+ * Available since gamecore-api 2026-05-01 (Wave 5 #51).
456
+ */
457
+ export interface CashbackPreview {
458
+ totalAmount: number;
459
+ currency: "balance";
460
+ breakdown: Array<{
461
+ source: "level_base";
462
+ amount: number;
463
+ label: string;
464
+ }>;
465
+ rateExplanation: string;
466
+ }
467
+ /**
468
+ * One block of Steam-sourced PC requirements (minimum or recommended).
469
+ * Available since gamecore-api 2026-05-01 (Wave 5 #50).
470
+ */
471
+ export interface SystemRequirementsBlock {
472
+ os?: string;
473
+ processor?: string;
474
+ memory?: string;
475
+ graphics?: string;
476
+ directX?: string;
477
+ storage?: string;
478
+ soundCard?: string;
479
+ additional?: string;
480
+ }
481
+ /**
482
+ * Response from `gc.catalog.getGameSystemRequirements()`. `hasData`
483
+ * is false for non-Steam games and Steam games where Steam couldn't
484
+ * supply requirements — frontends should branch on this rather than
485
+ * treating an empty `minimum`/`recommended` as an error.
486
+ */
487
+ export interface SystemRequirementsResponse {
488
+ hasData: boolean;
489
+ minimum: SystemRequirementsBlock;
490
+ recommended: SystemRequirementsBlock;
491
+ source: "steam";
492
+ fetchedAt: string;
493
+ }
494
+ export interface SteamScreenshot {
495
+ thumbnailUrl: string;
496
+ fullUrl: string;
497
+ order: number;
498
+ }
499
+ export interface SteamMovie {
500
+ thumbnailUrl: string;
501
+ webmUrl?: string;
502
+ mp4Url?: string;
503
+ title: string;
504
+ }
505
+ /**
506
+ * Response from `gc.catalog.getGameScreenshots()`. `hasData` mirrors
507
+ * the system-requirements semantics: false when no screenshots OR
508
+ * movies are available.
509
+ */
510
+ export interface ScreenshotsResponse {
511
+ hasData: boolean;
512
+ screenshots: SteamScreenshot[];
513
+ movies: SteamMovie[];
514
+ source: "steam";
515
+ fetchedAt: string;
516
+ }
414
517
  export interface CheckoutRequest {
415
518
  email?: string;
416
519
  items: Array<{
@@ -820,6 +923,107 @@ export interface CouponResult {
820
923
  gameId?: number | null;
821
924
  bonusAmount?: number;
822
925
  }
926
+ /**
927
+ * Public-facing coupon entry returned by `gc.coupons.getActiveForGame()`.
928
+ * Designed for SEO landings (`/promocode/<gameSlug>`). `code` is `null`
929
+ * when the operator chose to advertise the offer without exposing the
930
+ * code itself (`isPublic === false`); the storefront still renders the
931
+ * card with `description`, validity window, and discount details.
932
+ *
933
+ * Available since gamecore-api 2026-05-01 (Wave 5 #54).
934
+ */
935
+ export interface PublicCoupon {
936
+ code: string | null;
937
+ isPublic: boolean;
938
+ type: "bonus_balance" | "markup_discount";
939
+ value: number;
940
+ gameId: string | null;
941
+ description: string | null;
942
+ validFrom: string | null;
943
+ validUntil: string | null;
944
+ usagesLeft: number | null;
945
+ oneTimePerUser: boolean;
946
+ }
947
+ /**
948
+ * Daily-bonus claim status returned by `gc.profile.getDailyBonus()`.
949
+ * `available` is true when the cooldown has elapsed; `nextRewardAmount`
950
+ * is a preview of what the user would get on the next claim using
951
+ * the current streak. Available since gamecore-api 2026-05-01
952
+ * (Wave 5 #49).
953
+ */
954
+ export interface DailyBonusStatus {
955
+ available: boolean;
956
+ currentStreak: number;
957
+ longestStreak: number;
958
+ nextRewardAmount: number;
959
+ nextAvailableAt: string | null;
960
+ streakMultiplier: number;
961
+ lastClaimedAt: string | null;
962
+ totalClaimedAmount: number;
963
+ }
964
+ export interface DailyBonusClaimResult {
965
+ claimedAmount: number;
966
+ newStreak: number;
967
+ newBalance: number;
968
+ nextAvailableAt: string;
969
+ }
970
+ /**
971
+ * Quest catalog entry returned by `gc.profile.getQuests()`.
972
+ * Status lifecycle:
973
+ * available → in_progress → completed → claimed
974
+ * `verificationMode` describes how the quest is checked:
975
+ * * `auto` — backend computes progress from order/event tables.
976
+ * * `trust` — storefront sends a click signal (TG/VK subscribe).
977
+ * * `manual` — admin moderates (e.g. tied to review-proof #56).
978
+ *
979
+ * Available since gamecore-api 2026-05-01 (Wave 5 #49).
980
+ */
981
+ export interface Quest {
982
+ id: number;
983
+ code: string;
984
+ type: "daily" | "weekly" | "one_time";
985
+ category: string | null;
986
+ titleRu: string;
987
+ titleEn: string | null;
988
+ descriptionRu: string | null;
989
+ descriptionEn: string | null;
990
+ rewardAmount: number;
991
+ rewardCurrency: string;
992
+ verificationMode: "auto" | "trust" | "manual";
993
+ iconUrl: string | null;
994
+ status: "available" | "in_progress" | "completed" | "claimed";
995
+ progressCurrent: number;
996
+ progressRequired: number;
997
+ expiresAt: string | null;
998
+ }
999
+ export interface QuestCompleteResult {
1000
+ rewardAmount: number;
1001
+ newBalance: number;
1002
+ alreadyClaimed: boolean;
1003
+ }
1004
+ /**
1005
+ * Review-proof submission row returned by `gc.profile.getReviewProofs()`
1006
+ * and surfaced to admins via `gc.admin.reviewProofs.list()`. Available
1007
+ * since gamecore-api 2026-05-01 (Wave 5 #56).
1008
+ *
1009
+ * Status flow: pending → approved (gets `payoutAmount`) or rejected
1010
+ * (gets `moderatorNote`). Already-resolved rows cannot transition again.
1011
+ */
1012
+ export interface ReviewProof {
1013
+ id: number;
1014
+ siteId: number;
1015
+ userId: number;
1016
+ platform: string;
1017
+ screenshotUrl: string;
1018
+ reviewUrl: string | null;
1019
+ userNote: string | null;
1020
+ status: "pending" | "approved" | "rejected";
1021
+ payoutAmount: number | null;
1022
+ moderatorNote: string | null;
1023
+ reviewedByUserId: number | null;
1024
+ reviewedAt: string | null;
1025
+ createdAt: string;
1026
+ }
823
1027
  export interface ReferralStats {
824
1028
  totalReferrals: number;
825
1029
  lifetimeEarnings: number;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gamecore-api/sdk",
3
- "version": "0.17.0",
3
+ "version": "0.18.0",
4
4
  "description": "TypeScript SDK for GameCore API — browser-safe, zero dependencies",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",