@cstar.help/js 0.12.1 → 0.13.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -6,102 +6,199 @@ var ErrorType = {
6
6
  NOT_FOUND_ERROR: "not_found_error",
7
7
  CONFLICT_ERROR: "conflict_error",
8
8
  RATE_LIMIT_ERROR: "rate_limit_error",
9
+ FEATURE_NOT_CONFIGURED: "feature_not_configured",
9
10
  API_ERROR: "api_error",
10
11
  SERVER_ERROR: "server_error"
11
12
  };
13
+ var DEFAULT_DOC_URL = "https://cstar.help/developers/api/errors";
14
+ function normalizeArgs(messageOrStatus, maybeBody, defaults) {
15
+ if (typeof messageOrStatus === "string") {
16
+ return {
17
+ statusCode: defaults?.code === "rate_limit_error" ? 429 : 0,
18
+ body: {
19
+ type: defaults?.type ?? ErrorType.API_ERROR,
20
+ code: defaults?.code ?? "sdk_error",
21
+ message: messageOrStatus,
22
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL,
23
+ ...defaults?.feature ? { feature: defaults.feature } : {},
24
+ ...defaults?.enable_url ? { enable_url: defaults.enable_url } : {}
25
+ }
26
+ };
27
+ }
28
+ return {
29
+ statusCode: messageOrStatus,
30
+ body: maybeBody ?? {
31
+ type: defaults?.type ?? ErrorType.API_ERROR,
32
+ code: defaults?.code ?? "unknown_error",
33
+ message: defaults?.message ?? `Request failed with status ${messageOrStatus}`,
34
+ doc_url: defaults?.doc_url ?? DEFAULT_DOC_URL
35
+ }
36
+ };
37
+ }
12
38
  var CStarError = class extends Error {
13
- constructor(statusCode, body) {
14
- super(body.message);
39
+ constructor(messageOrStatus, body) {
40
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body);
41
+ super(resolved.message);
15
42
  this.name = "CStarError";
16
- this.type = body.type;
17
- this.code = body.code;
18
- this.param = body.param;
19
- this.docUrl = body.doc_url;
20
- this.requestId = body.request_id;
43
+ this.type = resolved.type;
44
+ this.code = resolved.code;
45
+ this.param = resolved.param;
46
+ this.docUrl = resolved.doc_url;
47
+ this.requestId = resolved.request_id;
21
48
  this.statusCode = statusCode;
22
49
  }
23
50
  };
24
51
  var CStarAuthenticationError = class extends CStarError {
25
- constructor(statusCode, body) {
26
- super(statusCode, body);
52
+ constructor(messageOrStatus, body) {
53
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
54
+ type: ErrorType.AUTHENTICATION_ERROR,
55
+ code: "authentication_failed",
56
+ doc_url: `${DEFAULT_DOC_URL}#authentication-error`
57
+ });
58
+ super(statusCode, resolved);
27
59
  this.name = "CStarAuthenticationError";
28
60
  }
29
61
  };
30
62
  var CStarRateLimitError = class extends CStarError {
31
- constructor(statusCode, body, retryAfter) {
32
- super(statusCode, body);
63
+ constructor(messageOrStatus, body, retryAfter) {
64
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
65
+ type: ErrorType.RATE_LIMIT_ERROR,
66
+ code: "rate_limit_exceeded",
67
+ doc_url: `${DEFAULT_DOC_URL}#rate-limit-error`
68
+ });
69
+ super(statusCode === 0 ? 429 : statusCode, resolved);
33
70
  this.name = "CStarRateLimitError";
34
- this.retryAfter = retryAfter;
71
+ this.retryAfter = retryAfter ?? 60;
35
72
  }
36
73
  };
37
74
  var CStarNotFoundError = class extends CStarError {
38
- constructor(statusCode, body) {
39
- super(statusCode, body);
75
+ constructor(messageOrStatus, body) {
76
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
77
+ type: ErrorType.NOT_FOUND_ERROR,
78
+ code: "resource_not_found",
79
+ doc_url: `${DEFAULT_DOC_URL}#not-found-error`
80
+ });
81
+ super(statusCode, resolved);
40
82
  this.name = "CStarNotFoundError";
41
83
  }
42
84
  };
43
85
  var CStarValidationError = class extends CStarError {
44
- constructor(statusCode, body) {
45
- super(statusCode, body);
86
+ constructor(messageOrStatus, body) {
87
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
88
+ type: ErrorType.INVALID_REQUEST_ERROR,
89
+ code: "validation_error",
90
+ doc_url: `${DEFAULT_DOC_URL}#validation-error`
91
+ });
92
+ super(statusCode, resolved);
46
93
  this.name = "CStarValidationError";
47
94
  }
