zuburi-game-platform 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.
- package/README.md +214 -0
- package/dist/achievements/listAchievements.d.ts +5 -0
- package/dist/achievements/listAchievements.d.ts.map +1 -0
- package/dist/achievements/listAchievements.js +8 -0
- package/dist/achievements/listAchievements.js.map +1 -0
- package/dist/client/createGamePlatformClient.d.ts +4 -0
- package/dist/client/createGamePlatformClient.d.ts.map +1 -0
- package/dist/client/createGamePlatformClient.js +37 -0
- package/dist/client/createGamePlatformClient.js.map +1 -0
- package/dist/client/types.d.ts +35 -0
- package/dist/client/types.d.ts.map +1 -0
- package/dist/client/types.js +2 -0
- package/dist/client/types.js.map +1 -0
- package/dist/errors/GamePlatformError.d.ts +11 -0
- package/dist/errors/GamePlatformError.d.ts.map +1 -0
- package/dist/errors/GamePlatformError.js +13 -0
- package/dist/errors/GamePlatformError.js.map +1 -0
- package/dist/errors/normalizeError.d.ts +3 -0
- package/dist/errors/normalizeError.d.ts.map +1 -0
- package/dist/errors/normalizeError.js +69 -0
- package/dist/errors/normalizeError.js.map +1 -0
- package/dist/events/emitEvent.d.ts +5 -0
- package/dist/events/emitEvent.d.ts.map +1 -0
- package/dist/events/emitEvent.js +25 -0
- package/dist/events/emitEvent.js.map +1 -0
- package/dist/events/idempotency.d.ts +2 -0
- package/dist/events/idempotency.d.ts.map +1 -0
- package/dist/events/idempotency.js +14 -0
- package/dist/events/idempotency.js.map +1 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/internal/callRpc.d.ts +20 -0
- package/dist/internal/callRpc.d.ts.map +1 -0
- package/dist/internal/callRpc.js +40 -0
- package/dist/internal/callRpc.js.map +1 -0
- package/dist/internal/mappers.d.ts +24 -0
- package/dist/internal/mappers.d.ts.map +1 -0
- package/dist/internal/mappers.js +184 -0
- package/dist/internal/mappers.js.map +1 -0
- package/dist/internal/validateApiVersion.d.ts +4 -0
- package/dist/internal/validateApiVersion.d.ts.map +1 -0
- package/dist/internal/validateApiVersion.js +12 -0
- package/dist/internal/validateApiVersion.js.map +1 -0
- package/dist/internal/validators.d.ts +14 -0
- package/dist/internal/validators.d.ts.map +1 -0
- package/dist/internal/validators.js +53 -0
- package/dist/internal/validators.js.map +1 -0
- package/dist/login-bonus/claimAvailable.d.ts +5 -0
- package/dist/login-bonus/claimAvailable.d.ts.map +1 -0
- package/dist/login-bonus/claimAvailable.js +10 -0
- package/dist/login-bonus/claimAvailable.js.map +1 -0
- package/dist/login-bonus/claimProgram.d.ts +5 -0
- package/dist/login-bonus/claimProgram.d.ts.map +1 -0
- package/dist/login-bonus/claimProgram.js +12 -0
- package/dist/login-bonus/claimProgram.js.map +1 -0
- package/dist/login-bonus/getStatus.d.ts +5 -0
- package/dist/login-bonus/getStatus.d.ts.map +1 -0
- package/dist/login-bonus/getStatus.js +8 -0
- package/dist/login-bonus/getStatus.js.map +1 -0
- package/dist/points/getAccount.d.ts +5 -0
- package/dist/points/getAccount.d.ts.map +1 -0
- package/dist/points/getAccount.js +8 -0
- package/dist/points/getAccount.js.map +1 -0
- package/dist/points/listTransactions.d.ts +5 -0
- package/dist/points/listTransactions.d.ts.map +1 -0
- package/dist/points/listTransactions.js +17 -0
- package/dist/points/listTransactions.js.map +1 -0
- package/dist/state/getState.d.ts +5 -0
- package/dist/state/getState.d.ts.map +1 -0
- package/dist/state/getState.js +7 -0
- package/dist/state/getState.js.map +1 -0
- package/dist/types/achievement.d.ts +24 -0
- package/dist/types/achievement.d.ts.map +1 -0
- package/dist/types/achievement.js +2 -0
- package/dist/types/achievement.js.map +1 -0
- package/dist/types/api.d.ts +11 -0
- package/dist/types/api.d.ts.map +1 -0
- package/dist/types/api.js +2 -0
- package/dist/types/api.js.map +1 -0
- package/dist/types/event.d.ts +46 -0
- package/dist/types/event.d.ts.map +1 -0
- package/dist/types/event.js +2 -0
- package/dist/types/event.js.map +1 -0
- package/dist/types/loginBonus.d.ts +45 -0
- package/dist/types/loginBonus.d.ts.map +1 -0
- package/dist/types/loginBonus.js +2 -0
- package/dist/types/loginBonus.js.map +1 -0
- package/dist/types/point.d.ts +26 -0
- package/dist/types/point.d.ts.map +1 -0
- package/dist/types/point.js +2 -0
- package/dist/types/point.js.map +1 -0
- package/dist/types/state.d.ts +14 -0
- package/dist/types/state.d.ts.map +1 -0
- package/dist/types/state.js +2 -0
- package/dist/types/state.js.map +1 -0
- package/package.json +48 -0
package/README.md
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
# zuburi-game-platform
|
|
2
|
+
|
|
3
|
+
ZUBURI のゲームから KZTK Game Platform を利用するための、React 非依存 TypeScript SDK です。
|
|
4
|
+
|
|
5
|
+
このパッケージは **npmjs.org の public package** として配布します。リポジトリ全体ではなく、`packages/game-platform` の内容だけが publish 対象です。
|
|
6
|
+
|
|
7
|
+
## Package
|
|
8
|
+
|
|
9
|
+
```text
|
|
10
|
+
Package: zuburi-game-platform
|
|
11
|
+
Registry: https://registry.npmjs.org/
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Install
|
|
15
|
+
|
|
16
|
+
GitHub Packages 用の `.npmrc` や GitHub 認証は不要です。
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
pnpm add zuburi-game-platform @supabase/supabase-js
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
npm / yarn など、npm registry を利用できる別の package manager からも通常どおり install できます。
|
|
23
|
+
|
|
24
|
+
## Basic usage
|
|
25
|
+
|
|
26
|
+
SDK は Supabase Client を生成しません。ゲームが既に利用している、ログイン Session 付きの Client を注入します。
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
import { createGamePlatformClient } from 'zuburi-game-platform'
|
|
30
|
+
import { supabase } from '../lib/supabase'
|
|
31
|
+
|
|
32
|
+
export const platform = createGamePlatformClient({
|
|
33
|
+
supabase,
|
|
34
|
+
gameId: 'coffee_roaster',
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
const state = await platform.getState()
|
|
38
|
+
const account = await platform.points.getAccount()
|
|
39
|
+
const achievements = await platform.achievements.list()
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`service_role` / Supabase Secret key / `userId` は SDK に渡しません。ユーザーは Backend が `auth.uid()` から判定します。
|
|
43
|
+
|
|
44
|
+
## Client events
|
|
45
|
+
|
|
46
|
+
Admin で `client` または `both` に設定された Event のみ送信できます。
|
|
47
|
+
|
|
48
|
+
```ts
|
|
49
|
+
await platform.events.emit({
|
|
50
|
+
type: 'tutorial_completed',
|
|
51
|
+
sourceId: 'tutorial:v1',
|
|
52
|
+
})
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
`sourceId` がある場合、標準の idempotency key は次です。
|
|
56
|
+
|
|
57
|
+
```text
|
|
58
|
+
{gameId}:{eventType}:{sourceId}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
明示的な `idempotencyKey` がある場合はそちらを優先します。
|
|
62
|
+
|
|
63
|
+
## Typed game-local events
|
|
64
|
+
|
|
65
|
+
```ts
|
|
66
|
+
type CoffeeRoasterEvents = {
|
|
67
|
+
tutorial_completed: {
|
|
68
|
+
value?: never
|
|
69
|
+
metadata?: never
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
roast_completed: {
|
|
73
|
+
value: number
|
|
74
|
+
metadata?: {
|
|
75
|
+
difficulty?: 'normal' | 'hard'
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const platform = createGamePlatformClient<CoffeeRoasterEvents>({
|
|
81
|
+
supabase,
|
|
82
|
+
gameId: 'coffee_roaster',
|
|
83
|
+
})
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Event Definition はゲーム側で型を持ち、共通 SDK 自体へゲーム固有 Event をハードコードしません。
|
|
87
|
+
|
|
88
|
+
## Points
|
|
89
|
+
|
|
90
|
+
```ts
|
|
91
|
+
const account = await platform.points.getAccount()
|
|
92
|
+
|
|
93
|
+
const page = await platform.points.listTransactions({
|
|
94
|
+
limit: 30,
|
|
95
|
+
cursor: null,
|
|
96
|
+
})
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
ポイント額は Backend / Admin ルールで決定します。SDK に `points.add()` や `points.set()` はありません。
|
|
100
|
+
|
|
101
|
+
## Achievements
|
|
102
|
+
|
|
103
|
+
```ts
|
|
104
|
+
const achievements = await platform.achievements.list()
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
現在のゲーム固有実績と Global 実績を取得します。進捗・解除判定・報酬付与は Backend が行います。
|
|
108
|
+
|
|
109
|
+
## Login bonus
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const status = await platform.loginBonus.getStatus()
|
|
113
|
+
const result = await platform.loginBonus.claimAvailable()
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
特定 Campaign だけ受け取る場合:
|
|
117
|
+
|
|
118
|
+
```ts
|
|
119
|
+
await platform.loginBonus.claimProgram('summer_campaign_2026')
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
`claimAvailable()` は Backend が現在有効な Standard と Campaign を選択します。ゲーム固有 Standard がある場合は Global Standard より優先されます。
|
|
123
|
+
|
|
124
|
+
## Development write guard
|
|
125
|
+
|
|
126
|
+
```ts
|
|
127
|
+
const platform = createGamePlatformClient({
|
|
128
|
+
supabase,
|
|
129
|
+
gameId: 'coffee_roaster',
|
|
130
|
+
writesEnabled: import.meta.env.VITE_DEVELOPMENT_MODE !== 'true',
|
|
131
|
+
})
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
`writesEnabled=false` の場合、Event送信とLogin Bonus claimは `WRITE_DISABLED` になります。これは開発事故防止であり、セキュリティ境界ではありません。
|
|
135
|
+
|
|
136
|
+
## Security boundary
|
|
137
|
+
|
|
138
|
+
SDKは意図的に以下を提供しません。
|
|
139
|
+
|
|
140
|
+
```text
|
|
141
|
+
points.add()
|
|
142
|
+
points.set()
|
|
143
|
+
achievements.unlock()
|
|
144
|
+
achievements.setProgress()
|
|
145
|
+
events.emitTrusted()
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
スコア、勝敗、ゲーム終了など改ざん耐性が必要な処理は、ゲーム固有 RPC で検証して Trusted Event を発火してください。
|
|
149
|
+
|
|
150
|
+
## Repository development
|
|
151
|
+
|
|
152
|
+
この monorepo は pnpm workspace を使用します。repository root で実行してください。
|
|
153
|
+
|
|
154
|
+
```bash
|
|
155
|
+
pnpm install
|
|
156
|
+
pnpm check
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
workspace 定義は `pnpm-workspace.yaml`、使用する pnpm version は root `package.json` の `packageManager` で固定しています。
|
|
160
|
+
|
|
161
|
+
## Publishing
|
|
162
|
+
|
|
163
|
+
GitHub Actions の利用時間を抑えるため、release用のbuildは**ローカルマシンで作成してrepositoryへcommit**します。
|
|
164
|
+
|
|
165
|
+
repository rootで:
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
pnpm release:prepare
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
これによりローカルでtypecheck / test / buildを行い、次を生成します。
|
|
172
|
+
|
|
173
|
+
```text
|
|
174
|
+
packages/game-platform/dist/
|
|
175
|
+
packages/game-platform/release-build.json
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
`release-build.json` にはsourceとdistのSHA-256が記録されるため、build後にsourceだけ変更した状態ではRelease Actionが失敗します。
|
|
179
|
+
|
|
180
|
+
生成物をcommitしてmainへpushします。
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
git add pnpm-lock.yaml packages/game-platform/dist packages/game-platform/release-build.json packages/game-platform/package.json packages/game-platform/README.md
|
|
184
|
+
git commit -m "Prepare game platform SDK release"
|
|
185
|
+
git push origin main
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
その後:
|
|
189
|
+
|
|
190
|
+
```text
|
|
191
|
+
Actions
|
|
192
|
+
→ Release Game Platform SDK
|
|
193
|
+
→ Run workflow
|
|
194
|
+
→ Branch: main
|
|
195
|
+
→ Run workflow
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
Release Action側では **pnpm install / typecheck / test / buildを実行しません**。commit済みartifactのhash検証、`npm pack --dry-run`、tag作成、npmjs.orgへのpublish、GitHub Release作成だけを行います。
|
|
199
|
+
|
|
200
|
+
実際の registry publish に `npm publish` を使うのは、npm Trusted Publishing / OIDC 認証を npm CLI が処理するためです。workspace の package manager は pnpm のままです。
|
|
201
|
+
|
|
202
|
+
初回 publish だけは npm にパッケージがまだ存在しないため、GitHub Actions secret `NPM_TOKEN` を一時的に使用できます。初回 publish 後は npmjs.com の package settings で `ZUBURI/kztk-game-platform` の `publish-sdk.yml` を Trusted Publisher に登録し、`npm publish` を許可してください。その後は `NPM_TOKEN` を削除し、OIDC で publish できます。
|
|
203
|
+
|
|
204
|
+
ローカルでprebuilt artifactだけ再確認する場合:
|
|
205
|
+
|
|
206
|
+
```bash
|
|
207
|
+
pnpm release:verify
|
|
208
|
+
```
|
|
209
|
+
|
|
210
|
+
詳細は [`../../docs/PUBLISHING.md`](../../docs/PUBLISHING.md) を参照してください。
|
|
211
|
+
|
|
212
|
+
## Supported backend
|
|
213
|
+
|
|
214
|
+
KZTK Game Platform Backend API v1 を対象とします。
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type SupabaseRpcClient } from "../internal/callRpc.js";
|
|
2
|
+
import type { GamePlatformLogger } from "../types/api.js";
|
|
3
|
+
import type { AchievementState } from "../types/achievement.js";
|
|
4
|
+
export declare function listAchievements(supabase: SupabaseRpcClient, gameId: string, logger?: GamePlatformLogger): Promise<AchievementState[]>;
|
|
5
|
+
//# sourceMappingURL=listAchievements.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listAchievements.d.ts","sourceRoot":"","sources":["../../src/achievements/listAchievements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAW,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAGxE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAE/D,wBAAsB,gBAAgB,CACpC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,MAAM,EACd,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAG7B"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { callRpc } from "../internal/callRpc.js";
|
|
2
|
+
import { mapAchievementState } from "../internal/mappers.js";
|
|
3
|
+
import { array, record } from "../internal/validators.js";
|
|
4
|
+
export async function listAchievements(supabase, gameId, logger) {
|
|
5
|
+
const raw = await callRpc(supabase, "game_platform_v1_list_achievements", { p_game_id: gameId }, { logger });
|
|
6
|
+
return array(record(raw, "achievement response").items, "achievement response.items").map(mapAchievementState);
|
|
7
|
+
}
|
|
8
|
+
//# sourceMappingURL=listAchievements.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"listAchievements.js","sourceRoot":"","sources":["../../src/achievements/listAchievements.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAA0B,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AAC5D,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,2BAA2B,CAAA;AAIzD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,QAA2B,EAC3B,MAAc,EACd,MAA2B;IAE3B,MAAM,GAAG,GAAG,MAAM,OAAO,CAAU,QAAQ,EAAE,oCAAoC,EAAE,EAAE,SAAS,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,EAAE,CAAC,CAAA;IACrH,OAAO,KAAK,CAAC,MAAM,CAAC,GAAG,EAAE,sBAAsB,CAAC,CAAC,KAAK,EAAE,4BAA4B,CAAC,CAAC,GAAG,CAAC,mBAAmB,CAAC,CAAA;AAChH,CAAC"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { EventMap, DefaultEventMap } from "../types/event.js";
|
|
2
|
+
import type { CreateGamePlatformClientOptions, GamePlatformClient } from "./types.js";
|
|
3
|
+
export declare function createGamePlatformClient<TEvents extends EventMap = DefaultEventMap>(options: CreateGamePlatformClientOptions): GamePlatformClient<TEvents>;
|
|
4
|
+
//# sourceMappingURL=createGamePlatformClient.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGamePlatformClient.d.ts","sourceRoot":"","sources":["../../src/client/createGamePlatformClient.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,QAAQ,EAAE,eAAe,EAAqB,MAAM,mBAAmB,CAAA;AAWrF,OAAO,KAAK,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,MAAM,YAAY,CAAA;AAGrF,wBAAgB,wBAAwB,CACtC,OAAO,SAAS,QAAQ,GAAG,eAAe,EAC1C,OAAO,EAAE,+BAA+B,GAAG,kBAAkB,CAAC,OAAO,CAAC,CA4BvE"}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { GamePlatformError } from "../errors/GamePlatformError.js";
|
|
2
|
+
import { listAchievements } from "../achievements/listAchievements.js";
|
|
3
|
+
import { emitEvent } from "../events/emitEvent.js";
|
|
4
|
+
import { getLoginBonusStatus } from "../login-bonus/getStatus.js";
|
|
5
|
+
import { claimAvailableLoginBonuses } from "../login-bonus/claimAvailable.js";
|
|
6
|
+
import { claimLoginBonusProgram } from "../login-bonus/claimProgram.js";
|
|
7
|
+
import { getPointAccount } from "../points/getAccount.js";
|
|
8
|
+
import { listTransactions } from "../points/listTransactions.js";
|
|
9
|
+
import { getState } from "../state/getState.js";
|
|
10
|
+
export function createGamePlatformClient(options) {
|
|
11
|
+
const gameId = options.gameId.trim();
|
|
12
|
+
if (!gameId)
|
|
13
|
+
throw new GamePlatformError("GAME_NOT_FOUND", "gameId is required");
|
|
14
|
+
const supabase = options.supabase;
|
|
15
|
+
const writesEnabled = options.writesEnabled ?? true;
|
|
16
|
+
const logger = options.logger;
|
|
17
|
+
return Object.freeze({
|
|
18
|
+
gameId,
|
|
19
|
+
getState: () => getState(supabase, gameId, logger),
|
|
20
|
+
events: Object.freeze({
|
|
21
|
+
emit: (input) => emitEvent(supabase, gameId, writesEnabled, input, logger),
|
|
22
|
+
}),
|
|
23
|
+
points: Object.freeze({
|
|
24
|
+
getAccount: () => getPointAccount(supabase, logger),
|
|
25
|
+
listTransactions: (input) => listTransactions(supabase, input, logger),
|
|
26
|
+
}),
|
|
27
|
+
achievements: Object.freeze({
|
|
28
|
+
list: () => listAchievements(supabase, gameId, logger),
|
|
29
|
+
}),
|
|
30
|
+
loginBonus: Object.freeze({
|
|
31
|
+
getStatus: () => getLoginBonusStatus(supabase, gameId, logger),
|
|
32
|
+
claimAvailable: () => claimAvailableLoginBonuses(supabase, gameId, writesEnabled, logger),
|
|
33
|
+
claimProgram: (programId) => claimLoginBonusProgram(supabase, gameId, programId, writesEnabled, logger),
|
|
34
|
+
}),
|
|
35
|
+
});
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=createGamePlatformClient.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"createGamePlatformClient.js","sourceRoot":"","sources":["../../src/client/createGamePlatformClient.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,gBAAgB,EAAE,MAAM,qCAAqC,CAAA;AACtE,OAAO,EAAE,SAAS,EAAE,MAAM,wBAAwB,CAAA;AAClD,OAAO,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAA;AACjE,OAAO,EAAE,0BAA0B,EAAE,MAAM,kCAAkC,CAAA;AAC7E,OAAO,EAAE,sBAAsB,EAAE,MAAM,gCAAgC,CAAA;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAA;AACzD,OAAO,EAAE,gBAAgB,EAAE,MAAM,+BAA+B,CAAA;AAChE,OAAO,EAAE,QAAQ,EAAE,MAAM,sBAAsB,CAAA;AAK/C,MAAM,UAAU,wBAAwB,CAEtC,OAAwC;IACxC,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CAAA;IACpC,IAAI,CAAC,MAAM;QAAE,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,oBAAoB,CAAC,CAAA;IAEhF,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAwC,CAAA;IACjE,MAAM,aAAa,GAAG,OAAO,CAAC,aAAa,IAAI,IAAI,CAAA;IACnD,MAAM,MAAM,GAAG,OAAO,CAAC,MAAM,CAAA;IAE7B,OAAO,MAAM,CAAC,MAAM,CAAC;QACnB,MAAM;QACN,QAAQ,EAAE,GAAG,EAAE,CAAC,QAAQ,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;QAClD,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;YACpB,IAAI,EAAE,CAAuC,KAAwC,EAAE,EAAE,CACvF,SAAS,CAAiB,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,CAAC;SAC5E,CAAC;QACF,MAAM,EAAE,MAAM,CAAC,MAAM,CAAC;YACpB,UAAU,EAAE,GAAG,EAAE,CAAC,eAAe,CAAC,QAAQ,EAAE,MAAM,CAAC;YACnD,gBAAgB,EAAE,CAAC,KAA+B,EAAE,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,KAAK,EAAE,MAAM,CAAC;SACjG,CAAC;QACF,YAAY,EAAE,MAAM,CAAC,MAAM,CAAC;YAC1B,IAAI,EAAE,GAAG,EAAE,CAAC,gBAAgB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;SACvD,CAAC;QACF,UAAU,EAAE,MAAM,CAAC,MAAM,CAAC;YACxB,SAAS,EAAE,GAAG,EAAE,CAAC,mBAAmB,CAAC,QAAQ,EAAE,MAAM,EAAE,MAAM,CAAC;YAC9D,cAAc,EAAE,GAAG,EAAE,CAAC,0BAA0B,CAAC,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,CAAC;YACzF,YAAY,EAAE,CAAC,SAAiB,EAAE,EAAE,CAAC,sBAAsB,CAAC,QAAQ,EAAE,MAAM,EAAE,SAAS,EAAE,aAAa,EAAE,MAAM,CAAC;SAChH,CAAC;KACH,CAAC,CAAA;AACJ,CAAC"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import type { GamePlatformLogger } from "../types/api.js";
|
|
2
|
+
import type { AchievementState } from "../types/achievement.js";
|
|
3
|
+
import type { DefaultEventMap, EmitEventInputFor, EventMap, EventResult } from "../types/event.js";
|
|
4
|
+
import type { LoginBonusClaimResult, LoginBonusSingleClaimResult, LoginBonusStatus } from "../types/loginBonus.js";
|
|
5
|
+
import type { ListTransactionsOptions, PointAccount, PointTransactionPage } from "../types/point.js";
|
|
6
|
+
import type { GamePlatformState } from "../types/state.js";
|
|
7
|
+
export type SupabaseClientLike = {
|
|
8
|
+
rpc: (...args: any[]) => any;
|
|
9
|
+
};
|
|
10
|
+
export type CreateGamePlatformClientOptions = {
|
|
11
|
+
supabase: SupabaseClientLike;
|
|
12
|
+
gameId: string;
|
|
13
|
+
writesEnabled?: boolean;
|
|
14
|
+
logger?: GamePlatformLogger;
|
|
15
|
+
};
|
|
16
|
+
export type GamePlatformClient<TEvents extends EventMap = DefaultEventMap> = {
|
|
17
|
+
readonly gameId: string;
|
|
18
|
+
getState: () => Promise<GamePlatformState>;
|
|
19
|
+
events: {
|
|
20
|
+
emit: <TType extends keyof TEvents & string>(input: EmitEventInputFor<TEvents, TType>) => Promise<EventResult>;
|
|
21
|
+
};
|
|
22
|
+
points: {
|
|
23
|
+
getAccount: () => Promise<PointAccount>;
|
|
24
|
+
listTransactions: (options?: ListTransactionsOptions) => Promise<PointTransactionPage>;
|
|
25
|
+
};
|
|
26
|
+
achievements: {
|
|
27
|
+
list: () => Promise<AchievementState[]>;
|
|
28
|
+
};
|
|
29
|
+
loginBonus: {
|
|
30
|
+
getStatus: () => Promise<LoginBonusStatus>;
|
|
31
|
+
claimAvailable: () => Promise<LoginBonusClaimResult>;
|
|
32
|
+
claimProgram: (programId: string) => Promise<LoginBonusSingleClaimResult>;
|
|
33
|
+
};
|
|
34
|
+
};
|
|
35
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,gBAAgB,EAAE,MAAM,yBAAyB,CAAA;AAC/D,OAAO,KAAK,EAAE,eAAe,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAClG,OAAO,KAAK,EAAE,qBAAqB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,wBAAwB,CAAA;AAClH,OAAO,KAAK,EAAE,uBAAuB,EAAE,YAAY,EAAE,oBAAoB,EAAE,MAAM,mBAAmB,CAAA;AACpG,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,mBAAmB,CAAA;AAE1D,MAAM,MAAM,kBAAkB,GAAG;IAC/B,GAAG,EAAE,CAAC,GAAG,IAAI,EAAE,GAAG,EAAE,KAAK,GAAG,CAAA;CAC7B,CAAA;AAED,MAAM,MAAM,+BAA+B,GAAG;IAC5C,QAAQ,EAAE,kBAAkB,CAAA;IAC5B,MAAM,EAAE,MAAM,CAAA;IACd,aAAa,CAAC,EAAE,OAAO,CAAA;IACvB,MAAM,CAAC,EAAE,kBAAkB,CAAA;CAC5B,CAAA;AAED,MAAM,MAAM,kBAAkB,CAAC,OAAO,SAAS,QAAQ,GAAG,eAAe,IAAI;IAC3E,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,EAAE,MAAM,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAC1C,MAAM,EAAE;QACN,IAAI,EAAE,CAAC,KAAK,SAAS,MAAM,OAAO,GAAG,MAAM,EACzC,KAAK,EAAE,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,KACrC,OAAO,CAAC,WAAW,CAAC,CAAA;KAC1B,CAAA;IACD,MAAM,EAAE;QACN,UAAU,EAAE,MAAM,OAAO,CAAC,YAAY,CAAC,CAAA;QACvC,gBAAgB,EAAE,CAAC,OAAO,CAAC,EAAE,uBAAuB,KAAK,OAAO,CAAC,oBAAoB,CAAC,CAAA;KACvF,CAAA;IACD,YAAY,EAAE;QACZ,IAAI,EAAE,MAAM,OAAO,CAAC,gBAAgB,EAAE,CAAC,CAAA;KACxC,CAAA;IACD,UAAU,EAAE;QACV,SAAS,EAAE,MAAM,OAAO,CAAC,gBAAgB,CAAC,CAAA;QAC1C,cAAc,EAAE,MAAM,OAAO,CAAC,qBAAqB,CAAC,CAAA;QACpD,YAAY,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,2BAA2B,CAAC,CAAA;KAC1E,CAAA;CACF,CAAA"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/client/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type GamePlatformErrorCode = "AUTH_REQUIRED" | "PROFILE_REQUIRED" | "GAME_NOT_FOUND" | "GAME_INACTIVE" | "EVENT_NOT_FOUND" | "EVENT_NOT_ALLOWED" | "SOURCE_ID_REQUIRED" | "INVALID_EVENT_VALUE" | "INVALID_METADATA" | "IDEMPOTENCY_CONFLICT" | "LOGIN_PROGRAM_NOT_FOUND" | "LOGIN_PROGRAM_NOT_ELIGIBLE" | "NETWORK_ERROR" | "RPC_ERROR" | "INVALID_RESPONSE" | "INVALID_CURSOR" | "INVALID_ARGUMENT" | "API_VERSION_MISMATCH" | "WRITE_DISABLED";
|
|
2
|
+
export declare class GamePlatformError extends Error {
|
|
3
|
+
readonly code: GamePlatformErrorCode;
|
|
4
|
+
readonly retryable: boolean;
|
|
5
|
+
readonly cause: unknown;
|
|
6
|
+
constructor(code: GamePlatformErrorCode, message: string, options?: {
|
|
7
|
+
retryable?: boolean;
|
|
8
|
+
cause?: unknown;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=GamePlatformError.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GamePlatformError.d.ts","sourceRoot":"","sources":["../../src/errors/GamePlatformError.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,qBAAqB,GAC7B,eAAe,GACf,kBAAkB,GAClB,gBAAgB,GAChB,eAAe,GACf,iBAAiB,GACjB,mBAAmB,GACnB,oBAAoB,GACpB,qBAAqB,GACrB,kBAAkB,GAClB,sBAAsB,GACtB,yBAAyB,GACzB,4BAA4B,GAC5B,eAAe,GACf,WAAW,GACX,kBAAkB,GAClB,gBAAgB,GAChB,kBAAkB,GAClB,sBAAsB,GACtB,gBAAgB,CAAA;AAEpB,qBAAa,iBAAkB,SAAQ,KAAK;IAC1C,QAAQ,CAAC,IAAI,EAAE,qBAAqB,CAAA;IACpC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,SAAkB,KAAK,EAAE,OAAO,CAAA;gBAG9B,IAAI,EAAE,qBAAqB,EAC3B,OAAO,EAAE,MAAM,EACf,OAAO,GAAE;QAAE,SAAS,CAAC,EAAE,OAAO,CAAC;QAAC,KAAK,CAAC,EAAE,OAAO,CAAA;KAAO;CAQzD"}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export class GamePlatformError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
retryable;
|
|
4
|
+
cause;
|
|
5
|
+
constructor(code, message, options = {}) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "GamePlatformError";
|
|
8
|
+
this.code = code;
|
|
9
|
+
this.retryable = options.retryable ?? false;
|
|
10
|
+
this.cause = options.cause;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
//# sourceMappingURL=GamePlatformError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"GamePlatformError.js","sourceRoot":"","sources":["../../src/errors/GamePlatformError.ts"],"names":[],"mappings":"AAqBA,MAAM,OAAO,iBAAkB,SAAQ,KAAK;IACjC,IAAI,CAAuB;IAC3B,SAAS,CAAS;IACT,KAAK,CAAS;IAEhC,YACE,IAA2B,EAC3B,OAAe,EACf,UAAoD,EAAE;QAEtD,KAAK,CAAC,OAAO,CAAC,CAAA;QACd,IAAI,CAAC,IAAI,GAAG,mBAAmB,CAAA;QAC/B,IAAI,CAAC,IAAI,GAAG,IAAI,CAAA;QAChB,IAAI,CAAC,SAAS,GAAG,OAAO,CAAC,SAAS,IAAI,KAAK,CAAA;QAC3C,IAAI,CAAC,KAAK,GAAG,OAAO,CAAC,KAAK,CAAA;IAC5B,CAAC;CACF"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeError.d.ts","sourceRoot":"","sources":["../../src/errors/normalizeError.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAA8B,MAAM,wBAAwB,CAAA;AAsDtF,wBAAgB,cAAc,CAAC,KAAK,EAAE,OAAO,GAAG,iBAAiB,CAwBhE"}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
import { GamePlatformError } from "./GamePlatformError.js";
|
|
2
|
+
const BUSINESS_CODES = new Set([
|
|
3
|
+
"AUTH_REQUIRED",
|
|
4
|
+
"PROFILE_REQUIRED",
|
|
5
|
+
"GAME_NOT_FOUND",
|
|
6
|
+
"GAME_INACTIVE",
|
|
7
|
+
"EVENT_NOT_FOUND",
|
|
8
|
+
"EVENT_NOT_ALLOWED",
|
|
9
|
+
"SOURCE_ID_REQUIRED",
|
|
10
|
+
"INVALID_EVENT_VALUE",
|
|
11
|
+
"INVALID_METADATA",
|
|
12
|
+
"IDEMPOTENCY_CONFLICT",
|
|
13
|
+
"LOGIN_PROGRAM_NOT_FOUND",
|
|
14
|
+
"LOGIN_PROGRAM_NOT_ELIGIBLE",
|
|
15
|
+
]);
|
|
16
|
+
function asMessage(error) {
|
|
17
|
+
if (error instanceof Error)
|
|
18
|
+
return error.message;
|
|
19
|
+
if (typeof error === "string")
|
|
20
|
+
return error;
|
|
21
|
+
if (error && typeof error === "object" && "message" in error) {
|
|
22
|
+
const message = error.message;
|
|
23
|
+
if (typeof message === "string")
|
|
24
|
+
return message;
|
|
25
|
+
}
|
|
26
|
+
return "Unknown RPC error";
|
|
27
|
+
}
|
|
28
|
+
function extractBusinessCode(message) {
|
|
29
|
+
for (const code of BUSINESS_CODES) {
|
|
30
|
+
if (message === code || message.startsWith(`${code}:`) || message.includes(` ${code}`)) {
|
|
31
|
+
return code;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
function looksNetworkRelated(error, message) {
|
|
37
|
+
if (error instanceof TypeError && /fetch|network|connection|socket/i.test(message))
|
|
38
|
+
return true;
|
|
39
|
+
return /failed to fetch|networkerror|network request failed|econnreset|etimedout|fetch failed/i.test(message);
|
|
40
|
+
}
|
|
41
|
+
function looksTransientRpc(error) {
|
|
42
|
+
if (!error || typeof error !== "object")
|
|
43
|
+
return false;
|
|
44
|
+
const code = error.code;
|
|
45
|
+
return typeof code === "string" && ["40001", "40P01", "53300", "57P01", "57P03"].includes(code);
|
|
46
|
+
}
|
|
47
|
+
export function normalizeError(error) {
|
|
48
|
+
if (error instanceof GamePlatformError)
|
|
49
|
+
return error;
|
|
50
|
+
const message = asMessage(error);
|
|
51
|
+
const businessCode = extractBusinessCode(message);
|
|
52
|
+
if (businessCode) {
|
|
53
|
+
return new GamePlatformError(businessCode, message, { cause: error });
|
|
54
|
+
}
|
|
55
|
+
if (looksNetworkRelated(error, message)) {
|
|
56
|
+
return new GamePlatformError("NETWORK_ERROR", message, {
|
|
57
|
+
retryable: true,
|
|
58
|
+
cause: error,
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
if (looksTransientRpc(error)) {
|
|
62
|
+
return new GamePlatformError("RPC_ERROR", message, {
|
|
63
|
+
retryable: true,
|
|
64
|
+
cause: error,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
return new GamePlatformError("RPC_ERROR", message, { cause: error });
|
|
68
|
+
}
|
|
69
|
+
//# sourceMappingURL=normalizeError.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalizeError.js","sourceRoot":"","sources":["../../src/errors/normalizeError.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { type SupabaseRpcClient } from "../internal/callRpc.js";
|
|
2
|
+
import type { GamePlatformLogger } from "../types/api.js";
|
|
3
|
+
import type { EmitEventInputFor, EventMap, EventResult } from "../types/event.js";
|
|
4
|
+
export declare function emitEvent<TEvents extends EventMap, TType extends keyof TEvents & string>(supabase: SupabaseRpcClient, gameId: string, writesEnabled: boolean, input: EmitEventInputFor<TEvents, TType>, logger?: GamePlatformLogger): Promise<EventResult>;
|
|
5
|
+
//# sourceMappingURL=emitEvent.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitEvent.d.ts","sourceRoot":"","sources":["../../src/events/emitEvent.ts"],"names":[],"mappings":"AACA,OAAO,EAAW,KAAK,iBAAiB,EAAE,MAAM,wBAAwB,CAAA;AAExE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AACzD,OAAO,KAAK,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AAGjF,wBAAsB,SAAS,CAC7B,OAAO,SAAS,QAAQ,EACxB,KAAK,SAAS,MAAM,OAAO,GAAG,MAAM,EAEpC,QAAQ,EAAE,iBAAiB,EAC3B,MAAM,EAAE,MAAM,EACd,aAAa,EAAE,OAAO,EACtB,KAAK,EAAE,iBAAiB,CAAC,OAAO,EAAE,KAAK,CAAC,EACxC,MAAM,CAAC,EAAE,kBAAkB,GAC1B,OAAO,CAAC,WAAW,CAAC,CAgCtB"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { GamePlatformError } from "../errors/GamePlatformError.js";
|
|
2
|
+
import { callRpc } from "../internal/callRpc.js";
|
|
3
|
+
import { mapEventResult } from "../internal/mappers.js";
|
|
4
|
+
import { createIdempotencyKey } from "./idempotency.js";
|
|
5
|
+
export async function emitEvent(supabase, gameId, writesEnabled, input, logger) {
|
|
6
|
+
if (!writesEnabled) {
|
|
7
|
+
throw new GamePlatformError("WRITE_DISABLED", "Game Platform writes are disabled");
|
|
8
|
+
}
|
|
9
|
+
const idempotencyKey = createIdempotencyKey(gameId, input.type, "sourceId" in input ? input.sourceId : undefined, "idempotencyKey" in input ? input.idempotencyKey : undefined);
|
|
10
|
+
const raw = await callRpc(supabase, "game_platform_v1_emit_event", {
|
|
11
|
+
p_game_id: gameId,
|
|
12
|
+
p_event_type: input.type,
|
|
13
|
+
p_value: "value" in input ? input.value ?? null : null,
|
|
14
|
+
p_source_id: "sourceId" in input ? input.sourceId ?? null : null,
|
|
15
|
+
p_idempotency_key: idempotencyKey,
|
|
16
|
+
p_metadata: "metadata" in input ? input.metadata ?? {} : {},
|
|
17
|
+
}, { logger });
|
|
18
|
+
const result = mapEventResult(raw);
|
|
19
|
+
logger?.debug?.("Game Platform event processed", {
|
|
20
|
+
eventType: input.type,
|
|
21
|
+
duplicate: result.duplicate,
|
|
22
|
+
});
|
|
23
|
+
return result;
|
|
24
|
+
}
|
|
25
|
+
//# sourceMappingURL=emitEvent.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"emitEvent.js","sourceRoot":"","sources":["../../src/events/emitEvent.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,OAAO,EAA0B,MAAM,wBAAwB,CAAA;AACxE,OAAO,EAAE,cAAc,EAAE,MAAM,wBAAwB,CAAA;AAGvD,OAAO,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AAEvD,MAAM,CAAC,KAAK,UAAU,SAAS,CAI7B,QAA2B,EAC3B,MAAc,EACd,aAAsB,EACtB,KAAwC,EACxC,MAA2B;IAE3B,IAAI,CAAC,aAAa,EAAE,CAAC;QACnB,MAAM,IAAI,iBAAiB,CAAC,gBAAgB,EAAE,mCAAmC,CAAC,CAAA;IACpF,CAAC;IAED,MAAM,cAAc,GAAG,oBAAoB,CACzC,MAAM,EACN,KAAK,CAAC,IAAI,EACV,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,SAAS,EAChD,gBAAgB,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,cAAc,CAAC,CAAC,CAAC,SAAS,CAC7D,CAAA;IAED,MAAM,GAAG,GAAG,MAAM,OAAO,CACvB,QAAQ,EACR,6BAA6B,EAC7B;QACE,SAAS,EAAE,MAAM;QACjB,YAAY,EAAE,KAAK,CAAC,IAAI;QACxB,OAAO,EAAE,OAAO,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI;QACtD,WAAW,EAAE,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,IAAI,CAAC,CAAC,CAAC,IAAI;QAChE,iBAAiB,EAAE,cAAc;QACjC,UAAU,EAAE,UAAU,IAAI,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,CAAC,EAAE;KAC5D,EACD,EAAE,MAAM,EAAE,CACX,CAAA;IAED,MAAM,MAAM,GAAG,cAAc,CAAC,GAAG,CAAC,CAAA;IAClC,MAAM,EAAE,KAAK,EAAE,CAAC,+BAA+B,EAAE;QAC/C,SAAS,EAAE,KAAK,CAAC,IAAI;QACrB,SAAS,EAAE,MAAM,CAAC,SAAS;KAC5B,CAAC,CAAA;IACF,OAAO,MAAM,CAAA;AACf,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.d.ts","sourceRoot":"","sources":["../../src/events/idempotency.ts"],"names":[],"mappings":"AAAA,wBAAgB,oBAAoB,CAClC,MAAM,EAAE,MAAM,EACd,SAAS,EAAE,MAAM,EACjB,QAAQ,CAAC,EAAE,MAAM,EACjB,WAAW,CAAC,EAAE,MAAM,GACnB,MAAM,CAaR"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export function createIdempotencyKey(gameId, eventType, sourceId, explicitKey) {
|
|
2
|
+
const explicit = explicitKey?.trim();
|
|
3
|
+
if (explicit)
|
|
4
|
+
return explicit;
|
|
5
|
+
const source = sourceId?.trim();
|
|
6
|
+
if (source)
|
|
7
|
+
return `${gameId}:${eventType}:${source}`;
|
|
8
|
+
const cryptoObject = globalThis.crypto;
|
|
9
|
+
if (cryptoObject && typeof cryptoObject.randomUUID === "function") {
|
|
10
|
+
return cryptoObject.randomUUID();
|
|
11
|
+
}
|
|
12
|
+
return `gp:${Date.now()}:${Math.random().toString(36).slice(2)}:${Math.random().toString(36).slice(2)}`;
|
|
13
|
+
}
|
|
14
|
+
//# sourceMappingURL=idempotency.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"idempotency.js","sourceRoot":"","sources":["../../src/events/idempotency.ts"],"names":[],"mappings":"AAAA,MAAM,UAAU,oBAAoB,CAClC,MAAc,EACd,SAAiB,EACjB,QAAiB,EACjB,WAAoB;IAEpB,MAAM,QAAQ,GAAG,WAAW,EAAE,IAAI,EAAE,CAAA;IACpC,IAAI,QAAQ;QAAE,OAAO,QAAQ,CAAA;IAE7B,MAAM,MAAM,GAAG,QAAQ,EAAE,IAAI,EAAE,CAAA;IAC/B,IAAI,MAAM;QAAE,OAAO,GAAG,MAAM,IAAI,SAAS,IAAI,MAAM,EAAE,CAAA;IAErD,MAAM,YAAY,GAAG,UAAU,CAAC,MAAM,CAAA;IACtC,IAAI,YAAY,IAAI,OAAO,YAAY,CAAC,UAAU,KAAK,UAAU,EAAE,CAAC;QAClE,OAAO,YAAY,CAAC,UAAU,EAAE,CAAA;IAClC,CAAC;IAED,OAAO,MAAM,IAAI,CAAC,GAAG,EAAE,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAA;AACzG,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export { createGamePlatformClient } from "./client/createGamePlatformClient.js";
|
|
2
|
+
export type { CreateGamePlatformClientOptions, GamePlatformClient, SupabaseClientLike } from "./client/types.js";
|
|
3
|
+
export { GamePlatformError } from "./errors/GamePlatformError.js";
|
|
4
|
+
export type { GamePlatformErrorCode } from "./errors/GamePlatformError.js";
|
|
5
|
+
export type { GamePlatformLogger, Json, JsonObject } from "./types/api.js";
|
|
6
|
+
export type { AchievementProgress, AchievementState, AchievementUnlocked } from "./types/achievement.js";
|
|
7
|
+
export type { DefaultEventMap, EmitEventInputFor, EventMap, EventResult, EventSpec } from "./types/event.js";
|
|
8
|
+
export type { LoginBonusClaim, LoginBonusClaimResult, LoginBonusProgramState, LoginBonusSingleClaimResult, LoginBonusStatus } from "./types/loginBonus.js";
|
|
9
|
+
export type { ListTransactionsOptions, PointAccount, PointTransaction, PointTransactionPage } from "./types/point.js";
|
|
10
|
+
export type { GamePlatformState } from "./types/state.js";
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAA;AAC/E,YAAY,EAAE,+BAA+B,EAAE,kBAAkB,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAA;AAEhH,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA;AACjE,YAAY,EAAE,qBAAqB,EAAE,MAAM,+BAA+B,CAAA;AAE1E,YAAY,EAAE,kBAAkB,EAAE,IAAI,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAA;AAC1E,YAAY,EAAE,mBAAmB,EAAE,gBAAgB,EAAE,mBAAmB,EAAE,MAAM,wBAAwB,CAAA;AACxG,YAAY,EAAE,eAAe,EAAE,iBAAiB,EAAE,QAAQ,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,kBAAkB,CAAA;AAC5G,YAAY,EAAE,eAAe,EAAE,qBAAqB,EAAE,sBAAsB,EAAE,2BAA2B,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAA;AAC1J,YAAY,EAAE,uBAAuB,EAAE,YAAY,EAAE,gBAAgB,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAA;AACrH,YAAY,EAAE,iBAAiB,EAAE,MAAM,kBAAkB,CAAA"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,wBAAwB,EAAE,MAAM,sCAAsC,CAAA;AAG/E,OAAO,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAA"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { GamePlatformLogger } from "../types/api.js";
|
|
2
|
+
type RpcErrorLike = {
|
|
3
|
+
code?: string;
|
|
4
|
+
message?: string;
|
|
5
|
+
details?: string;
|
|
6
|
+
hint?: string;
|
|
7
|
+
};
|
|
8
|
+
export type SupabaseRpcClient = {
|
|
9
|
+
rpc: (fn: string, args?: Record<string, unknown>) => PromiseLike<{
|
|
10
|
+
data: unknown;
|
|
11
|
+
error: RpcErrorLike | null;
|
|
12
|
+
}>;
|
|
13
|
+
};
|
|
14
|
+
type CallRpcOptions = {
|
|
15
|
+
logger?: GamePlatformLogger | undefined;
|
|
16
|
+
retries?: number;
|
|
17
|
+
};
|
|
18
|
+
export declare function callRpc<T>(client: SupabaseRpcClient, name: string, params: Record<string, unknown> | undefined, options?: CallRpcOptions): Promise<T>;
|
|
19
|
+
export {};
|
|
20
|
+
//# sourceMappingURL=callRpc.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callRpc.d.ts","sourceRoot":"","sources":["../../src/internal/callRpc.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,iBAAiB,CAAA;AAKzD,KAAK,YAAY,GAAG;IAAE,IAAI,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAExF,MAAM,MAAM,iBAAiB,GAAG;IAC9B,GAAG,EAAE,CACH,EAAE,EAAE,MAAM,EACV,IAAI,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAC3B,WAAW,CAAC;QAAE,IAAI,EAAE,OAAO,CAAC;QAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CAAA;KAAE,CAAC,CAAA;CAChE,CAAA;AAED,KAAK,cAAc,GAAG;IACpB,MAAM,CAAC,EAAE,kBAAkB,GAAG,SAAS,CAAA;IACvC,OAAO,CAAC,EAAE,MAAM,CAAA;CACjB,CAAA;AAID,wBAAsB,OAAO,CAAC,CAAC,EAC7B,MAAM,EAAE,iBAAiB,EACzB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,SAAS,EAC3C,OAAO,GAAE,cAAmB,GAC3B,OAAO,CAAC,CAAC,CAAC,CAkCZ"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { normalizeError } from "../errors/normalizeError.js";
|
|
2
|
+
import { GamePlatformError } from "../errors/GamePlatformError.js";
|
|
3
|
+
import { validateApiVersion } from "./validateApiVersion.js";
|
|
4
|
+
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
5
|
+
export async function callRpc(client, name, params, options = {}) {
|
|
6
|
+
const maxRetries = options.retries ?? 1;
|
|
7
|
+
let attempt = 0;
|
|
8
|
+
while (true) {
|
|
9
|
+
const startedAt = Date.now();
|
|
10
|
+
try {
|
|
11
|
+
const { data, error } = await client.rpc(name, params);
|
|
12
|
+
if (error)
|
|
13
|
+
throw normalizeError(error);
|
|
14
|
+
if (data === null || data === undefined) {
|
|
15
|
+
throw new GamePlatformError("INVALID_RESPONSE", `${name} returned no data`);
|
|
16
|
+
}
|
|
17
|
+
validateApiVersion(data);
|
|
18
|
+
options.logger?.debug?.("Game Platform RPC completed", {
|
|
19
|
+
rpc: name,
|
|
20
|
+
durationMs: Date.now() - startedAt,
|
|
21
|
+
attempt,
|
|
22
|
+
});
|
|
23
|
+
return data;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
const normalized = normalizeError(error);
|
|
27
|
+
options.logger?.warn?.("Game Platform RPC failed", {
|
|
28
|
+
rpc: name,
|
|
29
|
+
code: normalized.code,
|
|
30
|
+
retryable: normalized.retryable,
|
|
31
|
+
attempt,
|
|
32
|
+
});
|
|
33
|
+
if (!normalized.retryable || attempt >= maxRetries)
|
|
34
|
+
throw normalized;
|
|
35
|
+
attempt += 1;
|
|
36
|
+
await sleep(100 * attempt);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=callRpc.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"callRpc.js","sourceRoot":"","sources":["../../src/internal/callRpc.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,6BAA6B,CAAA;AAC5D,OAAO,EAAE,iBAAiB,EAAE,MAAM,gCAAgC,CAAA;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,yBAAyB,CAAA;AAgB5D,MAAM,KAAK,GAAG,CAAC,EAAU,EAAE,EAAE,CAAC,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,UAAU,CAAC,OAAO,EAAE,EAAE,CAAC,CAAC,CAAA;AAErF,MAAM,CAAC,KAAK,UAAU,OAAO,CAC3B,MAAyB,EACzB,IAAY,EACZ,MAA2C,EAC3C,UAA0B,EAAE;IAE5B,MAAM,UAAU,GAAG,OAAO,CAAC,OAAO,IAAI,CAAC,CAAA;IACvC,IAAI,OAAO,GAAG,CAAC,CAAA;IAEf,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,SAAS,GAAG,IAAI,CAAC,GAAG,EAAE,CAAA;QAC5B,IAAI,CAAC;YACH,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,GAAG,MAAM,MAAM,CAAC,GAAG,CAAC,IAAI,EAAE,MAAM,CAAC,CAAA;YACtD,IAAI,KAAK;gBAAE,MAAM,cAAc,CAAC,KAAK,CAAC,CAAA;YACtC,IAAI,IAAI,KAAK,IAAI,IAAI,IAAI,KAAK,SAAS,EAAE,CAAC;gBACxC,MAAM,IAAI,iBAAiB,CAAC,kBAAkB,EAAE,GAAG,IAAI,mBAAmB,CAAC,CAAA;YAC7E,CAAC;YAED,kBAAkB,CAAC,IAAI,CAAC,CAAA;YACxB,OAAO,CAAC,MAAM,EAAE,KAAK,EAAE,CAAC,6BAA6B,EAAE;gBACrD,GAAG,EAAE,IAAI;gBACT,UAAU,EAAE,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS;gBAClC,OAAO;aACR,CAAC,CAAA;YACF,OAAO,IAAS,CAAA;QAClB,CAAC;QAAC,OAAO,KAAK,EAAE,CAAC;YACf,MAAM,UAAU,GAAG,cAAc,CAAC,KAAK,CAAC,CAAA;YACxC,OAAO,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,0BAA0B,EAAE;gBACjD,GAAG,EAAE,IAAI;gBACT,IAAI,EAAE,UAAU,CAAC,IAAI;gBACrB,SAAS,EAAE,UAAU,CAAC,SAAS;gBAC/B,OAAO;aACR,CAAC,CAAA;YAEF,IAAI,CAAC,UAAU,CAAC,SAAS,IAAI,OAAO,IAAI,UAAU;gBAAE,MAAM,UAAU,CAAA;YACpE,OAAO,IAAI,CAAC,CAAA;YACZ,MAAM,KAAK,CAAC,GAAG,GAAG,OAAO,CAAC,CAAA;QAC5B,CAAC;IACH,CAAC;AACH,CAAC"}
|