@pickleball/server-sdk 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.
@@ -0,0 +1,39 @@
1
+ import { PickleballLiveClient } from './index.cjs';
2
+
3
+ interface ProxyEnvironment {
4
+ PICKLEBALL_API_BASE_URL: string;
5
+ PICKLEBALL_APP_ID: string;
6
+ PICKLEBALL_API_KEY: string;
7
+ PICKLEBALL_PROXY_AUTHZ_URL: string;
8
+ PICKLEBALL_PROXY_AUTHZ_SERVICE_TOKEN: string;
9
+ }
10
+ interface PickleballApiEnvironment {
11
+ PICKLEBALL_API_BASE_URL: string;
12
+ PICKLEBALL_APP_ID: string;
13
+ PICKLEBALL_API_KEY: string;
14
+ }
15
+ interface ProxyDependencies {
16
+ env: Partial<ProxyEnvironment>;
17
+ createClient?: (env: PickleballApiEnvironment) => ProxyClient;
18
+ authzFetch?: typeof globalThis.fetch;
19
+ logger?: Pick<Console, "error">;
20
+ }
21
+ type ProxyClient = {
22
+ apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
23
+ sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
24
+ };
25
+ interface SessionActionParams {
26
+ id: string;
27
+ action: string;
28
+ }
29
+ declare function createStableIdempotencyKey(input: {
30
+ appId: string;
31
+ installationId: string;
32
+ externalSessionId: string;
33
+ consentVersion: string;
34
+ }): Promise<string>;
35
+ declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
36
+ declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
37
+ declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
38
+
39
+ export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
@@ -0,0 +1,39 @@
1
+ import { PickleballLiveClient } from './index.js';
2
+
3
+ interface ProxyEnvironment {
4
+ PICKLEBALL_API_BASE_URL: string;
5
+ PICKLEBALL_APP_ID: string;
6
+ PICKLEBALL_API_KEY: string;
7
+ PICKLEBALL_PROXY_AUTHZ_URL: string;
8
+ PICKLEBALL_PROXY_AUTHZ_SERVICE_TOKEN: string;
9
+ }
10
+ interface PickleballApiEnvironment {
11
+ PICKLEBALL_API_BASE_URL: string;
12
+ PICKLEBALL_APP_ID: string;
13
+ PICKLEBALL_API_KEY: string;
14
+ }
15
+ interface ProxyDependencies {
16
+ env: Partial<ProxyEnvironment>;
17
+ createClient?: (env: PickleballApiEnvironment) => ProxyClient;
18
+ authzFetch?: typeof globalThis.fetch;
19
+ logger?: Pick<Console, "error">;
20
+ }
21
+ type ProxyClient = {
22
+ apps: Pick<PickleballLiveClient["apps"], "bootstrap">;
23
+ sessions: Pick<PickleballLiveClient["sessions"], "start" | "refresh" | "end" | "publish" | "unpublish">;
24
+ };
25
+ interface SessionActionParams {
26
+ id: string;
27
+ action: string;
28
+ }
29
+ declare function createStableIdempotencyKey(input: {
30
+ appId: string;
31
+ installationId: string;
32
+ externalSessionId: string;
33
+ consentVersion: string;
34
+ }): Promise<string>;
35
+ declare function handleBootstrap(request: Request, deps: ProxyDependencies): Promise<Response>;
36
+ declare function handleSessions(request: Request, deps: ProxyDependencies): Promise<Response>;
37
+ declare function handleSessionAction(request: Request, params: SessionActionParams, deps: ProxyDependencies): Promise<Response>;
38
+
39
+ export { type ProxyDependencies, type ProxyEnvironment, type SessionActionParams, createStableIdempotencyKey, handleBootstrap, handleSessionAction, handleSessions };
package/dist/proxy.js ADDED
@@ -0,0 +1,403 @@
1
+ import {
2
+ PickleballApiError,
3
+ PickleballConfigurationError,
4
+ PickleballHttpError,
5
+ PickleballInvalidResponseError,
6
+ PickleballNetworkError,
7
+ PickleballTimeoutError,
8
+ createPickleballLiveClient
9
+ } from "./chunk-HBTNLURN.js";
10
+
11
+ // src/proxy.ts
12
+ var MAX_BODY_BYTES = 16 * 1024;
13
+ var SEMVER = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)$/;
14
+ var BUNDLE_ID = /^[A-Za-z0-9](?:[A-Za-z0-9._-]{1,253}[A-Za-z0-9])?$/;
15
+ var INSTALLATION_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{5,255}$/;
16
+ var SAFE_UPSTREAM_CODES = /* @__PURE__ */ new Set([
17
+ "PERMISSION_DENIED",
18
+ "NETWORK_UNAVAILABLE",
19
+ "TOKEN_EXPIRED",
20
+ "SDK_UPGRADE_REQUIRED",
21
+ "SESSION_CONFLICT",
22
+ "RECORDING_FAILED",
23
+ "CONSENT_REQUIRED",
24
+ "SDK_DISABLED",
25
+ "UNKNOWN"
26
+ ]);
27
+ var InvalidRequest = class extends Error {
28
+ };
29
+ var AuthenticationRequired = class extends Error {
30
+ };
31
+ var AuthorizationDecision = class extends Error {
32
+ constructor(status, code) {
33
+ super(code);
34
+ this.status = status;
35
+ this.code = code;
36
+ }
37
+ };
38
+ var AuthorizationUnavailable = class extends Error {
39
+ };
40
+ function response(data, status = 200) {
41
+ return Response.json(data, {
42
+ status,
43
+ headers: {
44
+ "cache-control": "no-store",
45
+ "content-type": "application/json; charset=utf-8"
46
+ }
47
+ });
48
+ }
49
+ function errorResponse(status, code, message) {
50
+ return response({ error: { code, message } }, status);
51
+ }
52
+ function isObject(value) {
53
+ return value !== null && typeof value === "object" && !Array.isArray(value);
54
+ }
55
+ function hasOnlyKeys(value, allowed) {
56
+ const allowedKeys = new Set(allowed);
57
+ return Object.keys(value).every((key) => allowedKeys.has(key));
58
+ }
59
+ function nonblank(value, maximum) {
60
+ return typeof value === "string" && value.trim().length > 0 && value.length <= maximum && !/[\u0000-\u001f\u007f]/.test(value);
61
+ }
62
+ async function bodyObject(request) {
63
+ const contentType = request.headers.get("content-type")?.split(";", 1)[0].trim();
64
+ if (contentType !== "application/json") throw new InvalidRequest();
65
+ const declaredLength = request.headers.get("content-length");
66
+ if (declaredLength !== null) {
67
+ if (!/^\d+$/.test(declaredLength) || Number(declaredLength) > MAX_BODY_BYTES) {
68
+ await request.body?.cancel().catch(() => void 0);
69
+ throw new InvalidRequest();
70
+ }
71
+ }
72
+ const reader = request.body?.getReader();
73
+ if (!reader) throw new InvalidRequest();
74
+ const chunks = [];
75
+ let byteLength = 0;
76
+ while (true) {
77
+ const { done, value } = await reader.read();
78
+ if (done) break;
79
+ byteLength += value.byteLength;
80
+ if (byteLength > MAX_BODY_BYTES) {
81
+ await reader.cancel().catch(() => void 0);
82
+ throw new InvalidRequest();
83
+ }
84
+ chunks.push(value);
85
+ }
86
+ const bytes = new Uint8Array(byteLength);
87
+ let offset = 0;
88
+ for (const chunk of chunks) {
89
+ bytes.set(chunk, offset);
90
+ offset += chunk.byteLength;
91
+ }
92
+ try {
93
+ const text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
94
+ const value = JSON.parse(text);
95
+ if (!isObject(value)) throw new InvalidRequest();
96
+ return value;
97
+ } catch (error) {
98
+ if (error instanceof InvalidRequest) throw error;
99
+ throw new InvalidRequest();
100
+ }
101
+ }
102
+ function userAuthorization(headers) {
103
+ const value = headers.get("authorization") ?? "";
104
+ if (!/^Bearer [^\s,]{1,4096}$/.test(value)) throw new AuthenticationRequired();
105
+ return value;
106
+ }
107
+ function appAttestation(headers) {
108
+ const value = headers.get("x-pickleball-app-attestation");
109
+ if (value === null) return void 0;
110
+ if (!nonblank(value, 8192)) throw new InvalidRequest();
111
+ return value;
112
+ }
113
+ function identityFromHeaders(headers) {
114
+ const sdkVersion = headers.get("x-pickleball-sdk-version") ?? "";
115
+ const platform = headers.get("x-pickleball-platform") ?? "";
116
+ const bundleId = headers.get("x-pickleball-bundle-id") ?? "";
117
+ const installationId = headers.get("x-pickleball-installation-id") ?? "";
118
+ if (!SEMVER.test(sdkVersion) || platform !== "ios" && platform !== "android" || !BUNDLE_ID.test(bundleId) || !INSTALLATION_ID.test(installationId)) {
119
+ throw new InvalidRequest();
120
+ }
121
+ return { sdkVersion, platform, bundleId, installationId };
122
+ }
123
+ function bootstrapBody(value) {
124
+ if (!hasOnlyKeys(value, ["sdkVersion", "platform", "bundleId", "installationId"]) || !SEMVER.test(typeof value.sdkVersion === "string" ? value.sdkVersion : "") || value.platform !== "ios" && value.platform !== "android" || !BUNDLE_ID.test(typeof value.bundleId === "string" ? value.bundleId : "") || !INSTALLATION_ID.test(
125
+ typeof value.installationId === "string" ? value.installationId : ""
126
+ )) {
127
+ throw new InvalidRequest();
128
+ }
129
+ return value;
130
+ }
131
+ function identitiesMatch(left, right) {
132
+ return left.sdkVersion === right.sdkVersion && left.platform === right.platform && left.bundleId === right.bundleId && left.installationId === right.installationId;
133
+ }
134
+ function startBody(value) {
135
+ if (!hasOnlyKeys(value, [
136
+ "externalSessionId",
137
+ "title",
138
+ "visibility",
139
+ "consentVersion",
140
+ "metadata",
141
+ "standby"
142
+ ]) || !nonblank(value.externalSessionId, 256) || !nonblank(value.title, 200) || !nonblank(value.consentVersion, 100) || value.visibility !== void 0 && value.visibility !== "public" && value.visibility !== "private" || value.standby !== void 0 && typeof value.standby !== "boolean") {
143
+ throw new InvalidRequest();
144
+ }
145
+ if (value.metadata !== void 0) {
146
+ if (!isObject(value.metadata) || Object.keys(value.metadata).length > 50) {
147
+ throw new InvalidRequest();
148
+ }
149
+ for (const [key, entry] of Object.entries(value.metadata)) {
150
+ if (!nonblank(key, 100) || !nonblank(entry, 500)) throw new InvalidRequest();
151
+ }
152
+ }
153
+ return value;
154
+ }
155
+ function endBody(value, sessionId) {
156
+ if (!hasOnlyKeys(value, ["sessionId", "reason"]) || value.sessionId !== sessionId || ![
157
+ "user",
158
+ "background_timeout",
159
+ "network_timeout",
160
+ "standby_timeout",
161
+ "error"
162
+ ].includes(typeof value.reason === "string" ? value.reason : "")) {
163
+ throw new InvalidRequest();
164
+ }
165
+ return value;
166
+ }
167
+ function apiEnvironment(value) {
168
+ if (!nonblank(value.PICKLEBALL_API_BASE_URL, 2048) || !nonblank(value.PICKLEBALL_APP_ID, 256) || !nonblank(value.PICKLEBALL_API_KEY, 1024)) {
169
+ throw new PickleballConfigurationError("Proxy is not configured");
170
+ }
171
+ return {
172
+ PICKLEBALL_API_BASE_URL: value.PICKLEBALL_API_BASE_URL,
173
+ PICKLEBALL_APP_ID: value.PICKLEBALL_APP_ID,
174
+ PICKLEBALL_API_KEY: value.PICKLEBALL_API_KEY
175
+ };
176
+ }
177
+ function authorizationEnvironment(value) {
178
+ if (!nonblank(value.PICKLEBALL_PROXY_AUTHZ_URL, 2048) || !nonblank(value.PICKLEBALL_PROXY_AUTHZ_SERVICE_TOKEN, 1024)) {
179
+ throw new AuthorizationUnavailable();
180
+ }
181
+ try {
182
+ const url = new URL(value.PICKLEBALL_PROXY_AUTHZ_URL);
183
+ if (url.protocol !== "https:" || url.username || url.password) throw new Error();
184
+ } catch {
185
+ throw new AuthorizationUnavailable();
186
+ }
187
+ return {
188
+ url: value.PICKLEBALL_PROXY_AUTHZ_URL,
189
+ serviceToken: value.PICKLEBALL_PROXY_AUTHZ_SERVICE_TOKEN
190
+ };
191
+ }
192
+ function defaultCreateClient(env) {
193
+ return createPickleballLiveClient({
194
+ baseUrl: env.PICKLEBALL_API_BASE_URL,
195
+ appId: env.PICKLEBALL_APP_ID,
196
+ apiKey: env.PICKLEBALL_API_KEY
197
+ });
198
+ }
199
+ function clientFor(deps) {
200
+ const configured = apiEnvironment(deps.env);
201
+ return (deps.createClient ?? defaultCreateClient)(configured);
202
+ }
203
+ async function authorize(deps, authorization, operation, identity, scope = {}, attestation) {
204
+ const configured = authorizationEnvironment(deps.env);
205
+ const fetchImplementation = deps.authzFetch ?? globalThis.fetch;
206
+ if (typeof fetchImplementation !== "function") throw new AuthorizationUnavailable();
207
+ const controller = new AbortController();
208
+ const timeout = setTimeout(() => controller.abort(), 5e3);
209
+ let result;
210
+ try {
211
+ result = await fetchImplementation(configured.url, {
212
+ method: "POST",
213
+ headers: {
214
+ accept: "application/json",
215
+ authorization,
216
+ "content-type": "application/json",
217
+ "x-pickleball-proxy-service-token": configured.serviceToken,
218
+ ...attestation === void 0 ? {} : { "x-pickleball-app-attestation": attestation }
219
+ },
220
+ body: JSON.stringify({ operation, identity, ...scope }),
221
+ signal: controller.signal,
222
+ redirect: "error"
223
+ });
224
+ } catch {
225
+ throw new AuthorizationUnavailable();
226
+ } finally {
227
+ clearTimeout(timeout);
228
+ }
229
+ if (result.status === 401 || result.status === 403 || result.status === 429) {
230
+ await result.body?.cancel().catch(() => void 0);
231
+ if (result.status === 401) throw new AuthorizationDecision(401, "UNAUTHORIZED");
232
+ if (result.status === 403) throw new AuthorizationDecision(403, "FORBIDDEN");
233
+ throw new AuthorizationDecision(429, "RATE_LIMITED");
234
+ }
235
+ if (!result.ok) {
236
+ await result.body?.cancel().catch(() => void 0);
237
+ throw new AuthorizationUnavailable();
238
+ }
239
+ let payload;
240
+ try {
241
+ payload = await result.json();
242
+ } catch {
243
+ throw new AuthorizationUnavailable();
244
+ }
245
+ if (!isObject(payload) || !isObject(payload.data) || payload.data.allowed !== true || !nonblank(payload.data.principalId, 256)) {
246
+ throw new AuthorizationUnavailable();
247
+ }
248
+ return { principalId: payload.data.principalId };
249
+ }
250
+ function safeLog(deps, error) {
251
+ const candidate = error;
252
+ (deps.logger ?? console).error("Pickleball proxy request failed", {
253
+ name: typeof candidate?.name === "string" ? candidate.name : "Error",
254
+ code: typeof candidate?.code === "string" ? candidate.code : "UNKNOWN",
255
+ status: typeof candidate?.status === "number" ? candidate.status : void 0
256
+ });
257
+ }
258
+ function mappedError(error, deps) {
259
+ if (error instanceof InvalidRequest) {
260
+ return errorResponse(400, "INVALID_REQUEST", "Invalid request");
261
+ }
262
+ if (error instanceof AuthenticationRequired) {
263
+ return errorResponse(401, "UNAUTHORIZED", "Authentication required");
264
+ }
265
+ if (error instanceof AuthorizationDecision) {
266
+ const message = error.code === "UNAUTHORIZED" ? "Authentication required" : error.code === "RATE_LIMITED" ? "Too many requests" : "Operation not permitted";
267
+ return errorResponse(error.status, error.code, message);
268
+ }
269
+ if (error instanceof AuthorizationUnavailable) {
270
+ safeLog(deps, error);
271
+ return errorResponse(503, "AUTHZ_UNAVAILABLE", "Authorization service unavailable");
272
+ }
273
+ safeLog(deps, error);
274
+ const candidate = error;
275
+ if (error instanceof PickleballApiError || candidate?.name === "PickleballApiError") {
276
+ const status = typeof candidate.status === "number" && candidate.status >= 400 && candidate.status <= 599 ? candidate.status : 502;
277
+ const code = SAFE_UPSTREAM_CODES.has(candidate.code ?? "") ? candidate.code : "UNKNOWN";
278
+ return errorResponse(status, code, "Livestream request was rejected");
279
+ }
280
+ if (error instanceof PickleballConfigurationError) {
281
+ return errorResponse(500, "CONFIGURATION_ERROR", "Proxy is not configured");
282
+ }
283
+ if (error instanceof PickleballTimeoutError || candidate?.name === "PickleballTimeoutError") {
284
+ return errorResponse(504, "TIMEOUT", "Upstream request timed out");
285
+ }
286
+ if (error instanceof PickleballNetworkError || candidate?.name === "PickleballNetworkError") {
287
+ return errorResponse(502, "NETWORK_UNAVAILABLE", "Upstream network unavailable");
288
+ }
289
+ if (error instanceof PickleballInvalidResponseError || candidate?.name === "PickleballInvalidResponseError") {
290
+ return errorResponse(502, "INVALID_RESPONSE", "Invalid upstream response");
291
+ }
292
+ if (error instanceof PickleballHttpError || candidate?.name === "PickleballHttpError") {
293
+ return errorResponse(502, "UNKNOWN", "Upstream request failed");
294
+ }
295
+ return errorResponse(500, "UNKNOWN", "Proxy request failed");
296
+ }
297
+ async function execute(deps, action) {
298
+ try {
299
+ return response({ data: await action() });
300
+ } catch (error) {
301
+ return mappedError(error, deps);
302
+ }
303
+ }
304
+ async function createStableIdempotencyKey(input) {
305
+ const canonical = [
306
+ "pickleball-sdk-v1",
307
+ input.appId,
308
+ input.installationId,
309
+ input.externalSessionId,
310
+ input.consentVersion
311
+ ].join("\0");
312
+ const digest = new Uint8Array(
313
+ await crypto.subtle.digest("SHA-256", new TextEncoder().encode(canonical))
314
+ );
315
+ return Array.from(digest, (byte) => byte.toString(16).padStart(2, "0")).join("");
316
+ }
317
+ async function handleBootstrap(request, deps) {
318
+ return execute(deps, async () => {
319
+ const authorization = userAuthorization(request.headers);
320
+ const attestation = appAttestation(request.headers);
321
+ const identity = identityFromHeaders(request.headers);
322
+ const input = bootstrapBody(await bodyObject(request));
323
+ if (!identitiesMatch(identity, input)) throw new InvalidRequest();
324
+ await authorize(deps, authorization, "bootstrap", identity, {}, attestation);
325
+ return clientFor(deps).apps.bootstrap(input);
326
+ });
327
+ }
328
+ async function handleSessions(request, deps) {
329
+ return execute(deps, async () => {
330
+ const authorization = userAuthorization(request.headers);
331
+ const attestation = appAttestation(request.headers);
332
+ const identity = identityFromHeaders(request.headers);
333
+ const input = startBody(await bodyObject(request));
334
+ const startAuthorization = await authorize(deps, authorization, "start", identity, {
335
+ externalSessionId: input.externalSessionId
336
+ }, attestation);
337
+ const configured = apiEnvironment(deps.env);
338
+ const idempotencyKey = await createStableIdempotencyKey({
339
+ appId: configured.PICKLEBALL_APP_ID,
340
+ installationId: identity.installationId,
341
+ externalSessionId: input.externalSessionId,
342
+ consentVersion: input.consentVersion
343
+ });
344
+ const client = (deps.createClient ?? defaultCreateClient)(configured);
345
+ const grant = await client.sessions.start({ ...identity, ...input, idempotencyKey });
346
+ try {
347
+ await authorize(deps, authorization, "claim", identity, {
348
+ sessionId: grant.sessionId,
349
+ externalSessionId: input.externalSessionId,
350
+ principalId: startAuthorization.principalId
351
+ }, attestation);
352
+ } catch (error) {
353
+ try {
354
+ await client.sessions.end({ sessionId: grant.sessionId, reason: "error" });
355
+ } catch (cleanupError) {
356
+ safeLog(deps, cleanupError);
357
+ }
358
+ throw error;
359
+ }
360
+ return grant;
361
+ });
362
+ }
363
+ async function handleSessionAction(request, params, deps) {
364
+ if (params.action !== "refresh" && params.action !== "end" && params.action !== "publish" && params.action !== "unpublish") {
365
+ return errorResponse(404, "NOT_FOUND", "Route not found");
366
+ }
367
+ return execute(deps, async () => {
368
+ const authorization = userAuthorization(request.headers);
369
+ const attestation = appAttestation(request.headers);
370
+ if (!nonblank(params.id, 256)) throw new InvalidRequest();
371
+ const identity = identityFromHeaders(request.headers);
372
+ const body = await bodyObject(request);
373
+ if (params.action === "refresh") {
374
+ if (Object.keys(body).length !== 0) throw new InvalidRequest();
375
+ await authorize(deps, authorization, "refresh", identity, {
376
+ sessionId: params.id
377
+ }, attestation);
378
+ return clientFor(deps).sessions.refresh(params.id, identity);
379
+ }
380
+ if (params.action === "publish" || params.action === "unpublish") {
381
+ if (!hasOnlyKeys(body, ["sessionId"]) || body.sessionId !== params.id) {
382
+ throw new InvalidRequest();
383
+ }
384
+ await authorize(deps, authorization, params.action, identity, {
385
+ sessionId: params.id
386
+ }, attestation);
387
+ const sessions = clientFor(deps).sessions;
388
+ return params.action === "publish" ? sessions.publish(params.id) : sessions.unpublish(params.id);
389
+ }
390
+ const input = endBody(body, params.id);
391
+ await authorize(deps, authorization, "end", identity, {
392
+ sessionId: params.id
393
+ }, attestation);
394
+ await clientFor(deps).sessions.end(input);
395
+ return null;
396
+ });
397
+ }
398
+ export {
399
+ createStableIdempotencyKey,
400
+ handleBootstrap,
401
+ handleSessionAction,
402
+ handleSessions
403
+ };
package/package.json ADDED
@@ -0,0 +1,65 @@
1
+ {
2
+ "name": "@pickleball/server-sdk",
3
+ "version": "0.1.1",
4
+ "description": "Framework-free server SDK for Pickleball Live",
5
+ "license": "MIT",
6
+ "repository": {
7
+ "type": "git",
8
+ "url": "git+https://github.com/Cabinfood/livestream-pickleball.git",
9
+ "directory": "packages/server-sdk"
10
+ },
11
+ "homepage": "https://github.com/Cabinfood/livestream-pickleball#readme",
12
+ "bugs": "https://github.com/Cabinfood/livestream-pickleball/issues",
13
+ "type": "module",
14
+ "main": "./dist/index.cjs",
15
+ "module": "./dist/index.js",
16
+ "types": "./dist/index.d.ts",
17
+ "exports": {
18
+ ".": {
19
+ "import": {
20
+ "types": "./dist/index.d.ts",
21
+ "default": "./dist/index.js"
22
+ },
23
+ "require": {
24
+ "types": "./dist/index.d.cts",
25
+ "default": "./dist/index.cjs"
26
+ }
27
+ },
28
+ "./proxy": {
29
+ "import": {
30
+ "types": "./dist/proxy.d.ts",
31
+ "default": "./dist/proxy.js"
32
+ },
33
+ "require": {
34
+ "types": "./dist/proxy.d.cts",
35
+ "default": "./dist/proxy.cjs"
36
+ }
37
+ }
38
+ },
39
+ "files": [
40
+ "dist",
41
+ "README.md",
42
+ "LICENSE",
43
+ "CHANGELOG.md"
44
+ ],
45
+ "publishConfig": {
46
+ "access": "public"
47
+ },
48
+ "sideEffects": false,
49
+ "engines": {
50
+ "node": ">=20"
51
+ },
52
+ "devDependencies": {
53
+ "tsup": "^8.5.0",
54
+ "typescript": "^5.8.0",
55
+ "vitest": "^2.1.9",
56
+ "@pickleball/shared": "0.1.0"
57
+ },
58
+ "scripts": {
59
+ "build": "tsup src/index.ts src/proxy.ts --format esm,cjs --dts --clean",
60
+ "test": "vitest run",
61
+ "typecheck": "pnpm run build && tsc --noEmit && pnpm run typecheck:nodenext",
62
+ "typecheck:nodenext": "tsc --noEmit -p tsconfig.nodenext.json",
63
+ "verify:pack": "node scripts/verify-pack.mjs"
64
+ }
65
+ }