48
95
  };
49
96
  var CStarPermissionError = class extends CStarError {
50
- constructor(statusCode, body) {
51
- super(statusCode, body);
97
+ constructor(messageOrStatus, body) {
98
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
99
+ type: ErrorType.PERMISSION_ERROR,
100
+ code: "permission_denied",
101
+ doc_url: `${DEFAULT_DOC_URL}#permission-error`
102
+ });
103
+ super(statusCode, resolved);
52
104
  this.name = "CStarPermissionError";
53
105
  }
54
106
  };
55
107
  var CStarConflictError = class extends CStarError {
56
- constructor(statusCode, body) {
57
- super(statusCode, body);
108
+ constructor(messageOrStatus, body) {
109
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
110
+ type: ErrorType.CONFLICT_ERROR,
111
+ code: "conflict",
112
+ doc_url: `${DEFAULT_DOC_URL}#conflict-error`
113
+ });
114
+ super(statusCode, resolved);
58
115
  this.name = "CStarConflictError";
59
116
  }
60
117
  };
61
118
  var CStarServerError = class extends CStarError {
62
- constructor(statusCode, body) {
63
- super(statusCode, body);
119
+ constructor(messageOrStatus, body) {
120
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
121
+ type: ErrorType.SERVER_ERROR,
122
+ code: "server_error",
123
+ doc_url: `${DEFAULT_DOC_URL}#server-error`
124
+ });
125
+ super(statusCode, resolved);
64
126
  this.name = "CStarServerError";
65
127
  }
66
128
  };
