@holdem-api/holdem-sdk 0.0.0-stage → 0.1.1

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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Justin Baker
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
2
-
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
1
+ # Holdem SDK
2
+
3
+ Server-side JavaScript/TypeScript client for Node.js 20+. Zero runtime dependencies, ESM and TypeScript declarations. All requests use `POST /api/actions`.
4
+
5
+ ## Install
6
+
7
+ In your **backend/server project** (for Blitzfold, use `blitzfold-server`):
8
+
9
+ ```sh
10
+ npm install @holdem-api/holdem-sdk
11
+ ```
12
+
13
+ No repository clone, local archive, or SDK build step is required. Node.js 20+ is required. The package is ESM-only. JavaScript `.mjs` files can import it directly. CommonJS applications can use native `await import('@holdem-api/holdem-sdk')` inside an async function; TypeScript compiling to CommonJS must preserve that native import rather than rewriting it to require().
14
+
15
+ Verify installation without an API key or network request:
16
+
17
+ ```sh
18
+ node --input-type=module -e "import { HoldemClient, RangeStore } from '@holdem-api/holdem-sdk'; console.log('SDK ready:', typeof HoldemClient, typeof RangeStore)"
19
+ ```
20
+
21
+ Expected output: `SDK ready: function function`.
22
+
23
+ ## Configure your API access
24
+
25
+ Obtain a Holdem API key and the origin of the API deployment you are authorized to use. Installing this package does not create an API account, provision a server, or include credentials. Configure these variables on your backend:
26
+
27
+ ```dotenv
28
+ HOLDEM_API_KEY=your_api_key
29
+ HOLDEM_API_URL=https://your-api-host.example
30
+ ```
31
+
32
+ Replace the example host with your actual deployment origin, without `/api`. Keep `.env` out of version control. Node 20.6+ can load it using `node --env-file=.env your-script.mjs`; on earlier Node 20 versions set environment variables in your shell or use your application's environment loader. Never put the key in browser code.
33
+
34
+ ## First live request
35
+
36
+ Save this as `holdem-smoke.mjs` in the project where you installed the SDK:
37
+
38
+ ```js
39
+ import { readFile } from 'node:fs/promises'
40
+ import { HoldemClient } from '@holdem-api/holdem-sdk'
41
+
42
+ const { HOLDEM_API_KEY: apiKey, HOLDEM_API_URL: baseUrl } = process.env
43
+ if (!apiKey || !baseUrl || !process.argv[2]) {
44
+ throw new Error('Set HOLDEM_API_KEY and HOLDEM_API_URL; supply a request JSON file')
45
+ }
46
+ const client = new HoldemClient({ apiKey, baseUrl })
47
+ const request = JSON.parse(await readFile(process.argv[2], 'utf8'))
48
+ const result = await client.actions(request)
49
+ console.log({
50
+ chosenAction: result.chosenAction,
51
+ probabilities: result.output,
52
+ equity: result.equity,
53
+ expectedValues: result.expectedValues,
54
+ rangeCount: Object.keys(result.range).length,
55
+ })
56
+ ```
57
+
58
+ Export a real pre-decision game state using the policy encoding below into `actions-request.json`, with top-level `{ "hand": "AsAh", "policy": { "schemaVersion": "holdem-public-board-joint-v1", "context": [...], "history": [...], "legalMask": [0,1,1] } }`. The ellipses describe encoder output; replace them with actual arrays. Do not use made-up features or an empty context. Then run:
59
+
60
+ ```sh
61
+ node --env-file=.env holdem-smoke.mjs actions-request.json
62
+ ```
63
+
64
+ This makes one live request and may consume API quota. It does not execute an action in your game. If this fails, 401/403 indicates an access/authentication issue; 400 indicates invalid input; 502/503 can indicate unavailable equity/model services. Inspect the SDK error status and your API configuration.
65
+
66
+ ## Integrate decisions and track ranges
67
+
68
+ ```ts
69
+ import { HoldemClient, RangeStore } from '@holdem-api/holdem-sdk'
70
+ import type { PublicPolicy } from '@holdem-api/holdem-sdk'
71
+
72
+ const holdem = new HoldemClient({
73
+ apiKey: process.env.HOLDEM_API_KEY!,
74
+ baseUrl: process.env.HOLDEM_API_URL!, // origin without /api
75
+ })
76
+ const ranges = new RangeStore()
77
+
78
+ async function decide(handId: string, playerId: string, policy: PublicPolicy) {
79
+ const result = await holdem.actions({
80
+ policy,
81
+ hand: 'AsAh',
82
+ range: ranges.get(handId, playerId),
83
+ // opponentRanges: [ranges.get(handId, opponentId) ?? null],
84
+ })
85
+ // Your engine executes result.chosenAction, sampling sizes/weights on aggression.
86
+ // Only AFTER successful execution of that action:
87
+ // ranges.record(handId, playerId, result, executedAction)
88
+ return result
89
+ }
90
+ ```
91
+
92
+ `predictActions` is an alias with the same request and response. This replaces the original unpublished SDK's legacy state-based prediction, separate range-update, and equity methods.
93
+
94
+ Supply the exact public encoder output as `policy`: schemaVersion `holdem-public-board-joint-v1`, 316 context features, chronological history of at most 512 events with 32 features each, and legalMask `[fold, passive, aggressive]`. Use the API/parser public encoder; an ordinary game snapshot is not that encoding. Context has board slots (260), actor (9), street (4), six log-scaled amounts/counts, nine public seat records (36), and remaining opponents / 9. Amount scaling is `log1p(max(0,value))/log1p(400)`.
95
+
96
+ ## Complete response
97
+
98
+ - `equity`, `uniformEquity`, `equityBasis`, optional `opponentEquity`.
99
+ - `output`: hand-specific action probabilities; `population`: public model targets.
100
+ - `chosenAction`: sampled fold/call/raise or check/bet. No size is sampled.
101
+ - `sizes` and `weights`: conditional aggression sizing distribution. Your engine applies legal payments, raises, all-ins and side pots.
102
+ - `range`: normalized actor posterior conditioned on chosenAction.
103
+ - `expectedValues`: simplified uniform heads-up showdown benchmark in BB. Assumes one caller, no fold equity, no future betting, no rake or side pots; not strategic or opponent-range-aware EV.
104
+ - Optional `learnedExpectedValues`: experimental observed-action historical returns, only when configured on the API server. Not validated counterfactual action values.
105
+ - `improvementPotential`, `improvement`, allocation identifiers, and all additional server fields are preserved.
106
+
107
+ ## Ranges
108
+
109
+ `WeightedRange` maps exact two-card combos to nonnegative weights. Omit `range` for a uniform board-compatible actor population. Pass a prior to track a reached range; the actual hand must have positive mass. Do not initialize a range with just the known hand. Optional `opponentRanges` has one map or null (uniform) per remaining opponent; never send revealed opponents' actual cards.
110
+
111
+ `RangeStore` provides `set`, `get`, `record`, and `clear(handId?)`. It copies snapshots and isolates each hand/player. It is in memory; persist snapshots using your application's database if they must survive restarts. Explicitly record a posterior only after executing chosenAction. A different action requires a different posterior that this request did not return. Serialize calls/updates per player to prevent stale writes. Clear completed hands. The API itself stores no ranges. This SDK does not infer ranges for observed opponent actions.
112
+
113
+ ## Errors
114
+
115
+ `HoldemValidationError` rejects local invalid input before sending. `HoldemApiError` includes status and body. `HoldemResponseError` rejects malformed successful responses. `HoldemTransportError` wraps network, timeout and cancellation failures. Methods accept `{signal}` as the second argument. Default timeout is 60 seconds and covers body reading. No automatic retries. Keys use `x-api-key`; keep credentials on your backend.
116
+
117
+ ## SDK maintainers: build and publish
118
+
119
+ ```sh
120
+ npm ci
121
+ npm test
122
+ npm pack
123
+ ```
124
+
125
+ The package is configured for public publication as `@holdem-api/holdem-sdk` under the MIT license. For publication:
126
+
127
+ 1. Create or obtain access to the `holdem-api` npm organization. The package name is configured as `@holdem-api/holdem-sdk`. The public access setting and MIT license are already configured.
128
+ 2. Run `npm login` and complete npm's browser authentication/2FA.
129
+ 3. Run `npm test` and `npm pack --dry-run` to inspect contents.
130
+ 4. Run `npm publish --access public` and complete any npm verification prompt.
131
+ 5. In the consuming project, run `npm install @holdem-api/holdem-sdk`.
132
+
133
+ Do not put credentials in source or the package. Before publishing another release, bump the version with `npm version patch` (requires a clean git checkout) or edit the version and update the lockfile.
134
+
135
+ ## Public policy encoding reference
136
+
137
+ The SDK accepts encoded public features; it does not reconstruct betting state. Use consistent seat IDs 0�8 throughout a hand. Cards are indexed by `rankIndex * 4 + suitIndex`, ranks `23456789TJQKA`, suits `cdhs`. Street indexes are preflop=0, flop=1, turn=2, river=3. A one-hot block has exactly one 1; an empty board slot is all zeros. Board slots retain deal order. All amounts below are BB unless a count is specified; scale amounts/counts using `log1p(max(0,x))/log1p(400)`.
138
+
139
+ | Context indexes | Encoding |
140
+ | --- | --- |
141
+ | 0�259 | Five ordered 52-value board one-hot slots |
142
+ | 260�268 | Actor seat one-hot |
143
+ | 269�272 | Street one-hot |
144
+ | 273�278 | Scaled pot, facing amount, call payment, min raise-to, max raise-to, number of seats |
145
+ | 279�314 | Nine seat records: present flag, scaled remaining stack, scaled current-street contribution, active flag; absent seats have four zeros |
146
+ | 315 | Remaining active opponent count divided by 9 |
147
+
148
+ Each history event has 32 values: actor seat one-hot (9), street one-hot (4), action one-hot (6, ordered posts/folds/checks/calls/bets/raises), four scaled values (payment added, raise-to or 0, live post or 0, dead post or 0), and nine scaled refund amounts indexed by seat. Sum multiple refunds to the same seat after the event before scaling. History must contain all actual preceding events in order, across streets, excluding the current decision. Send no artificial padding; empty history is supported. Maximum history length is 512. LegalMask is `[fold, passive, aggressive]` derived from actual engine legality, with only 0/1 values and at least one allowed action. Do not include opponents' private cards or future events. Encode call payment, contributions, and raise limits from actual engine state; do not infer them from incomplete logs.
149
+
150
+ ## Blitzfold migration checklist
151
+
152
+ Install in the server. Replace the `/api/equity_actions` request with `client.actions(payload)` using the existing public encoder. Preserve the full response, including `range`; remove any code deleting it. Use `chosenAction` directly instead of sampling the action category a second time. Sample only the conditional size after aggression and enforce engine legality. Commit the actor range only after that same category executes successfully; reject stale hand/turn responses and clear completed hands. Select optional player styles locally and send them with the request, so decisions need only `/api/actions`. Keep error/cooldown handling and the game's timeout budget. Verify both the development runner and the compiled server can load this ESM package.
@@ -0,0 +1,30 @@
1
+ export * from './types.js';
2
+ export { HoldemValidationError } from './validation.js';
3
+ import type { CallOptions, ClientOptions, ActionsInput, ActionsResult } from './types.js';
4
+ export { RangeStore } from './ranges.js';
5
+ export declare class HoldemApiError extends Error {
6
+ readonly status: number;
7
+ readonly body: unknown;
8
+ constructor(message: string, status: number, body: unknown);
9
+ }
10
+ export declare class HoldemTransportError extends Error {
11
+ constructor(message: string, cause: unknown);
12
+ }
13
+ export declare class HoldemResponseError extends Error {
14
+ readonly status: number;
15
+ constructor(status: number);
16
+ }
17
+ /** Server-side client. Credentials are sent only in the x-api-key header. No automatic retries. */
18
+ export declare class HoldemClient {
19
+ private readonly baseUrl;
20
+ private readonly apiKey;
21
+ private readonly timeoutMs;
22
+ private readonly transport;
23
+ constructor(config: ClientOptions);
24
+ /** Returns the complete action response without dropping analysis or optional fields. */
25
+ actions(input: ActionsInput, call?: CallOptions): Promise<ActionsResult>;
26
+ /** Alias for actions; uses the same /api/actions contract. */
27
+ predictActions(input: ActionsInput, call?: CallOptions): Promise<ActionsResult>;
28
+ private post;
29
+ }
30
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,YAAY,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,iBAAiB,CAAA;AACvD,OAAO,KAAK,EAAE,WAAW,EAAE,aAAa,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAEzF,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAA;AAExC,qBAAa,cAAe,SAAQ,KAAK;aACM,MAAM,EAAE,MAAM;aAAkB,IAAI,EAAE,OAAO;gBAA9E,OAAO,EAAE,MAAM,EAAkB,MAAM,EAAE,MAAM,EAAkB,IAAI,EAAE,OAAO;CAG3F;AACD,qBAAa,oBAAqB,SAAQ,KAAK;gBACjC,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO;CAC5C;AACD,qBAAa,mBAAoB,SAAQ,KAAK;aAChB,MAAM,EAAE,MAAM;gBAAd,MAAM,EAAE,MAAM;CAC3C;AAkBD,mGAAmG;AACnG,qBAAa,YAAY;IACvB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAQ;IAChC,OAAO,CAAC,QAAQ,CAAC,MAAM,CAAQ;IAC/B,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAQ;IAClC,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAyB;gBACvC,MAAM,EAAE,aAAa;IAWjC,yFAAyF;IACnF,OAAO,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;IAI9E,8DAA8D;IACxD,cAAc,CAAC,KAAK,EAAE,YAAY,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,aAAa,CAAC;YAGvE,IAAI;CA2BnB"}
package/dist/index.js ADDED
@@ -0,0 +1,123 @@
1
+ export * from './types.js';
2
+ export { HoldemValidationError } from './validation.js';
3
+ import { check, validateActions, range } from './validation.js';
4
+ export { RangeStore } from './ranges.js';
5
+ export class HoldemApiError extends Error {
6
+ status;
7
+ body;
8
+ constructor(message, status, body) {
9
+ super(message);
10
+ this.status = status;
11
+ this.body = body;
12
+ this.name = 'HoldemApiError';
13
+ }
14
+ }
15
+ export class HoldemTransportError extends Error {
16
+ constructor(message, cause) { super(message, { cause }); this.name = 'HoldemTransportError'; }
17
+ }
18
+ export class HoldemResponseError extends Error {
19
+ status;
20
+ constructor(status) {
21
+ super('Holdem API returned an invalid response');
22
+ this.status = status;
23
+ this.name = 'HoldemResponseError';
24
+ }
25
+ }
26
+ const object = (v) => !!v && typeof v === 'object' && !Array.isArray(v);
27
+ const probability = (v) => typeof v === 'number' && Number.isFinite(v) && v >= 0 && v <= 1;
28
+ function validActions(v) {
29
+ if (!object(v) || v.ok !== true || typeof v.facingBet !== 'boolean' || !object(v.output))
30
+ return false;
31
+ const keys = v.facingBet ? ['fold', 'call', 'raise'] : ['check', 'bet'];
32
+ const output = v.output;
33
+ if (!keys.every(k => probability(output[k])) || Math.abs(keys.reduce((s, k) => s + Number(output[k]), 0) - 1) > 1e-6)
34
+ return false;
35
+ if (!keys.includes(String(v.chosenAction)) || !(Number(output[String(v.chosenAction)]) > 0))
36
+ return false;
37
+ try {
38
+ range(v.range, []);
39
+ }
40
+ catch {
41
+ return false;
42
+ }
43
+ return object(v.range) && Math.abs(Object.values(v.range).reduce((s, n) => s + Number(n), 0) - 1) < 1e-6 &&
44
+ typeof v.requestId === 'string' && typeof v.hand === 'string' && probability(v.equity) && probability(v.uniformEquity) &&
45
+ typeof v.equityBasis === 'string' && object(v.expectedValues) && object(v.expectedValues.output) &&
46
+ object(v.population) && ['fold', 'passive', 'aggressive'].every(k => probability(v.population[k])) &&
47
+ Array.isArray(v.sizes) && v.sizes.every(x => typeof x === 'string') &&
48
+ Array.isArray(v.weights) && v.weights.length === v.sizes.length && v.weights.every(probability);
49
+ }
50
+ /** Server-side client. Credentials are sent only in the x-api-key header. No automatic retries. */
51
+ export class HoldemClient {
52
+ baseUrl;
53
+ apiKey;
54
+ timeoutMs;
55
+ transport;
56
+ constructor(config) {
57
+ check(typeof config.apiKey === 'string' && config.apiKey.trim().length > 0, 'apiKey is required');
58
+ let url;
59
+ try {
60
+ url = new URL(config.baseUrl);
61
+ }
62
+ catch {
63
+ throw new Error('baseUrl must be an absolute HTTP(S) URL');
64
+ }
65
+ check(['http:', 'https:'].includes(url.protocol) && !url.username && !url.password && !url.search && !url.hash, 'Invalid baseUrl');
66
+ this.baseUrl = url.href.replace(/\/$/, '');
67
+ this.apiKey = config.apiKey;
68
+ this.timeoutMs = config.timeoutMs ?? 60_000;
69
+ check(Number.isInteger(this.timeoutMs) && this.timeoutMs > 0 && this.timeoutMs <= 2147483647, 'Invalid timeoutMs');
70
+ this.transport = config.fetch ?? globalThis.fetch;
71
+ check(typeof this.transport === 'function', 'fetch is required');
72
+ }
73
+ /** Returns the complete action response without dropping analysis or optional fields. */
74
+ async actions(input, call) {
75
+ validateActions(input);
76
+ return this.post('/api/actions', input, validActions, call);
77
+ }
78
+ /** Alias for actions; uses the same /api/actions contract. */
79
+ async predictActions(input, call) {
80
+ return this.actions(input, call);
81
+ }
82
+ async post(path, payload, valid, call) {
83
+ const controller = new AbortController();
84
+ const abort = () => controller.abort(call?.signal?.reason);
85
+ if (call?.signal?.aborted)
86
+ abort();
87
+ else
88
+ call?.signal?.addEventListener('abort', abort, { once: true });
89
+ const timer = setTimeout(() => controller.abort(new Error('Holdem request timed out')), this.timeoutMs);
90
+ try {
91
+ let response;
92
+ let body;
93
+ try {
94
+ response = await this.transport(`${this.baseUrl}${path}`, {
95
+ method: 'POST', redirect: 'error', signal: controller.signal,
96
+ headers: { 'Content-Type': 'application/json', 'x-api-key': this.apiKey },
97
+ body: JSON.stringify(payload),
98
+ });
99
+ const raw = await response.text();
100
+ try {
101
+ body = JSON.parse(raw);
102
+ }
103
+ catch {
104
+ body = null;
105
+ }
106
+ }
107
+ catch (cause) {
108
+ throw new HoldemTransportError('Holdem request failed, timed out, or was cancelled', cause);
109
+ }
110
+ if (!response.ok || (object(body) && body.ok === false)) {
111
+ throw new HoldemApiError(`Holdem API request failed (${response.status})`, response.status, body);
112
+ }
113
+ if (!valid(body))
114
+ throw new HoldemResponseError(response.status);
115
+ return body;
116
+ }
117
+ finally {
118
+ clearTimeout(timer);
119
+ call?.signal?.removeEventListener('abort', abort);
120
+ }
121
+ }
122
+ }
123
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,11 @@
1
+ import type { ActionType, ActionsResult, WeightedRange } from './types.js';
2
+ /** In-memory ranges keyed by hand and player. Persist get() snapshots in your application if needed. */
3
+ export declare class RangeStore {
4
+ private readonly hands;
5
+ get(handId: string, playerId: string): WeightedRange | undefined;
6
+ set(handId: string, playerId: string, value: WeightedRange): void;
7
+ /** Call after the engine executes the returned action. Serialize updates per player. */
8
+ record(handId: string, playerId: string, result: ActionsResult, executedAction: ActionType): void;
9
+ clear(handId?: string): void;
10
+ }
11
+ //# sourceMappingURL=ranges.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranges.d.ts","sourceRoot":"","sources":["../src/ranges.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,aAAa,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAG1E,wGAAwG;AACxG,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,KAAK,CAAgD;IACtE,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,aAAa,GAAC,SAAS;IAI9D,GAAG,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,aAAa,GAAG,IAAI;IAOjE,wFAAwF;IACxF,MAAM,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,MAAM,EAAE,aAAa,EAAE,cAAc,EAAE,UAAU,GAAG,IAAI;IAIjG,KAAK,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,IAAI;CAI7B"}
package/dist/ranges.js ADDED
@@ -0,0 +1,31 @@
1
+ import { check, range } from './validation.js';
2
+ /** In-memory ranges keyed by hand and player. Persist get() snapshots in your application if needed. */
3
+ export class RangeStore {
4
+ hands = new Map();
5
+ get(handId, playerId) {
6
+ const value = this.hands.get(handId)?.get(playerId);
7
+ return value === undefined ? undefined : { ...value };
8
+ }
9
+ set(handId, playerId, value) {
10
+ check(handId.length > 0 && playerId.length > 0, 'handId and playerId are required');
11
+ range(value, []);
12
+ let players = this.hands.get(handId);
13
+ if (!players) {
14
+ players = new Map();
15
+ this.hands.set(handId, players);
16
+ }
17
+ players.set(playerId, { ...value });
18
+ }
19
+ /** Call after the engine executes the returned action. Serialize updates per player. */
20
+ record(handId, playerId, result, executedAction) {
21
+ check(executedAction === result.chosenAction, 'Range is conditioned on chosenAction; executed action differs');
22
+ this.set(handId, playerId, result.range);
23
+ }
24
+ clear(handId) {
25
+ if (handId === undefined)
26
+ this.hands.clear();
27
+ else
28
+ this.hands.delete(handId);
29
+ }
30
+ }
31
+ //# sourceMappingURL=ranges.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ranges.js","sourceRoot":"","sources":["../src/ranges.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,iBAAiB,CAAA;AAE9C,wGAAwG;AACxG,MAAM,OAAO,UAAU;IACJ,KAAK,GAAG,IAAI,GAAG,EAAsC,CAAA;IACtE,GAAG,CAAC,MAAc,EAAE,QAAgB;QAClC,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,EAAE,GAAG,CAAC,QAAQ,CAAC,CAAA;QACnD,OAAO,KAAK,KAAK,SAAS,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC,CAAC,EAAE,GAAG,KAAK,EAAE,CAAA;IACvD,CAAC;IACD,GAAG,CAAC,MAAc,EAAE,QAAgB,EAAE,KAAoB;QACxD,KAAK,CAAC,MAAM,CAAC,MAAM,GAAG,CAAC,IAAI,QAAQ,CAAC,MAAM,GAAG,CAAC,EAAE,kCAAkC,CAAC,CAAA;QACnF,KAAK,CAAC,KAAK,EAAE,EAAE,CAAC,CAAA;QAChB,IAAI,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,CAAC,CAAA;QACpC,IAAI,CAAC,OAAO,EAAE,CAAC;YAAC,OAAO,GAAG,IAAI,GAAG,EAAE,CAAC;YAAC,IAAI,CAAC,KAAK,CAAC,GAAG,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;QAAC,CAAC;QACtE,OAAO,CAAC,GAAG,CAAC,QAAQ,EAAE,EAAE,GAAG,KAAK,EAAE,CAAC,CAAA;IACrC,CAAC;IACD,wFAAwF;IACxF,MAAM,CAAC,MAAc,EAAE,QAAgB,EAAE,MAAqB,EAAE,cAA0B;QACxF,KAAK,CAAC,cAAc,KAAK,MAAM,CAAC,YAAY,EAAE,+DAA+D,CAAC,CAAA;QAC9G,IAAI,CAAC,GAAG,CAAC,MAAM,EAAE,QAAQ,EAAE,MAAM,CAAC,KAAK,CAAC,CAAA;IAC1C,CAAC;IACD,KAAK,CAAC,MAAe;QACnB,IAAI,MAAM,KAAK,SAAS;YAAE,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAA;;YACvC,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IAChC,CAAC;CACF"}
@@ -0,0 +1,94 @@
1
+ export type Rank = '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9' | 'T' | 'J' | 'Q' | 'K' | 'A';
2
+ export type Card = `${Rank}${'c' | 'd' | 'h' | 's'}`;
3
+ export type WeightedRange = Readonly<Record<string, number>>;
4
+ export type ActionType = 'fold' | 'check' | 'call' | 'bet' | 'raise';
5
+ export type PlayerStyle = 'tight-passive' | 'tight-aggressive' | 'loose-passive' | 'loose-aggressive';
6
+ /** Exact public encoder output. No private-card features. */
7
+ export interface PublicPolicy {
8
+ schemaVersion: 'holdem-public-board-joint-v1';
9
+ context: readonly number[];
10
+ history: readonly (readonly number[])[];
11
+ legalMask: readonly [0 | 1, 0 | 1, 0 | 1];
12
+ }
13
+ export interface ActionsInput {
14
+ policy: PublicPolicy;
15
+ hand: string;
16
+ range?: WeightedRange;
17
+ opponentRanges?: readonly (WeightedRange | null)[];
18
+ gateSlope?: number;
19
+ style?: PlayerStyle;
20
+ policyVersion?: string;
21
+ }
22
+ export interface CallOptions {
23
+ signal?: AbortSignal;
24
+ }
25
+ export interface ClientOptions {
26
+ apiKey: string;
27
+ baseUrl: string;
28
+ timeoutMs?: number;
29
+ fetch?: typeof globalThis.fetch;
30
+ }
31
+ export type ActionOutput<T> = {
32
+ fold: T;
33
+ call: T;
34
+ raise: T;
35
+ } | {
36
+ check: T;
37
+ bet: T;
38
+ };
39
+ export interface ExpectedValues {
40
+ units: 'BB';
41
+ model: string;
42
+ output: ActionOutput<number | null>;
43
+ sizing: {
44
+ size: string;
45
+ paymentBB: number | null;
46
+ evBB: number | null;
47
+ }[];
48
+ assumptions?: string[];
49
+ [key: string]: unknown;
50
+ }
51
+ export interface ActionsResult {
52
+ ok: true;
53
+ requestId: string;
54
+ hand: string;
55
+ equity: number;
56
+ uniformEquity: number;
57
+ equityBasis: 'uniform-heads-up' | 'opponent-ranges-pot-share';
58
+ facingBet: boolean;
59
+ output: ActionOutput<number>;
60
+ chosenAction: ActionType;
61
+ /** Posterior conditioned on chosenAction; save only after executing that action. */
62
+ range: Record<string, number>;
63
+ population: {
64
+ fold: number;
65
+ passive: number;
66
+ aggressive: number;
67
+ };
68
+ sizes: string[];
69
+ weights: number[];
70
+ expectedValues: ExpectedValues;
71
+ learnedExpectedValues?: ExpectedValues;
72
+ opponentEquity?: {
73
+ equity: number;
74
+ [key: string]: unknown;
75
+ };
76
+ improvementPotential: number;
77
+ improvement: {
78
+ method: string;
79
+ equityGainThreshold: number;
80
+ samplesPerNextCard: number | null;
81
+ applicable: boolean;
82
+ samplesPerCandidate?: number;
83
+ };
84
+ gateSlope: number;
85
+ allocationVersion: string;
86
+ allocationCalibration: string;
87
+ allocationPopulation: string;
88
+ policyVersion?: string;
89
+ style?: PlayerStyle;
90
+ [key: string]: unknown;
91
+ }
92
+ export type PredictActionsInput = ActionsInput;
93
+ export type PredictActionsResult = ActionsResult;
94
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"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"}
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
@@ -0,0 +1,10 @@
1
+ import type { ActionsInput, WeightedRange } from './types.js';
2
+ export declare class HoldemValidationError extends Error {
3
+ constructor(message: string);
4
+ }
5
+ export declare function check(condition: unknown, message: string): asserts condition;
6
+ export declare function amount(value: number, label: string): void;
7
+ export declare function cards(values: readonly string[]): void;
8
+ export declare function range(value: WeightedRange | undefined, board: readonly string[]): void;
9
+ export declare function validateActions(input: ActionsInput): void;
10
+ //# sourceMappingURL=validation.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.d.ts","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAA;AAC7D,qBAAa,qBAAsB,SAAQ,KAAK;gBAClC,OAAO,EAAE,MAAM;CAC5B;AACD,wBAAgB,KAAK,CAAC,SAAS,EAAE,OAAO,EAAE,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAE5E;AACD,wBAAgB,MAAM,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEzD;AACD,wBAAgB,KAAK,CAAC,MAAM,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAGrD;AACD,wBAAgB,KAAK,CAAC,KAAK,EAAE,aAAa,GAAC,SAAS,EAAE,KAAK,EAAE,SAAS,MAAM,EAAE,GAAG,IAAI,CAcpF;AACD,wBAAgB,eAAe,CAAC,KAAK,EAAE,YAAY,GAAG,IAAI,CA0BzD"}
@@ -0,0 +1,69 @@
1
+ export class HoldemValidationError extends Error {
2
+ constructor(message) { super(message); this.name = 'HoldemValidationError'; }
3
+ }
4
+ export function check(condition, message) {
5
+ if (!condition)
6
+ throw new HoldemValidationError(message);
7
+ }
8
+ export function amount(value, label) {
9
+ check(Number.isFinite(value) && value >= 0, `${label} must be finite and nonnegative`);
10
+ }
11
+ export function cards(values) {
12
+ check(Array.isArray(values) && values.every(c => typeof c === 'string' && /^[2-9TJQKA][cdhs]$/.test(c)), 'Invalid card notation');
13
+ check(new Set(values).size === values.length, 'Duplicate physical card');
14
+ }
15
+ export function range(value, board) {
16
+ if (value === undefined)
17
+ return;
18
+ check(value !== null && typeof value === 'object' && !Array.isArray(value), 'Range must be a combo-weight map');
19
+ let mass = 0;
20
+ const seen = new Set();
21
+ for (const [combo, weight] of Object.entries(value)) {
22
+ check(/^[2-9TJQKA][cdhs][2-9TJQKA][cdhs]$/.test(combo), `Invalid combo: ${combo}`);
23
+ const pair = [combo.slice(0, 2), combo.slice(2)];
24
+ cards(pair);
25
+ amount(weight, 'Range weight');
26
+ const key = pair.slice().sort().join('');
27
+ check(!seen.has(key), `Duplicate reversed combo: ${combo}`);
28
+ seen.add(key);
29
+ if (!pair.some(c => board.includes(c)))
30
+ mass += weight;
31
+ }
32
+ check(Number.isFinite(mass) && mass > 0, 'Range must have positive unblocked mass');
33
+ }
34
+ export function validateActions(input) {
35
+ check(input && typeof input === 'object', 'Actions input is required');
36
+ const policy = input.policy;
37
+ check(policy && policy.schemaVersion === 'holdem-public-board-joint-v1', 'Invalid public policy schema');
38
+ check(Array.isArray(policy.context) && policy.context.length === 316 && policy.context.every(Number.isFinite), 'context requires 316 finite features');
39
+ check(Array.isArray(policy.history) && policy.history.length <= 512 && policy.history.every(row => Array.isArray(row) && row.length === 32 && row.every(Number.isFinite)), 'history requires at most 512 events of 32 finite features');
40
+ check(Array.isArray(policy.legalMask) && policy.legalMask.length === 3 && policy.legalMask.every(x => x === 0 || x === 1) && policy.legalMask.some(x => x === 1), 'Invalid legalMask');
41
+ check(typeof input.hand === 'string' && /^[2-9TJQKA][cdhs][2-9TJQKA][cdhs]$/.test(input.hand), 'hand requires an exact combo');
42
+ const board = [];
43
+ let empty = false;
44
+ for (let slot = 0; slot < 5; slot++) {
45
+ const block = policy.context.slice(slot * 52, (slot + 1) * 52);
46
+ check(block.every(x => x === 0 || x === 1) && block.reduce((s, n) => s + n, 0) <= 1, 'Board slots must be one-hot or empty');
47
+ const index = block.indexOf(1);
48
+ if (index < 0)
49
+ empty = true;
50
+ else {
51
+ check(!empty, 'Board slots must be contiguous');
52
+ board.push('23456789TJQKA'[Math.floor(index / 4)] + 'cdhs'[index % 4]);
53
+ }
54
+ }
55
+ cards([input.hand.slice(0, 2), input.hand.slice(2), ...board]);
56
+ range(input.range, board);
57
+ if (input.range)
58
+ check((input.range[input.hand] ?? input.range[input.hand.slice(2) + input.hand.slice(0, 2)] ?? 0) > 0, 'Hand must have positive range weight');
59
+ if (input.opponentRanges !== undefined) {
60
+ check(Array.isArray(input.opponentRanges) && Math.abs(input.opponentRanges.length - policy.context[315] * 9) < 1e-6, 'opponentRanges must match remaining opponents');
61
+ input.opponentRanges.forEach(r => { if (r !== null)
62
+ range(r, board); });
63
+ }
64
+ if (input.gateSlope !== undefined)
65
+ check(Number.isFinite(input.gateSlope) && input.gateSlope > 0 && input.gateSlope <= 200, 'gateSlope must be in (0, 200]');
66
+ if (input.style !== undefined)
67
+ check(['tight-passive', 'tight-aggressive', 'loose-passive', 'loose-aggressive'].includes(input.style), 'Invalid style');
68
+ }
69
+ //# sourceMappingURL=validation.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"validation.js","sourceRoot":"","sources":["../src/validation.ts"],"names":[],"mappings":"AACA,MAAM,OAAO,qBAAsB,SAAQ,KAAK;IAC9C,YAAY,OAAe,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAA,CAAC,CAAC;CACrF;AACD,MAAM,UAAU,KAAK,CAAC,SAAkB,EAAE,OAAe;IACvD,IAAI,CAAC,SAAS;QAAE,MAAM,IAAI,qBAAqB,CAAC,OAAO,CAAC,CAAA;AAC1D,CAAC;AACD,MAAM,UAAU,MAAM,CAAC,KAAa,EAAE,KAAa;IACjD,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,IAAI,KAAK,IAAI,CAAC,EAAE,GAAG,KAAK,iCAAiC,CAAC,CAAA;AACxF,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,MAAyB;IAC7C,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,IAAI,MAAM,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,OAAO,CAAC,KAAK,QAAQ,IAAI,oBAAoB,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,EAAE,uBAAuB,CAAC,CAAA;IACjI,KAAK,CAAC,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAA;AAC1E,CAAC;AACD,MAAM,UAAU,KAAK,CAAC,KAA8B,EAAE,KAAwB;IAC5E,IAAI,KAAK,KAAK,SAAS;QAAE,OAAM;IAC/B,KAAK,CAAC,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,kCAAkC,CAAC,CAAA;IAC/G,IAAI,IAAI,GAAG,CAAC,CAAA;IACZ,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAA;IAC9B,KAAK,MAAM,CAAC,KAAK,EAAE,MAAM,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QACpD,KAAK,CAAC,oCAAoC,CAAC,IAAI,CAAC,KAAK,CAAC,EAAE,kBAAkB,KAAK,EAAE,CAAC,CAAA;QAClF,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,EAAC,CAAC,CAAC,EAAE,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAA;QAC/C,KAAK,CAAC,IAAI,CAAC,CAAC;QAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAAA;QAC3C,MAAM,GAAG,GAAG,IAAI,CAAC,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;QACxC,KAAK,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,EAAE,6BAA6B,KAAK,EAAE,CAAC,CAAC;QAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;QAC1E,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAAE,IAAI,IAAI,MAAM,CAAA;IACxD,CAAC;IACD,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,IAAI,IAAI,GAAG,CAAC,EAAE,yCAAyC,CAAC,CAAA;AACrF,CAAC;AACD,MAAM,UAAU,eAAe,CAAC,KAAmB;IACjD,KAAK,CAAC,KAAK,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,2BAA2B,CAAC,CAAA;IACtE,MAAM,MAAM,GAAG,KAAK,CAAC,MAAM,CAAA;IAC3B,KAAK,CAAC,MAAM,IAAI,MAAM,CAAC,aAAa,KAAK,8BAA8B,EAAE,8BAA8B,CAAC,CAAA;IACxG,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,KAAK,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,sCAAsC,CAAC,CAAA;IACtJ,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,MAAM,IAAI,GAAG,IAAI,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,GAAG,CAAC,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,GAAG,CAAC,MAAM,KAAK,EAAE,IAAI,GAAG,CAAC,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,EAAE,2DAA2D,CAAC,CAAA;IACvO,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC,SAAS,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,MAAM,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,mBAAmB,CAAC,CAAA;IACtL,KAAK,CAAC,OAAO,KAAK,CAAC,IAAI,KAAK,QAAQ,IAAI,oCAAoC,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,8BAA8B,CAAC,CAAA;IAC9H,MAAM,KAAK,GAAa,EAAE,CAAA;IAC1B,IAAI,KAAK,GAAG,KAAK,CAAA;IACjB,KAAK,IAAI,IAAI,GAAC,CAAC,EAAE,IAAI,GAAC,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC;QAChC,MAAM,KAAK,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,IAAI,GAAC,EAAE,EAAE,CAAC,IAAI,GAAC,CAAC,CAAC,GAAC,EAAE,CAAC,CAAA;QACxD,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAC,CAAC,EAAE,EAAE,CAAC,CAAC,GAAC,CAAC,EAAC,CAAC,CAAC,IAAI,CAAC,EAAE,sCAAsC,CAAC,CAAA;QACxH,MAAM,KAAK,GAAG,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;QAC9B,IAAI,KAAK,GAAG,CAAC;YAAE,KAAK,GAAG,IAAI,CAAA;aACtB,CAAC;YAAC,KAAK,CAAC,CAAC,KAAK,EAAE,gCAAgC,CAAC,CAAC;YAAC,KAAK,CAAC,IAAI,CAAC,eAAe,CAAC,IAAI,CAAC,KAAK,CAAC,KAAK,GAAC,CAAC,CAAC,CAAE,GAAG,MAAM,CAAC,KAAK,GAAC,CAAC,CAAE,CAAC,CAAA;QAAC,CAAC;IAChI,CAAC;IACD,KAAK,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAC,CAAC,CAAC,EAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,EAAC,GAAG,KAAK,CAAC,CAAC,CAAA;IAC3D,KAAK,CAAC,KAAK,CAAC,KAAK,EAAC,KAAK,CAAC,CAAA;IACxB,IAAI,KAAK,CAAC,KAAK;QAAE,KAAK,CAAC,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAC,KAAK,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,EAAE,sCAAsC,CAAC,CAAA;IAC5J,IAAI,KAAK,CAAC,cAAc,KAAK,SAAS,EAAE,CAAC;QACvC,KAAK,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,GAAG,CAAC,KAAK,CAAC,cAAc,CAAC,MAAM,GAAG,MAAM,CAAC,OAAO,CAAC,GAAG,CAAE,GAAG,CAAC,CAAC,GAAG,IAAI,EAAE,+CAA+C,CAAC,CAAA;QACtK,KAAK,CAAC,cAAc,CAAC,OAAO,CAAC,CAAC,CAAC,EAAE,GAAG,IAAI,CAAC,KAAK,IAAI;YAAE,KAAK,CAAC,CAAC,EAAC,KAAK,CAAC,CAAA,CAAC,CAAC,CAAC,CAAA;IACvE,CAAC;IACD,IAAI,KAAK,CAAC,SAAS,KAAK,SAAS;QAAE,KAAK,CAAC,MAAM,CAAC,QAAQ,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,KAAK,CAAC,SAAS,GAAG,CAAC,IAAI,KAAK,CAAC,SAAS,IAAI,GAAG,EAAE,+BAA+B,CAAC,CAAA;IAC5J,IAAI,KAAK,CAAC,KAAK,KAAK,SAAS;QAAE,KAAK,CAAC,CAAC,eAAe,EAAC,kBAAkB,EAAC,eAAe,EAAC,kBAAkB,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,KAAK,CAAC,EAAE,eAAe,CAAC,CAAA;AACtJ,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,33 @@
1
- {
2
- "name": "@holdem-api/holdem-sdk",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
1
+ {
2
+ "name": "@holdem-api/holdem-sdk",
3
+ "version": "0.1.1",
4
+ "description": "Typed server-side client for Holdem API",
5
+ "type": "module",
6
+ "main": "./dist/index.js",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js"
12
+ }
13
+ },
14
+ "files": [
15
+ "dist",
16
+ "README.md"
17
+ ],
18
+ "engines": {
19
+ "node": ">=20"
20
+ },
21
+ "scripts": {
22
+ "build": "tsc -p tsconfig.json",
23
+ "typecheck": "tsc -p tsconfig.json --noEmit",
24
+ "test": "npm run build && node --test test/client.test.mjs"
25
+ },
26
+ "devDependencies": {
27
+ "typescript": "5.7.3"
28
+ },
29
+ "license": "MIT",
30
+ "publishConfig": {
31
+ "access": "public"
32
+ }
33
+ }