@noctaly/sdk 1.0.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.cjs ADDED
@@ -0,0 +1,370 @@
1
+ 'use strict';
2
+
3
+ // src/errors.ts
4
+ var NoctalyError = class extends Error {
5
+ /**
6
+ * Machine-readable error code.
7
+ */
8
+ code;
9
+ /**
10
+ * HTTP status code.
11
+ */
12
+ status;
13
+ /**
14
+ * Seconds to wait before retrying. Only present when `code` is
15
+ * `"rate_limited"`.
16
+ */
17
+ retryAfter;
18
+ /**
19
+ * `true` when the rate limit that was exceeded is the global one.
20
+ * Only present when `code` is `"rate_limited"`.
21
+ */
22
+ global;
23
+ /**
24
+ * Zod validation issues. Only present when `code` is
25
+ * `"validation_error"`.
26
+ */
27
+ details;
28
+ constructor(opts) {
29
+ super(opts.message);
30
+ this.name = "NoctalyError";
31
+ this.code = opts.code;
32
+ this.status = opts.status;
33
+ if (opts.retryAfter !== void 0) this.retryAfter = opts.retryAfter;
34
+ if (opts.global !== void 0) this.global = opts.global;
35
+ if (opts.details !== void 0) this.details = opts.details;
36
+ }
37
+ };
38
+
39
+ // src/request.ts
40
+ var MAX_RETRY_DELAY_MS = 6e4;
41
+ async function sleep(ms) {
42
+ return new Promise((resolve) => {
43
+ setTimeout(resolve, ms);
44
+ });
45
+ }
46
+ function parseRateLimitHeaders(headers) {
47
+ const limit = headers.get("X-RateLimit-Limit");
48
+ const remaining = headers.get("X-RateLimit-Remaining");
49
+ const reset = headers.get("X-RateLimit-Reset");
50
+ const resetAfter = headers.get("X-RateLimit-Reset-After");
51
+ if (!limit || !remaining || !reset || !resetAfter) return void 0;
52
+ return {
53
+ limit: Number(limit),
54
+ remaining: Number(remaining),
55
+ reset: Number(reset),
56
+ resetAfter: Number(resetAfter)
57
+ };
58
+ }
59
+ async function executeRequest(url, opts, config) {
60
+ const signal = config.timeout > 0 ? AbortSignal.timeout(config.timeout) : void 0;
61
+ return fetch(url, {
62
+ method: opts.method ?? "GET",
63
+ headers: {
64
+ Authorization: config.apiKey,
65
+ "Content-Type": "application/json",
66
+ "User-Agent": "@noctaly/sdk"
67
+ },
68
+ signal,
69
+ ...opts.body === void 0 ? {} : { body: JSON.stringify(opts.body) }
70
+ });
71
+ }
72
+ async function request(path, opts, config, attempt = 0) {
73
+ let url = `${config.baseURL}${path}`;
74
+ if (opts.query) {
75
+ const params = new URLSearchParams();
76
+ for (const [key, value] of Object.entries(opts.query)) {
77
+ if (value !== void 0 && value !== false) {
78
+ params.set(key, String(value));
79
+ }
80
+ }
81
+ const qs = params.toString();
82
+ if (qs) url += `?${qs}`;
83
+ }
84
+ const res = await executeRequest(url, opts, config);
85
+ const json = await res.json();
86
+ if (!res.ok) {
87
+ const err = json;
88
+ if (res.status === 429 && config.retry && attempt < config.maxRetries) {
89
+ const delayMs = Math.min((err.retryAfter ?? 1) * 1e3, MAX_RETRY_DELAY_MS);
90
+ await sleep(delayMs);
91
+ return request(path, opts, config, attempt + 1);
92
+ }
93
+ throw new NoctalyError({
94
+ code: err.code,
95
+ message: err.message,
96
+ status: res.status,
97
+ ...err.retryAfter === void 0 ? {} : { retryAfter: err.retryAfter },
98
+ ...err.global === void 0 ? {} : { global: err.global },
99
+ ...err.details === void 0 ? {} : { details: err.details }
100
+ });
101
+ }
102
+ const rateLimit = parseRateLimitHeaders(res.headers);
103
+ const response = { data: json };
104
+ if (rateLimit !== void 0) response.rateLimit = rateLimit;
105
+ return response;
106
+ }
107
+
108
+ // src/resources/GuildChestsResource.ts
109
+ var GuildChestsResource = class {
110
+ #guildID;
111
+ #config;
112
+ constructor(guildID, config) {
113
+ this.#guildID = guildID;
114
+ this.#config = config;
115
+ }
116
+ /**
117
+ * Returns all chests configured in the guild's economy.
118
+ *
119
+ * `GET /guilds/{guildID}/chests`
120
+ */
121
+ async list() {
122
+ return request(`/guilds/${this.#guildID}/chests`, {}, this.#config);
123
+ }
124
+ };
125
+
126
+ // src/resources/GuildItemsResource.ts
127
+ var GuildItemsResource = class {
128
+ #guildID;
129
+ #config;
130
+ constructor(guildID, config) {
131
+ this.#guildID = guildID;
132
+ this.#config = config;
133
+ }
134
+ /**
135
+ * Returns all items configured in the guild's economy.
136
+ *
137
+ * `GET /guilds/{guildID}/items`
138
+ */
139
+ async list() {
140
+ return request(`/guilds/${this.#guildID}/items`, {}, this.#config);
141
+ }
142
+ };
143
+
144
+ // src/resources/UserChestsResource.ts
145
+ var UserChestsResource = class {
146
+ #guildID;
147
+ #userID;
148
+ #config;
149
+ constructor(guildID, userID, config) {
150
+ this.#guildID = guildID;
151
+ this.#userID = userID;
152
+ this.#config = config;
153
+ }
154
+ /**
155
+ * Adds a quantity of a chest to the member's inventory.
156
+ *
157
+ * `POST /guilds/{guildID}/users/{userID}/chests`
158
+ */
159
+ async add(body) {
160
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: "POST", body }, this.#config);
161
+ }
162
+ /**
163
+ * Removes a quantity of a chest from the member's inventory. If the
164
+ * resulting quantity reaches `0` or below the chest is removed entirely.
165
+ *
166
+ * `DELETE /guilds/{guildID}/users/{userID}/chests`
167
+ */
168
+ async remove(body) {
169
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: "DELETE", body }, this.#config);
170
+ }
171
+ /**
172
+ * Sets the exact quantity of a chest in the member's inventory.
173
+ * Passing `0` removes the chest entirely.
174
+ *
175
+ * `PUT /guilds/{guildID}/users/{userID}/chests`
176
+ */
177
+ async set(body) {
178
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: "PUT", body }, this.#config);
179
+ }
180
+ };
181
+
182
+ // src/resources/UserItemsResource.ts
183
+ var UserItemsResource = class {
184
+ #guildID;
185
+ #userID;
186
+ #config;
187
+ constructor(guildID, userID, config) {
188
+ this.#guildID = guildID;
189
+ this.#userID = userID;
190
+ this.#config = config;
191
+ }
192
+ /**
193
+ * Adds a quantity of an item to the member's inventory.
194
+ *
195
+ * `POST /guilds/{guildID}/users/{userID}/items`
196
+ */
197
+ async add(body) {
198
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: "POST", body }, this.#config);
199
+ }
200
+ /**
201
+ * Removes a quantity of an item from the member's inventory. If the
202
+ * resulting quantity reaches `0` or below the item is removed entirely.
203
+ *
204
+ * `DELETE /guilds/{guildID}/users/{userID}/items`
205
+ */
206
+ async remove(body) {
207
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: "DELETE", body }, this.#config);
208
+ }
209
+ /**
210
+ * Sets the exact quantity of an item in the member's inventory.
211
+ * Passing `0` removes the item entirely.
212
+ *
213
+ * `PUT /guilds/{guildID}/users/{userID}/items`
214
+ */
215
+ async set(body) {
216
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: "PUT", body }, this.#config);
217
+ }
218
+ /**
219
+ * Triggers the item's configured actions (give/remove money, XP, roles,
220
+ * other items…). Only works for items of type `CUSTOM` with at least one
221
+ * action. The item is consumed unless it has the `KEEP_AFTER_USE` flag.
222
+ *
223
+ * `POST /guilds/{guildID}/users/{userID}/items/{itemID}/use`
224
+ */
225
+ async use(itemID, body = {}) {
226
+ return request(
227
+ `/guilds/${this.#guildID}/users/${this.#userID}/items/${itemID}/use`,
228
+ { method: "POST", body },
229
+ this.#config
230
+ );
231
+ }
232
+ };
233
+
234
+ // src/resources/UserResource.ts
235
+ var UserResource = class {
236
+ /**
237
+ * Item inventory operations for this member.
238
+ */
239
+ items;
240
+ /**
241
+ * Chest inventory operations for this member.
242
+ */
243
+ chests;
244
+ #guildID;
245
+ #userID;
246
+ #config;
247
+ constructor(guildID, userID, config) {
248
+ this.#guildID = guildID;
249
+ this.#userID = userID;
250
+ this.#config = config;
251
+ this.items = new UserItemsResource(guildID, userID, config);
252
+ this.chests = new UserChestsResource(guildID, userID, config);
253
+ }
254
+ async getEcoProfile(options) {
255
+ return request(
256
+ `/guilds/${this.#guildID}/users/${this.#userID}/eco-profile`,
257
+ {
258
+ query: {
259
+ items: options?.items,
260
+ chests: options?.chests,
261
+ dailyStreak: options?.dailyStreak
262
+ }
263
+ },
264
+ this.#config
265
+ );
266
+ }
267
+ /**
268
+ * Returns the member's full levelling profile.
269
+ *
270
+ * `GET /guilds/{guildID}/users/{userID}/level-profile`
271
+ */
272
+ async getLevelProfile() {
273
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/level-profile`, {}, this.#config);
274
+ }
275
+ /**
276
+ * Adds or removes XP from the member.
277
+ *
278
+ * `POST /guilds/{guildID}/users/{userID}/xp`
279
+ */
280
+ async updateXP(body) {
281
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/xp`, { method: "POST", body }, this.#config);
282
+ }
283
+ /**
284
+ * Adds or removes levels from the member.
285
+ *
286
+ * `POST /guilds/{guildID}/users/{userID}/level`
287
+ */
288
+ async updateLevel(body) {
289
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/level`, { method: "POST", body }, this.#config);
290
+ }
291
+ /**
292
+ * Adds or removes money from the member.
293
+ *
294
+ * `POST /guilds/{guildID}/users/{userID}/money`
295
+ */
296
+ async updateMoney(body) {
297
+ return request(`/guilds/${this.#guildID}/users/${this.#userID}/money`, { method: "POST", body }, this.#config);
298
+ }
299
+ };
300
+
301
+ // src/resources/GuildResource.ts
302
+ var GuildResource = class {
303
+ #guildID;
304
+ #config;
305
+ /**
306
+ * Guild-wide item catalogue operations.
307
+ */
308
+ items;
309
+ /**
310
+ * Guild-wide chest catalogue operations.
311
+ */
312
+ chests;
313
+ constructor(guildID, config) {
314
+ this.#guildID = guildID;
315
+ this.#config = config;
316
+ this.items = new GuildItemsResource(guildID, config);
317
+ this.chests = new GuildChestsResource(guildID, config);
318
+ }
319
+ /**
320
+ * Returns a {@link UserResource} scoped to the given guild member.
321
+ *
322
+ * @param userID - Discord user ID of the member.
323
+ */
324
+ users(userID) {
325
+ return new UserResource(this.#guildID, userID, this.#config);
326
+ }
327
+ };
328
+
329
+ // src/client.ts
330
+ var DEFAULT_BASE_URL = "https://noctaly.com/api/v1";
331
+ var DEFAULT_TIMEOUT = 3e4;
332
+ var DEFAULT_MAX_RETRIES = 3;
333
+ var NoctalyClient = class {
334
+ #config;
335
+ constructor({
336
+ apiKey,
337
+ baseURL = DEFAULT_BASE_URL,
338
+ timeout = DEFAULT_TIMEOUT,
339
+ retry = true,
340
+ maxRetries = DEFAULT_MAX_RETRIES
341
+ }) {
342
+ if (!apiKey) throw new Error("apiKey is required.");
343
+ this.#config = {
344
+ apiKey,
345
+ baseURL: baseURL.replace(/\/$/, ""),
346
+ timeout,
347
+ retry,
348
+ maxRetries
349
+ };
350
+ }
351
+ /**
352
+ * Returns a {@link GuildResource} scoped to the given guild.
353
+ *
354
+ * @param guildID - Discord guild (server) ID.
355
+ */
356
+ guilds(guildID) {
357
+ return new GuildResource(guildID, this.#config);
358
+ }
359
+ };
360
+
361
+ exports.GuildChestsResource = GuildChestsResource;
362
+ exports.GuildItemsResource = GuildItemsResource;
363
+ exports.GuildResource = GuildResource;
364
+ exports.NoctalyClient = NoctalyClient;
365
+ exports.NoctalyError = NoctalyError;
366
+ exports.UserChestsResource = UserChestsResource;
367
+ exports.UserItemsResource = UserItemsResource;
368
+ exports.UserResource = UserResource;
369
+ //# sourceMappingURL=index.cjs.map
370
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors.ts","../src/request.ts","../src/resources/GuildChestsResource.ts","../src/resources/GuildItemsResource.ts","../src/resources/UserChestsResource.ts","../src/resources/UserItemsResource.ts","../src/resources/UserResource.ts","../src/resources/GuildResource.ts","../src/client.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * All possible error codes returned by the Noctaly API.\n */\nexport type ApiErrorCode =\n\t| \"module_disabled\"\n\t| \"not_found\"\n\t| \"rate_limited\"\n\t| \"unauthenticated\"\n\t| \"unauthorized\"\n\t| \"unexpected_error\"\n\t| \"validation_error\";\n\n/**\n * A single Zod validation issue surfaced by the API.\n */\nexport interface ValidationIssue {\n\t/**\n\t * Zod error code.\n\t */\n\tcode: string;\n\t/**\n\t * Human-readable error message.\n\t */\n\tmessage: string;\n\t/**\n\t * Path to the field that failed validation.\n\t */\n\tpath: (number | string)[];\n}\n\n/**\n * Thrown by all SDK methods on a non-2xx API response.\n */\nexport class NoctalyError extends Error {\n\t/**\n\t * Machine-readable error code.\n\t */\n\tpublic readonly code: ApiErrorCode;\n\n\t/**\n\t * HTTP status code.\n\t */\n\tpublic readonly status: number;\n\n\t/**\n\t * Seconds to wait before retrying. Only present when `code` is\n\t * `\"rate_limited\"`.\n\t */\n\tpublic readonly retryAfter?: number;\n\n\t/**\n\t * `true` when the rate limit that was exceeded is the global one.\n\t * Only present when `code` is `\"rate_limited\"`.\n\t */\n\tpublic readonly global?: boolean;\n\n\t/**\n\t * Zod validation issues. Only present when `code` is\n\t * `\"validation_error\"`.\n\t */\n\tpublic readonly details?: ValidationIssue[];\n\n\tpublic constructor(opts: {\n\t\tcode: ApiErrorCode;\n\t\tdetails?: ValidationIssue[];\n\t\tglobal?: boolean;\n\t\tmessage: string;\n\t\tretryAfter?: number;\n\t\tstatus: number;\n\t}) {\n\t\tsuper(opts.message);\n\t\tthis.name = \"NoctalyError\";\n\t\tthis.code = opts.code;\n\t\tthis.status = opts.status;\n\t\tif (opts.retryAfter !== undefined) this.retryAfter = opts.retryAfter;\n\t\tif (opts.global !== undefined) this.global = opts.global;\n\t\tif (opts.details !== undefined) this.details = opts.details;\n\t}\n}\n","import type { ClientConfig } from \"./config.js\";\nimport type { ApiErrorCode, ValidationIssue } from \"./errors.js\";\nimport { NoctalyError } from \"./errors.js\";\nimport type { RateLimitInfo } from \"./types.js\";\n\ninterface ErrorBody {\n\tcode: string;\n\tdetails?: ValidationIssue[];\n\tglobal?: boolean;\n\tmessage: string;\n\tretryAfter?: number;\n}\n\nexport interface RequestOptions {\n\tbody?: unknown;\n\tmethod?: \"DELETE\" | \"GET\" | \"POST\" | \"PUT\";\n\t/**\n\t * Query-string parameters. `false` and `undefined` values are omitted.\n\t */\n\tquery?: Record<string, boolean | string | undefined>;\n}\n\nexport interface NoctalyResponse<T> {\n\t/**\n\t * The parsed response data.\n\t */\n\tdata: T;\n\t/**\n\t * Rate-limit info from the response headers.\n\t * Present on all successful responses that include rate-limit headers.\n\t */\n\trateLimit?: RateLimitInfo;\n}\n\n/**\n * Maximum seconds to honour from a `retryAfter` field before capping.\n */\nconst MAX_RETRY_DELAY_MS = 60_000;\n\nasync function sleep(ms: number): Promise<void> {\n\treturn new Promise((resolve) => {\n\t\tsetTimeout(resolve, ms);\n\t});\n}\n\nfunction parseRateLimitHeaders(headers: Headers): RateLimitInfo | undefined {\n\tconst limit = headers.get(\"X-RateLimit-Limit\");\n\tconst remaining = headers.get(\"X-RateLimit-Remaining\");\n\tconst reset = headers.get(\"X-RateLimit-Reset\");\n\tconst resetAfter = headers.get(\"X-RateLimit-Reset-After\");\n\n\tif (!limit || !remaining || !reset || !resetAfter) return undefined;\n\n\treturn {\n\t\tlimit: Number(limit),\n\t\tremaining: Number(remaining),\n\t\treset: Number(reset),\n\t\tresetAfter: Number(resetAfter),\n\t};\n}\n\nasync function executeRequest(url: string, opts: RequestOptions, config: ClientConfig): Promise<Response> {\n\tconst signal = config.timeout > 0 ? AbortSignal.timeout(config.timeout) : undefined;\n\n\treturn fetch(url, {\n\t\tmethod: opts.method ?? \"GET\",\n\t\theaders: {\n\t\t\tAuthorization: config.apiKey,\n\t\t\t\"Content-Type\": \"application/json\",\n\t\t\t\"User-Agent\": \"@noctaly/sdk\",\n\t\t},\n\t\tsignal,\n\t\t...(opts.body === undefined ? {} : { body: JSON.stringify(opts.body) }),\n\t});\n}\n\nexport async function request<T>(\n\tpath: string,\n\topts: RequestOptions,\n\tconfig: ClientConfig,\n\tattempt = 0,\n): Promise<NoctalyResponse<T>> {\n\tlet url = `${config.baseURL}${path}`;\n\n\tif (opts.query) {\n\t\tconst params = new URLSearchParams();\n\t\tfor (const [key, value] of Object.entries(opts.query)) {\n\t\t\tif (value !== undefined && value !== false) {\n\t\t\t\tparams.set(key, String(value));\n\t\t\t}\n\t\t}\n\n\t\tconst qs = params.toString();\n\t\tif (qs) url += `?${qs}`;\n\t}\n\n\tconst res = await executeRequest(url, opts, config);\n\tconst json = (await res.json()) as unknown;\n\n\tif (!res.ok) {\n\t\tconst err = json as ErrorBody;\n\n\t\tif (res.status === 429 && config.retry && attempt < config.maxRetries) {\n\t\t\tconst delayMs = Math.min((err.retryAfter ?? 1) * 1_000, MAX_RETRY_DELAY_MS);\n\t\t\tawait sleep(delayMs);\n\t\t\treturn request<T>(path, opts, config, attempt + 1);\n\t\t}\n\n\t\tthrow new NoctalyError({\n\t\t\tcode: err.code as ApiErrorCode,\n\t\t\tmessage: err.message,\n\t\t\tstatus: res.status,\n\t\t\t...(err.retryAfter === undefined ? {} : { retryAfter: err.retryAfter }),\n\t\t\t...(err.global === undefined ? {} : { global: err.global }),\n\t\t\t...(err.details === undefined ? {} : { details: err.details }),\n\t\t});\n\t}\n\n\tconst rateLimit = parseRateLimitHeaders(res.headers);\n\tconst response: NoctalyResponse<T> = { data: json as T };\n\tif (rateLimit !== undefined) response.rateLimit = rateLimit;\n\treturn response;\n}\n","import type { ClientConfig } from \"../config.js\";\nimport type { NoctalyResponse } from \"../request.js\";\nimport { request } from \"../request.js\";\nimport type { Chest } from \"../types.js\";\n\n/**\n * Manages guild-level chest operations.\n */\nexport class GuildChestsResource {\n\treadonly #guildID: string;\n\n\treadonly #config: ClientConfig;\n\n\tpublic constructor(guildID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#config = config;\n\t}\n\n\t/**\n\t * Returns all chests configured in the guild's economy.\n\t *\n\t * `GET /guilds/{guildID}/chests`\n\t */\n\tpublic async list(): Promise<NoctalyResponse<{ chests: Chest[] }>> {\n\t\treturn request(`/guilds/${this.#guildID}/chests`, {}, this.#config);\n\t}\n}\n","import type { ClientConfig } from \"../config.js\";\nimport type { NoctalyResponse } from \"../request.js\";\nimport { request } from \"../request.js\";\nimport type { Item } from \"../types.js\";\n\n/**\n * Manages guild-level item operations.\n */\nexport class GuildItemsResource {\n\treadonly #guildID: string;\n\n\treadonly #config: ClientConfig;\n\n\tpublic constructor(guildID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#config = config;\n\t}\n\n\t/**\n\t * Returns all items configured in the guild's economy.\n\t *\n\t * `GET /guilds/{guildID}/items`\n\t */\n\tpublic async list(): Promise<NoctalyResponse<{ items: Item[] }>> {\n\t\treturn request(`/guilds/${this.#guildID}/items`, {}, this.#config);\n\t}\n}\n","import type { ClientConfig } from \"../config.js\";\nimport type { NoctalyResponse } from \"../request.js\";\nimport { request } from \"../request.js\";\n\nexport interface AddChestsBody {\n\t/**\n\t * UUID of the chest to add.\n\t */\n\tchestID: string;\n\t/**\n\t * Quantity to add. Min: `1`. Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\nexport interface RemoveChestsBody {\n\t/**\n\t * UUID of the chest to remove.\n\t */\n\tchestID: string;\n\t/**\n\t * Quantity to remove. Min: `1`. Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\nexport interface SetChestQuantityBody {\n\t/**\n\t * UUID of the chest.\n\t */\n\tchestID: string;\n\t/**\n\t * Target quantity. Min: `0` (removes the chest entirely). Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\n/**\n * Manages chest operations for a specific guild member.\n */\nexport class UserChestsResource {\n\treadonly #guildID: string;\n\n\treadonly #userID: string;\n\n\treadonly #config: ClientConfig;\n\n\tpublic constructor(guildID: string, userID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#userID = userID;\n\t\tthis.#config = config;\n\t}\n\n\t/**\n\t * Adds a quantity of a chest to the member's inventory.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/chests`\n\t */\n\tpublic async add(body: AddChestsBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: \"POST\", body }, this.#config);\n\t}\n\n\t/**\n\t * Removes a quantity of a chest from the member's inventory. If the\n\t * resulting quantity reaches `0` or below the chest is removed entirely.\n\t *\n\t * `DELETE /guilds/{guildID}/users/{userID}/chests`\n\t */\n\tpublic async remove(body: RemoveChestsBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: \"DELETE\", body }, this.#config);\n\t}\n\n\t/**\n\t * Sets the exact quantity of a chest in the member's inventory.\n\t * Passing `0` removes the chest entirely.\n\t *\n\t * `PUT /guilds/{guildID}/users/{userID}/chests`\n\t */\n\tpublic async set(body: SetChestQuantityBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/chests`, { method: \"PUT\", body }, this.#config);\n\t}\n}\n","import type { ClientConfig } from \"../config.js\";\nimport type { NoctalyResponse } from \"../request.js\";\nimport { request } from \"../request.js\";\nimport type { UseItemResult } from \"../types.js\";\n\nexport interface AddItemsBody {\n\t/**\n\t * UUID of the item to add.\n\t */\n\titemID: string;\n\t/**\n\t * Quantity to add. Min: `1`. Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\nexport interface RemoveItemsBody {\n\t/**\n\t * UUID of the item to remove.\n\t */\n\titemID: string;\n\t/**\n\t * Quantity to remove. Min: `1`. Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\nexport interface SetItemQuantityBody {\n\t/**\n\t * UUID of the item.\n\t */\n\titemID: string;\n\t/**\n\t * Target quantity. Min: `0` (removes the item entirely). Max: `1,000,000`.\n\t */\n\tquantity: number;\n}\n\nexport interface UseItemBody {\n\t/**\n\t * Number of uses. Min: `1`. Max: `1,000,000`. Defaults to `1`.\n\t */\n\tquantity?: number;\n}\n\n/**\n * Manages item operations for a specific guild member.\n */\nexport class UserItemsResource {\n\treadonly #guildID: string;\n\n\treadonly #userID: string;\n\n\treadonly #config: ClientConfig;\n\n\tpublic constructor(guildID: string, userID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#userID = userID;\n\t\tthis.#config = config;\n\t}\n\n\t/**\n\t * Adds a quantity of an item to the member's inventory.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/items`\n\t */\n\tpublic async add(body: AddItemsBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: \"POST\", body }, this.#config);\n\t}\n\n\t/**\n\t * Removes a quantity of an item from the member's inventory. If the\n\t * resulting quantity reaches `0` or below the item is removed entirely.\n\t *\n\t * `DELETE /guilds/{guildID}/users/{userID}/items`\n\t */\n\tpublic async remove(body: RemoveItemsBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: \"DELETE\", body }, this.#config);\n\t}\n\n\t/**\n\t * Sets the exact quantity of an item in the member's inventory.\n\t * Passing `0` removes the item entirely.\n\t *\n\t * `PUT /guilds/{guildID}/users/{userID}/items`\n\t */\n\tpublic async set(body: SetItemQuantityBody): Promise<NoctalyResponse<{ quantity: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/items`, { method: \"PUT\", body }, this.#config);\n\t}\n\n\t/**\n\t * Triggers the item's configured actions (give/remove money, XP, roles,\n\t * other items…). Only works for items of type `CUSTOM` with at least one\n\t * action. The item is consumed unless it has the `KEEP_AFTER_USE` flag.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/items/{itemID}/use`\n\t */\n\tpublic async use(itemID: string, body: UseItemBody = {}): Promise<NoctalyResponse<UseItemResult>> {\n\t\treturn request(\n\t\t\t`/guilds/${this.#guildID}/users/${this.#userID}/items/${itemID}/use`,\n\t\t\t{ method: \"POST\", body },\n\t\t\tthis.#config,\n\t\t);\n\t}\n}\n","import type { ClientConfig } from \"../config.js\";\nimport type { NoctalyResponse } from \"../request.js\";\nimport { request } from \"../request.js\";\nimport type { EcoProfileOptions, EcoProfileResult, LevelProfile } from \"../types.js\";\nimport { UserChestsResource } from \"./UserChestsResource.js\";\nimport { UserItemsResource } from \"./UserItemsResource.js\";\n\nexport interface UpdateXPBody {\n\t/**\n\t * When `true`, the server multiplier and the member's booster roles\n\t * affect the amount of XP.\n\t *\n\t * @default false\n\t */\n\tmultiply?: boolean;\n\t/**\n\t * XP to give (positive) or remove (negative).\n\t * Min: `-100,000,000`. Max: `100,000,000`.\n\t */\n\txp: number;\n}\n\nexport interface UpdateLevelBody {\n\t/**\n\t * Levels to give (positive) or remove (negative).\n\t * Min: `-1,000`. Max: `1,000`.\n\t */\n\tlevel: number;\n}\n\nexport interface UpdateMoneyBody {\n\t/**\n\t * Money to give (positive) or remove (negative).\n\t * Min: `-1,000,000,000`. Max: `1,000,000,000`.\n\t */\n\tmoney: number;\n\t/**\n\t * When `true`, the server multiplier and the member's booster roles\n\t * affect the amount of money.\n\t *\n\t * @default false\n\t */\n\tmultiply?: boolean;\n}\n\n/**\n * Operations scoped to a single guild member.\n */\nexport class UserResource {\n\t/**\n\t * Item inventory operations for this member.\n\t */\n\tpublic readonly items: UserItemsResource;\n\n\t/**\n\t * Chest inventory operations for this member.\n\t */\n\tpublic readonly chests: UserChestsResource;\n\n\treadonly #guildID: string;\n\n\treadonly #userID: string;\n\n\treadonly #config: ClientConfig;\n\n\tpublic constructor(guildID: string, userID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#userID = userID;\n\t\tthis.#config = config;\n\t\tthis.items = new UserItemsResource(guildID, userID, config);\n\t\tthis.chests = new UserChestsResource(guildID, userID, config);\n\t}\n\n\t/**\n\t * Returns the member's economy profile.\n\t * Pass options to include optional relations (items, chests, daily streak).\n\t *\n\t * `GET /guilds/{guildID}/users/{userID}/eco-profile`\n\t */\n\tpublic async getEcoProfile(): Promise<NoctalyResponse<EcoProfileResult<Record<never, never>>>>;\n\tpublic async getEcoProfile<O extends EcoProfileOptions>(options: O): Promise<NoctalyResponse<EcoProfileResult<O>>>;\n\tpublic async getEcoProfile<O extends EcoProfileOptions>(options?: O): Promise<NoctalyResponse<EcoProfileResult<O>>> {\n\t\treturn request(\n\t\t\t`/guilds/${this.#guildID}/users/${this.#userID}/eco-profile`,\n\t\t\t{\n\t\t\t\tquery: {\n\t\t\t\t\titems: options?.items,\n\t\t\t\t\tchests: options?.chests,\n\t\t\t\t\tdailyStreak: options?.dailyStreak,\n\t\t\t\t},\n\t\t\t},\n\t\t\tthis.#config,\n\t\t) as Promise<NoctalyResponse<EcoProfileResult<O>>>;\n\t}\n\n\t/**\n\t * Returns the member's full levelling profile.\n\t *\n\t * `GET /guilds/{guildID}/users/{userID}/level-profile`\n\t */\n\tpublic async getLevelProfile(): Promise<NoctalyResponse<LevelProfile>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/level-profile`, {}, this.#config);\n\t}\n\n\t/**\n\t * Adds or removes XP from the member.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/xp`\n\t */\n\tpublic async updateXP(body: UpdateXPBody): Promise<NoctalyResponse<{ xp: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/xp`, { method: \"POST\", body }, this.#config);\n\t}\n\n\t/**\n\t * Adds or removes levels from the member.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/level`\n\t */\n\tpublic async updateLevel(body: UpdateLevelBody): Promise<NoctalyResponse<{ level: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/level`, { method: \"POST\", body }, this.#config);\n\t}\n\n\t/**\n\t * Adds or removes money from the member.\n\t *\n\t * `POST /guilds/{guildID}/users/{userID}/money`\n\t */\n\tpublic async updateMoney(body: UpdateMoneyBody): Promise<NoctalyResponse<{ money: number }>> {\n\t\treturn request(`/guilds/${this.#guildID}/users/${this.#userID}/money`, { method: \"POST\", body }, this.#config);\n\t}\n}\n","import type { ClientConfig } from \"../config.js\";\nimport { GuildChestsResource } from \"./GuildChestsResource.js\";\nimport { GuildItemsResource } from \"./GuildItemsResource.js\";\nimport { UserResource } from \"./UserResource.js\";\n\n/**\n * Operations scoped to a single guild.\n */\nexport class GuildResource {\n\treadonly #guildID: string;\n\n\treadonly #config: ClientConfig;\n\n\t/**\n\t * Guild-wide item catalogue operations.\n\t */\n\tpublic readonly items: GuildItemsResource;\n\n\t/**\n\t * Guild-wide chest catalogue operations.\n\t */\n\tpublic readonly chests: GuildChestsResource;\n\n\tpublic constructor(guildID: string, config: ClientConfig) {\n\t\tthis.#guildID = guildID;\n\t\tthis.#config = config;\n\t\tthis.items = new GuildItemsResource(guildID, config);\n\t\tthis.chests = new GuildChestsResource(guildID, config);\n\t}\n\n\t/**\n\t * Returns a {@link UserResource} scoped to the given guild member.\n\t *\n\t * @param userID - Discord user ID of the member.\n\t */\n\tpublic users(userID: string): UserResource {\n\t\treturn new UserResource(this.#guildID, userID, this.#config);\n\t}\n}\n","import type { ClientConfig } from \"./config.js\";\nimport { GuildResource } from \"./resources/GuildResource.js\";\n\nconst DEFAULT_BASE_URL = \"https://noctaly.com/api/v1\";\nconst DEFAULT_TIMEOUT = 30_000;\nconst DEFAULT_MAX_RETRIES = 3;\n\nexport interface NoctalyClientOptions {\n\t/**\n\t * Your Noctaly API key.\n\t */\n\tapiKey: string;\n\t/**\n\t * Override the base URL. Useful for self-hosted or staging environments.\n\t *\n\t * @default \"https://noctaly.com/api/v1\"\n\t */\n\tbaseURL?: string;\n\t/**\n\t * Maximum number of automatic retries before the error is thrown.\n\t *\n\t * @default 3\n\t */\n\tmaxRetries?: number;\n\t/**\n\t * Automatically retry requests that receive a `rate_limited` (429) response,\n\t * waiting `retryAfter` seconds between attempts.\n\t *\n\t * @default true\n\t */\n\tretry?: boolean;\n\t/**\n\t * Request timeout in milliseconds. Set to `0` to disable.\n\t *\n\t * @default 30_000\n\t */\n\ttimeout?: number;\n}\n\n/**\n * Entry point for the Noctaly SDK.\n *\n * @example\n * ```ts\n * import { NoctalyClient } from \"@noctaly/sdk\";\n *\n * const noctaly = new NoctalyClient({ apiKey: \"your-api-key\" });\n *\n * const { data } = await noctaly.guilds(\"GUILD_ID\").users(\"USER_ID\").getLevelProfile();\n * console.log(`Level ${data.level} — ${data.xp} XP`);\n * ```\n */\nexport class NoctalyClient {\n\treadonly #config: ClientConfig;\n\n\tpublic constructor({\n\t\tapiKey,\n\t\tbaseURL = DEFAULT_BASE_URL,\n\t\ttimeout = DEFAULT_TIMEOUT,\n\t\tretry = true,\n\t\tmaxRetries = DEFAULT_MAX_RETRIES,\n\t}: NoctalyClientOptions) {\n\t\tif (!apiKey) throw new Error(\"apiKey is required.\");\n\t\tthis.#config = {\n\t\t\tapiKey,\n\t\t\tbaseURL: baseURL.replace(/\\/$/, \"\"),\n\t\t\ttimeout,\n\t\t\tretry,\n\t\t\tmaxRetries,\n\t\t};\n\t}\n\n\t/**\n\t * Returns a {@link GuildResource} scoped to the given guild.\n\t *\n\t * @param guildID - Discord guild (server) ID.\n\t */\n\tpublic guilds(guildID: string): GuildResource {\n\t\treturn new GuildResource(guildID, this.#config);\n\t}\n}\n"]}