129
+ var CStarFeatureDisabledError = class extends CStarError {
130
+ constructor(messageOrStatus, body) {
131
+ const { statusCode, body: resolved } = normalizeArgs(messageOrStatus, body, {
132
+ type: ErrorType.FEATURE_NOT_CONFIGURED,
133
+ code: "feature_not_configured",
134
+ doc_url: `${DEFAULT_DOC_URL}#feature-not-configured`
135
+ });
136
+ super(statusCode, resolved);
137
+ this.name = "CStarFeatureDisabledError";
138
+ this.feature = resolved.feature ?? "unknown";
139
+ this.enableUrl = resolved.enable_url ?? "";
140
+ }
141
+ };
142
+ var STATUS_TO_CLASS = {
143
+ 400: CStarValidationError,
144
+ 401: CStarAuthenticationError,
145
+ 403: CStarPermissionError,
146
+ 404: CStarNotFoundError,
147
+ 409: CStarConflictError,
148
+ 422: CStarValidationError,
149
+ 429: CStarRateLimitError
150
+ };
151
+ var TYPE_TO_CLASS = {
152
+ [ErrorType.AUTHENTICATION_ERROR]: CStarAuthenticationError,
153
+ [ErrorType.PERMISSION_ERROR]: CStarPermissionError,
154
+ [ErrorType.RATE_LIMIT_ERROR]: CStarRateLimitError,
155
+ [ErrorType.NOT_FOUND_ERROR]: CStarNotFoundError,
156
+ [ErrorType.INVALID_REQUEST_ERROR]: CStarValidationError,
157
+ [ErrorType.CONFLICT_ERROR]: CStarConflictError,
158
+ [ErrorType.SERVER_ERROR]: CStarServerError,
159
+ [ErrorType.FEATURE_NOT_CONFIGURED]: CStarFeatureDisabledError
160
+ };
67
161
  function parseError(statusCode, body, retryAfter) {
68
162
  const errorBody = body?.error ?? {
69
163
  type: ErrorType.API_ERROR,
70
164
  code: "unknown_error",
71
165
  message: `Request failed with status ${statusCode}`,
72
- doc_url: "https://cstar.help/developers/api/errors"
166
+ doc_url: DEFAULT_DOC_URL
73
167
  };
74
- switch (errorBody.type) {
75
- case ErrorType.AUTHENTICATION_ERROR:
76
- return new CStarAuthenticationError(statusCode, errorBody);
77
- case ErrorType.PERMISSION_ERROR:
78
- return new CStarPermissionError(statusCode, errorBody);
79
- case ErrorType.RATE_LIMIT_ERROR:
80
- return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
81
- case ErrorType.NOT_FOUND_ERROR:
82
- return new CStarNotFoundError(statusCode, errorBody);
83
- case ErrorType.INVALID_REQUEST_ERROR:
84
- return new CStarValidationError(statusCode, errorBody);
85
- case ErrorType.CONFLICT_ERROR:
86
- return new CStarConflictError(statusCode, errorBody);
87
- case ErrorType.SERVER_ERROR:
88
- return new CStarServerError(statusCode, errorBody);
89
- default:
90
- if (statusCode === 403) return new CStarPermissionError(statusCode, errorBody);
91
- if (statusCode === 409) return new CStarConflictError(statusCode, errorBody);
92
- if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
93
- return new CStarError(statusCode, errorBody);
168
+ if (statusCode === 429) {
169
+ return new CStarRateLimitError(statusCode, errorBody, retryAfter ?? 60);
170
+ }
171
+ const StatusCls = STATUS_TO_CLASS[statusCode];
172
+ if (StatusCls) {
173
+ return new StatusCls(statusCode, errorBody);
174
+ }
175
+ const TypeCls = TYPE_TO_CLASS[errorBody.type];
176
+ if (TypeCls) {
177
+ return new TypeCls(statusCode, errorBody);
94
178
  }
179
+ if (statusCode >= 500) return new CStarServerError(statusCode, errorBody);
180
+ return new CStarError(statusCode, errorBody);
95
181
  }
96
182
 
97
183
  // src/client.ts
98
- var SDK_VERSION = "0.5.2";
184
+ var SDK_VERSION = "0.13.0";
99
185
  var DEFAULT_BASE_URL = "https://www.cstar.help";
100
186
  var DEFAULT_API_VERSION = "2026-03-01";
101
187
  var DEFAULT_MAX_RETRIES = 3;
102
188
  var DEFAULT_TIMEOUT = 3e4;
103
189
  var HttpClient = class {
104
190
  constructor(config) {
191
+ /**
192
+ * Side-channel metadata from the most recent response. Holds the server
193
+ * `requestId`, `timestamp`, and (for list endpoints) `pagination`. Mutable
194
+ * per-call — read it immediately after awaiting the call.
195
+ *
196
+ * ```ts
197
+ * const ticket = await cstar.tickets.create({...});
198
+ * console.error('Created ticket', ticket.id, 'request:', cstar.lastMeta.requestId);
199
+ * ```
200
+ */
201
+ this.lastMeta = { requestId: null, timestamp: null, pagination: null };
105
202
  if (!config || typeof config.apiKey !== "string" || config.apiKey.length === 0) {
106
203
  throw new Error(
107
204
  "[cStar] CStarClient: apiKey is required. Pass a secret key (sk_live_... / sk_test_...) or a publishable key (pk_live_... / pk_test_...). See https://cstar.help/developers/quickstart"
@@ -132,7 +229,32 @@ var HttpClient = class {
132
229
  * Automatically retries on 5xx errors and rate limits.
133
230
  */
134
231
  async request(method, path, options) {
135
- const url = this.buildUrl(path, options?.query);
232
+ return this._sendRequest(method, this.buildUrl(path, options?.query), options);
233
+ }
234
+ /**
235
+ * Like `request()` but skips the `/api/v1/teams/{teamId}` prefix. Used for
236
+ * the small set of non-team-scoped endpoints (`/auth/whoami`, etc).
237
+ *
238
+ * @internal — exposed to CStarClient for `whoami()` and similar; not part
239
+ * of the documented public surface.
240
+ */
241
+ async requestUnscoped(method, path, options) {
242
+ const baseUrl = this.config.baseUrl + "/api/v1" + path;
243
+ const finalUrl = this.appendQuery(baseUrl, options?.query);
244
+ return this._sendRequest(method, finalUrl, options);
245
+ }
246
+ appendQuery(url, query) {
247
+ if (!query) return url;
248
+ const params = new URLSearchParams();
249
+ for (const [key, value] of Object.entries(query)) {
250
+ if (value === void 0 || value === null) continue;
251
+ if (Array.isArray(value)) for (const v of value) params.append(`${key}[]`, v);
252
+ else params.set(key, String(value));
253
+ }
254
+ const qs = params.toString();
255
+ return qs ? `${url}?${qs}` : url;
256
+ }
257
+ async _sendRequest(method, url, options) {
136
258
  const headers = this.buildHeaders(options?.idempotencyKey);
137
259
  const body = options?.body ? JSON.stringify(options.body) : void 0;
138
260
  let lastError = null;
@@ -163,13 +285,9 @@ var HttpClient = class {
163
285
  if (response.ok) {
164
286
  if (response.status === 204) return void 0;
165
287
  const json = await response.json();
166
- const requestId2 = response.headers.get("x-request-id") ?? response.headers.get("x-cstar-request-id");
167
- if (requestId2 && json && typeof json === "object" && "meta" in json) {
168
- const existing = json.meta;
169
- const metaBase = existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {};
170
- json.meta = { ...metaBase, requestId: requestId2 };
171
- }
172
- return json;
288
+ const headerRequestId = response.headers.get("x-request-id") ?? response.headers.get("x-cstar-request-id");
289
+ this.captureMeta(json, headerRequestId);
290
+ return this.unwrapEnvelope(json);
173
291
  }
174
292
  const errorBody = await response.json().catch(() => null);
175
293
  const retryAfter = parseInt(response.headers.get("Retry-After") ?? "", 10) || void 0;
@@ -191,6 +309,51 @@ var HttpClient = class {
191
309
  }
192
310
  throw lastError ?? new Error("Request failed after retries");
193
311
  }
312
+ /**
313
+ * Capture `meta`, `requestId`, and `pagination` from the raw response
314
+ * into `this.lastMeta` before unwrapping the envelope.
315
+ */
316
+ captureMeta(body, headerRequestId) {
317
+ if (body && typeof body === "object" && !Array.isArray(body)) {
318
+ const obj = body;
319
+ const meta = obj.meta && typeof obj.meta === "object" ? obj.meta : null;
320
+ const pagination = obj.pagination && typeof obj.pagination === "object" ? obj.pagination : null;
321
+ this.lastMeta = {
322
+ requestId: headerRequestId ?? (typeof meta?.requestId === "string" ? meta.requestId : null),
323
+ timestamp: typeof meta?.timestamp === "string" ? meta.timestamp : null,
324
+ pagination
325
+ };
326
+ return;
327
+ }
328
+ this.lastMeta = { requestId: headerRequestId, timestamp: null, pagination: null };
329
+ }
330
+ /**
331
+ * Unwrap the legacy `{success, data, meta, pagination?}` envelope.
332
+ * - `{success:true, data: array, pagination}` → `{data, pagination, hasMore}`
333
+ * - `{success:true, data: object}` → `data` (bare resource)
334
+ * - other shapes pass through unchanged so subpath SDKs (chat, library) keep
335
+ * their bespoke shapes for now.
336
+ */
337
+ unwrapEnvelope(body) {
338
+ if (!body || typeof body !== "object" || Array.isArray(body)) return body;
339
+ const obj = body;
340
+ if (obj.success !== true || !("data" in obj)) return body;
341
+ const data = obj.data;
342
+ if (Array.isArray(data)) {
343
+ const pagination = obj.pagination ?? null;
344
+ return {
345
+ data,
346
+ pagination: pagination ?? {
347
+ total: data.length,
348
+ page: 1,
349
+ pageSize: data.length,
350
+ hasMore: false
351
+ },
352
+ hasMore: pagination?.hasMore ?? false
353
+ };
354
+ }
355
+ return data;
356
+ }
194
357
  buildUrl(path, query) {
195
358
  const base = `${this.config.baseUrl}/api/v1/teams/${this.config.teamId}${path}`;
196
359
  if (!query) return base;
@@ -1872,7 +2035,12 @@ var GameResource = class {
1872
2035
  async getPlayer(memberId) {
1873
2036
  return this.client.request("GET", `/game/player/${memberId}`);
1874
2037
  }
1875
- /** Get the team leaderboard. */
2038
+ /**
2039
+ * Get the team leaderboard. Returns the unwrapped envelope from the
2040
+ * `/game/leaderboard` endpoint (`{entries, totalParticipants, period, ...}`)
2041
+ * — wider than a flat array because the server includes the period and
2042
+ * a participant count alongside the entries.
2043
+ */
1876
2044
  async leaderboard(params) {
1877
2045
  return this.client.request("GET", "/game/leaderboard", {
1878
2046
  query: params
@@ -1895,12 +2063,12 @@ var GameResource = class {
1895
2063
  */
1896
2064
  subscribeBoss(realtime) {
1897
2065
  const fetchFn = async () => {
1898
- const res = await this.boss.current();
2066
+ const boss = await this.boss.current();
2067
+ const list = boss.active ? [boss] : [];
1899
2068
  return {
1900
- success: true,
1901
- data: res.data.active ? [res.data] : [],
1902
- pagination: { total: res.data.active ? 1 : 0, page: 1, pageSize: 1, hasMore: false },
1903
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2069
+ data: list,
2070
+ pagination: { total: list.length, page: 1, pageSize: 1, hasMore: false },
2071
+ hasMore: false
1904
2072
  };
1905
2073
  };
1906
2074
  return new Subscription(realtime, fetchFn, "/game/boss");
@@ -1911,12 +2079,11 @@ var GameResource = class {
1911
2079
  */
1912
2080
  subscribePlayer(realtime, userId) {
1913
2081
  const fetchFn = async () => {
1914
- const res = await this.player({ userId });
2082
+ const player = await this.player({ userId });
1915
2083
  return {
1916
- success: true,
1917
- data: [res.data],
2084
+ data: [player],
1918
2085
  pagination: { total: 1, page: 1, pageSize: 1, hasMore: false },
1919
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2086
+ hasMore: false
1920
2087
  };
1921
2088
  };
1922
2089
  return new Subscription(realtime, fetchFn, "/game/player");
@@ -1927,14 +2094,12 @@ var GameResource = class {
1927
2094
  */
1928
2095
  subscribeLeaderboard(realtime, params) {
1929
2096
  const fetchFn = async () => {
1930
- const res = await this.leaderboard(params);
1931
- const envelope = res.data;
1932
- const entries = Array.isArray(envelope) ? envelope : envelope?.entries ?? [];
2097
+ const board = await this.leaderboard(params);
2098
+ const entries = board.entries ?? [];
1933
2099
  return {
1934
- success: true,
1935
2100
  data: entries,
1936
2101
  pagination: { total: entries.length, page: 1, pageSize: 100, hasMore: false },
1937
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2102
+ hasMore: false
1938
2103
  };
1939
2104
  };
1940
2105
  return new Subscription(realtime, fetchFn, "/game/leaderboard");
@@ -1945,13 +2110,12 @@ var GameResource = class {
1945
2110
  */
1946
2111
  subscribeActivity(realtime) {
1947
2112
  const fetchFn = async () => {
1948
- const res = await this.activity.list({ limit: 50 });
1949
- const data = res.data.data || [];
2113
+ const feed = await this.activity.list({ limit: 50 });
2114
+ const items = feed.data ?? [];
1950
2115
  return {
1951
- success: true,
1952
- data,
1953
- pagination: { total: data.length, page: 1, pageSize: 50, hasMore: false },
1954
- meta: { requestId: "", timestamp: (/* @__PURE__ */ new Date()).toISOString() }
2116
+ data: items,
2117
+ pagination: { total: items.length, page: 1, pageSize: 50, hasMore: false },
2118
+ hasMore: false
1955
2119
  };
1956
2120
  };
1957
2121
  return new Subscription(realtime, fetchFn, "/game/activity");
@@ -2189,12 +2353,42 @@ var CStarClient = class {
2189
2353
  get isTestMode() {
2190
2354
  return this._httpClient.environment === "test";
2191
2355
  }
2356
+ /**
2357
+ * Validate the configured API key and return the team + key metadata it
2358
+ * resolves to. Useful for confirming the key is wired up correctly during
2359
+ * onboarding (and for AI assistants that want a one-call sanity check).
2360
+ *
2361
+ * Closes F-018.
2362
+ *
2363
+ * ```ts
2364
+ * const me = await cstar.whoami();
2365
+ * console.log(me.teamSlug, me.environment); // 'acme', 'test'
2366
+ * ```
2367
+ */
2368
+ async whoami() {
2369
+ return this._httpClient.requestUnscoped("GET", "/auth/whoami");
2370
+ }
2371
+ /**
2372
+ * Side-channel metadata from the most recent HTTP response. Holds the
2373
+ * server `requestId` (for support escalations), `timestamp`, and the
2374
+ * `pagination` info from list endpoints. Reset on every call — read it
2375
+ * immediately after awaiting the call you care about.
2376
+ *
2377
+ * ```ts
2378
+ * const ticket = await cstar.tickets.create({...});
2379
+ * console.error('Created', ticket.id, 'request:', cstar.lastMeta.requestId);
2380
+ * ```
2381
+ */
2382
+ get lastMeta() {
2383
+ return this._httpClient.lastMeta;
2384
+ }
2192
2385
  };
2193
2386
  export {
2194
2387
  CStarAuthenticationError,
2195
2388
  CStarClient,
2196
2389
  CStarConflictError,
2197
2390
  CStarError,
2391
+ CStarFeatureDisabledError,
2198
2392
  CStarNotFoundError,
2199
2393
  CStarPermissionError,
2200
2394
  CStarRateLimitError,