cwe-api-client 0.0.1 → 1.0.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/dist/index.js ADDED
@@ -0,0 +1,359 @@
1
+ // src/errors/CweApiError.ts
2
+ var CweApiError = class extends Error {
3
+ constructor(status, statusText) {
4
+ super(`CWE API error: ${status} ${statusText}`);
5
+ this.name = "CweApiError";
6
+ this.status = status;
7
+ this.statusText = statusText;
8
+ }
9
+ };
10
+
11
+ // src/resources/WeaknessResource.ts
12
+ var WeaknessResource = class {
13
+ /** @internal */
14
+ constructor(request, id) {
15
+ this.request = request;
16
+ this.id = id;
17
+ }
18
+ /**
19
+ * Allows the resource to be awaited directly, resolving with the full weakness.
20
+ * Delegates to {@link WeaknessResource.get}.
21
+ */
22
+ then(onfulfilled, onrejected) {
23
+ return this.get().then(onfulfilled, onrejected);
24
+ }
25
+ /**
26
+ * Fetches the full weakness entry.
27
+ *
28
+ * `GET /cwe/weakness/{id}`
29
+ *
30
+ * @returns The weakness object
31
+ */
32
+ async get() {
33
+ const data = await this.request(`/cwe/weakness/${this.id}`);
34
+ return data.Weaknesses[0];
35
+ }
36
+ /**
37
+ * Fetches the direct parents of this weakness in a given view.
38
+ *
39
+ * `GET /cwe/{id}/parents?view={viewId}`
40
+ *
41
+ * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)
42
+ * @returns Array of direct parent entries
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const parents = await cwe.weakness(74).parents(1000);
47
+ * ```
48
+ */
49
+ async parents(viewId) {
50
+ return this.request(
51
+ `/cwe/${this.id}/parents`,
52
+ viewId !== void 0 ? { view: viewId } : void 0
53
+ );
54
+ }
55
+ /**
56
+ * Fetches the direct children of this weakness in a given view.
57
+ *
58
+ * `GET /cwe/{id}/children?view={viewId}`
59
+ *
60
+ * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)
61
+ * @returns Array of direct child entries
62
+ *
63
+ * @example
64
+ * ```typescript
65
+ * const children = await cwe.weakness(74).children(1000);
66
+ * ```
67
+ */
68
+ async children(viewId) {
69
+ return this.request(
70
+ `/cwe/${this.id}/children`,
71
+ viewId !== void 0 ? { view: viewId } : void 0
72
+ );
73
+ }
74
+ /**
75
+ * Fetches the full ancestor tree of this weakness in a given view.
76
+ *
77
+ * `GET /cwe/{id}/ancestors?view={viewId}`
78
+ *
79
+ * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)
80
+ * @returns Recursive ancestor tree from this node up to the view root
81
+ *
82
+ * @example
83
+ * ```typescript
84
+ * const tree = await cwe.weakness(74).ancestors(1000);
85
+ * ```
86
+ */
87
+ async ancestors(viewId) {
88
+ return this.request(
89
+ `/cwe/${this.id}/ancestors`,
90
+ viewId !== void 0 ? { view: viewId } : void 0
91
+ );
92
+ }
93
+ /**
94
+ * Fetches the full descendant tree of this weakness in a given view.
95
+ *
96
+ * `GET /cwe/{id}/descendants?view={viewId}`
97
+ *
98
+ * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)
99
+ * @returns Recursive descendant tree from this node down to leaf entries
100
+ *
101
+ * @example
102
+ * ```typescript
103
+ * const tree = await cwe.weakness(74).descendants(1000);
104
+ * ```
105
+ */
106
+ async descendants(viewId) {
107
+ return this.request(
108
+ `/cwe/${this.id}/descendants`,
109
+ viewId !== void 0 ? { view: viewId } : void 0
110
+ );
111
+ }
112
+ };
113
+
114
+ // src/resources/CategoryResource.ts
115
+ var CategoryResource = class {
116
+ /** @internal */
117
+ constructor(request, id) {
118
+ this.request = request;
119
+ this.id = id;
120
+ }
121
+ /**
122
+ * Allows the resource to be awaited directly, resolving with the full category.
123
+ * Delegates to {@link CategoryResource.get}.
124
+ */
125
+ then(onfulfilled, onrejected) {
126
+ return this.get().then(onfulfilled, onrejected);
127
+ }
128
+ /**
129
+ * Fetches the full category entry.
130
+ *
131
+ * `GET /cwe/category/{id}`
132
+ *
133
+ * @returns The category object
134
+ */
135
+ async get() {
136
+ const data = await this.request(`/cwe/category/${this.id}`);
137
+ return data.Categories[0];
138
+ }
139
+ };
140
+
141
+ // src/resources/ViewResource.ts
142
+ var ViewResource = class {
143
+ /** @internal */
144
+ constructor(request, id) {
145
+ this.request = request;
146
+ this.id = id;
147
+ }
148
+ /**
149
+ * Allows the resource to be awaited directly, resolving with the full view.
150
+ * Delegates to {@link ViewResource.get}.
151
+ */
152
+ then(onfulfilled, onrejected) {
153
+ return this.get().then(onfulfilled, onrejected);
154
+ }
155
+ /**
156
+ * Fetches the full view entry.
157
+ *
158
+ * `GET /cwe/view/{id}`
159
+ *
160
+ * @returns The view object
161
+ */
162
+ async get() {
163
+ const data = await this.request(`/cwe/view/${this.id}`);
164
+ return data.Views[0];
165
+ }
166
+ };
167
+
168
+ // src/CweClient.ts
169
+ var DEFAULT_BASE_URL = "https://cwe-api.mitre.org/api/v1";
170
+ var CweClient = class {
171
+ /**
172
+ * @param options - Optional configuration for the API base URL
173
+ */
174
+ constructor(options = {}) {
175
+ this.listeners = /* @__PURE__ */ new Map();
176
+ this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\/$/, "");
177
+ }
178
+ /**
179
+ * Subscribes to a client event.
180
+ *
181
+ * @example
182
+ * ```typescript
183
+ * cwe.on('request', (event) => {
184
+ * console.log(`${event.method} ${event.url} — ${event.durationMs}ms`);
185
+ * if (event.error) console.error('Request failed:', event.error);
186
+ * });
187
+ * ```
188
+ */
189
+ on(event, callback) {
190
+ const callbacks = this.listeners.get(event) ?? [];
191
+ callbacks.push(callback);
192
+ this.listeners.set(event, callbacks);
193
+ return this;
194
+ }
195
+ emit(event, payload) {
196
+ const callbacks = this.listeners.get(event) ?? [];
197
+ for (const cb of callbacks) {
198
+ cb(payload);
199
+ }
200
+ }
201
+ /**
202
+ * Performs a GET request to the CWE API.
203
+ *
204
+ * @param path - Path to append to the base URL (e.g. `/cwe/version`)
205
+ * @param params - Optional query parameters
206
+ * @internal
207
+ */
208
+ async request(path, params) {
209
+ const url = buildUrl(`${this.baseUrl}${path}`, params);
210
+ const startedAt = /* @__PURE__ */ new Date();
211
+ let statusCode;
212
+ try {
213
+ const response = await fetch(url, {
214
+ headers: { Accept: "application/json" }
215
+ });
216
+ statusCode = response.status;
217
+ if (!response.ok) {
218
+ throw new CweApiError(response.status, response.statusText);
219
+ }
220
+ const data = await response.json();
221
+ this.emit("request", {
222
+ url,
223
+ method: "GET",
224
+ startedAt,
225
+ finishedAt: /* @__PURE__ */ new Date(),
226
+ durationMs: Date.now() - startedAt.getTime(),
227
+ statusCode
228
+ });
229
+ return data;
230
+ } catch (err) {
231
+ const finishedAt = /* @__PURE__ */ new Date();
232
+ this.emit("request", {
233
+ url,
234
+ method: "GET",
235
+ startedAt,
236
+ finishedAt,
237
+ durationMs: finishedAt.getTime() - startedAt.getTime(),
238
+ statusCode,
239
+ error: err instanceof Error ? err : new Error(String(err))
240
+ });
241
+ throw err;
242
+ }
243
+ }
244
+ /**
245
+ * Fetches CWE content version metadata.
246
+ *
247
+ * `GET /cwe/version`
248
+ *
249
+ * @returns Version metadata including content version, date, and counts
250
+ *
251
+ * @example
252
+ * ```typescript
253
+ * const v = await cwe.version();
254
+ * console.log(v.ContentVersion); // '4.19.1'
255
+ * console.log(v.TotalWeaknesses); // 969
256
+ * ```
257
+ */
258
+ async version() {
259
+ return this.request("/cwe/version");
260
+ }
261
+ /**
262
+ * Looks up multiple CWE entries by their numeric IDs in a single request.
263
+ *
264
+ * `GET /cwe/{ids}` (comma-separated)
265
+ *
266
+ * @param ids - Array of CWE numeric IDs (e.g. `[74, 79]`)
267
+ * @returns Array of lightweight CWE entries with type and ID
268
+ *
269
+ * @example
270
+ * ```typescript
271
+ * const entries = await cwe.lookup([74, 79]);
272
+ * entries.forEach(e => console.log(e.ID, e.Type));
273
+ * ```
274
+ */
275
+ async lookup(ids) {
276
+ return this.request(`/cwe/${ids.join(",")}`);
277
+ }
278
+ /**
279
+ * Returns a {@link WeaknessResource} for a given CWE weakness ID, providing
280
+ * access to weakness details and its hierarchy (parents, children, ancestors,
281
+ * descendants).
282
+ *
283
+ * The returned resource can be awaited directly to fetch the full weakness,
284
+ * or chained to access hierarchy methods.
285
+ *
286
+ * @param id - The CWE weakness numeric ID (e.g. `79`)
287
+ * @returns A chainable weakness resource
288
+ *
289
+ * @example
290
+ * ```typescript
291
+ * const weakness = await cwe.weakness(79);
292
+ * const parents = await cwe.weakness(74).parents(1000);
293
+ * const tree = await cwe.weakness(74).descendants(1000);
294
+ * ```
295
+ */
296
+ weakness(id) {
297
+ return new WeaknessResource(
298
+ (path, params) => this.request(path, params),
299
+ id
300
+ );
301
+ }
302
+ /**
303
+ * Returns a {@link CategoryResource} for a given CWE category ID, providing
304
+ * access to category details.
305
+ *
306
+ * The returned resource can be awaited directly to fetch the full category.
307
+ *
308
+ * @param id - The CWE category numeric ID (e.g. `189`)
309
+ * @returns A chainable category resource
310
+ *
311
+ * @example
312
+ * ```typescript
313
+ * const category = await cwe.category(189);
314
+ * console.log(category.Name); // 'Numeric Errors'
315
+ * ```
316
+ */
317
+ category(id) {
318
+ return new CategoryResource(
319
+ (path, params) => this.request(path, params),
320
+ id
321
+ );
322
+ }
323
+ /**
324
+ * Returns a {@link ViewResource} for a given CWE view ID, providing access
325
+ * to view details.
326
+ *
327
+ * The returned resource can be awaited directly to fetch the full view.
328
+ *
329
+ * @param id - The CWE view numeric ID (e.g. `1425`)
330
+ * @returns A chainable view resource
331
+ *
332
+ * @example
333
+ * ```typescript
334
+ * const view = await cwe.view(1425);
335
+ * console.log(view.Name); // 'Weaknesses in the 2023 CWE Top 25...'
336
+ * ```
337
+ */
338
+ view(id) {
339
+ return new ViewResource(
340
+ (path, params) => this.request(path, params),
341
+ id
342
+ );
343
+ }
344
+ };
345
+ function buildUrl(base, params) {
346
+ if (!params) return base;
347
+ const entries = Object.entries(params).filter(([, v]) => v !== void 0);
348
+ if (entries.length === 0) return base;
349
+ const search = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));
350
+ return `${base}?${search.toString()}`;
351
+ }
352
+ export {
353
+ CategoryResource,
354
+ CweApiError,
355
+ CweClient,
356
+ ViewResource,
357
+ WeaknessResource
358
+ };
359
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/CweApiError.ts","../src/resources/WeaknessResource.ts","../src/resources/CategoryResource.ts","../src/resources/ViewResource.ts","../src/CweClient.ts"],"sourcesContent":["/**\n * Thrown when the CWE API returns a non-2xx response.\n *\n * @example\n * ```typescript\n * import { CweApiError } from 'cwe-api-client';\n *\n * try {\n * await cwe.weakness(99999).get();\n * } catch (err) {\n * if (err instanceof CweApiError) {\n * console.log(err.status); // 404\n * console.log(err.statusText); // 'Not Found'\n * console.log(err.message); // 'CWE API error: 404 Not Found'\n * }\n * }\n * ```\n */\nexport class CweApiError extends Error {\n /** HTTP status code (e.g. `404`, `400`, `500`) */\n readonly status: number;\n /** HTTP status text (e.g. `'Not Found'`, `'Bad Request'`) */\n readonly statusText: string;\n\n constructor(status: number, statusText: string) {\n super(`CWE API error: ${status} ${statusText}`);\n this.name = 'CweApiError';\n this.status = status;\n this.statusText = statusText;\n }\n}\n","import type { CweWeakness } from '../domain/Weakness';\nimport type { CweRelationEntry, CweAncestorNode, CweDescendantNode } from '../domain/Relations';\n\n/** @internal */\nexport type RequestFn = <T>(\n path: string,\n params?: Record<string, string | number | boolean>,\n) => Promise<T>;\n\n/**\n * Represents a CWE weakness resource, providing access to weakness details\n * and its position in the CWE hierarchy.\n *\n * Implements `PromiseLike<CweWeakness>` so it can be awaited directly to\n * fetch the full weakness, while also exposing hierarchy methods.\n *\n * @example\n * ```typescript\n * // Await directly to get full weakness data\n * const weakness = await cwe.weakness(79);\n *\n * // Or call .get() explicitly\n * const weakness = await cwe.weakness(79).get();\n *\n * // Navigate the hierarchy\n * const parents = await cwe.weakness(74).parents(1000);\n * const children = await cwe.weakness(74).children(1000);\n * const ancestors = await cwe.weakness(74).ancestors(1000);\n * const descendants = await cwe.weakness(74).descendants(1000);\n * ```\n */\nexport class WeaknessResource implements PromiseLike<CweWeakness> {\n /** @internal */\n constructor(\n private readonly request: RequestFn,\n private readonly id: number,\n ) {}\n\n /**\n * Allows the resource to be awaited directly, resolving with the full weakness.\n * Delegates to {@link WeaknessResource.get}.\n */\n then<TResult1 = CweWeakness, TResult2 = never>(\n onfulfilled?: ((value: CweWeakness) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): PromiseLike<TResult1 | TResult2> {\n return this.get().then(onfulfilled, onrejected);\n }\n\n /**\n * Fetches the full weakness entry.\n *\n * `GET /cwe/weakness/{id}`\n *\n * @returns The weakness object\n */\n async get(): Promise<CweWeakness> {\n const data = await this.request<{ Weaknesses: CweWeakness[] }>(`/cwe/weakness/${this.id}`);\n return data.Weaknesses[0];\n }\n\n /**\n * Fetches the direct parents of this weakness in a given view.\n *\n * `GET /cwe/{id}/parents?view={viewId}`\n *\n * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)\n * @returns Array of direct parent entries\n *\n * @example\n * ```typescript\n * const parents = await cwe.weakness(74).parents(1000);\n * ```\n */\n async parents(viewId?: number): Promise<CweRelationEntry[]> {\n return this.request<CweRelationEntry[]>(\n `/cwe/${this.id}/parents`,\n viewId !== undefined ? { view: viewId } : undefined,\n );\n }\n\n /**\n * Fetches the direct children of this weakness in a given view.\n *\n * `GET /cwe/{id}/children?view={viewId}`\n *\n * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)\n * @returns Array of direct child entries\n *\n * @example\n * ```typescript\n * const children = await cwe.weakness(74).children(1000);\n * ```\n */\n async children(viewId?: number): Promise<CweRelationEntry[]> {\n return this.request<CweRelationEntry[]>(\n `/cwe/${this.id}/children`,\n viewId !== undefined ? { view: viewId } : undefined,\n );\n }\n\n /**\n * Fetches the full ancestor tree of this weakness in a given view.\n *\n * `GET /cwe/{id}/ancestors?view={viewId}`\n *\n * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)\n * @returns Recursive ancestor tree from this node up to the view root\n *\n * @example\n * ```typescript\n * const tree = await cwe.weakness(74).ancestors(1000);\n * ```\n */\n async ancestors(viewId?: number): Promise<CweAncestorNode[]> {\n return this.request<CweAncestorNode[]>(\n `/cwe/${this.id}/ancestors`,\n viewId !== undefined ? { view: viewId } : undefined,\n );\n }\n\n /**\n * Fetches the full descendant tree of this weakness in a given view.\n *\n * `GET /cwe/{id}/descendants?view={viewId}`\n *\n * @param viewId - CWE view ID to scope the hierarchy (e.g. `1000`)\n * @returns Recursive descendant tree from this node down to leaf entries\n *\n * @example\n * ```typescript\n * const tree = await cwe.weakness(74).descendants(1000);\n * ```\n */\n async descendants(viewId?: number): Promise<CweDescendantNode[]> {\n return this.request<CweDescendantNode[]>(\n `/cwe/${this.id}/descendants`,\n viewId !== undefined ? { view: viewId } : undefined,\n );\n }\n}\n","import type { CweCategory } from '../domain/Category';\nimport type { RequestFn } from './WeaknessResource';\n\n/**\n * Represents a CWE category resource, providing access to category details.\n *\n * Implements `PromiseLike<CweCategory>` so it can be awaited directly.\n *\n * @example\n * ```typescript\n * // Await directly to get full category data\n * const category = await cwe.category(189);\n *\n * // Or call .get() explicitly\n * const category = await cwe.category(189).get();\n * ```\n */\nexport class CategoryResource implements PromiseLike<CweCategory> {\n /** @internal */\n constructor(\n private readonly request: RequestFn,\n private readonly id: number,\n ) {}\n\n /**\n * Allows the resource to be awaited directly, resolving with the full category.\n * Delegates to {@link CategoryResource.get}.\n */\n then<TResult1 = CweCategory, TResult2 = never>(\n onfulfilled?: ((value: CweCategory) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): PromiseLike<TResult1 | TResult2> {\n return this.get().then(onfulfilled, onrejected);\n }\n\n /**\n * Fetches the full category entry.\n *\n * `GET /cwe/category/{id}`\n *\n * @returns The category object\n */\n async get(): Promise<CweCategory> {\n const data = await this.request<{ Categories: CweCategory[] }>(`/cwe/category/${this.id}`);\n return data.Categories[0];\n }\n}\n","import type { CweView } from '../domain/View';\nimport type { RequestFn } from './WeaknessResource';\n\n/**\n * Represents a CWE view resource, providing access to view details.\n *\n * Implements `PromiseLike<CweView>` so it can be awaited directly.\n *\n * @example\n * ```typescript\n * // Await directly to get full view data\n * const view = await cwe.view(1425);\n *\n * // Or call .get() explicitly\n * const view = await cwe.view(1425).get();\n * ```\n */\nexport class ViewResource implements PromiseLike<CweView> {\n /** @internal */\n constructor(\n private readonly request: RequestFn,\n private readonly id: number,\n ) {}\n\n /**\n * Allows the resource to be awaited directly, resolving with the full view.\n * Delegates to {@link ViewResource.get}.\n */\n then<TResult1 = CweView, TResult2 = never>(\n onfulfilled?: ((value: CweView) => TResult1 | PromiseLike<TResult1>) | null,\n onrejected?: ((reason: unknown) => TResult2 | PromiseLike<TResult2>) | null,\n ): PromiseLike<TResult1 | TResult2> {\n return this.get().then(onfulfilled, onrejected);\n }\n\n /**\n * Fetches the full view entry.\n *\n * `GET /cwe/view/{id}`\n *\n * @returns The view object\n */\n async get(): Promise<CweView> {\n const data = await this.request<{ Views: CweView[] }>(`/cwe/view/${this.id}`);\n return data.Views[0];\n }\n}\n","import { CweApiError } from './errors/CweApiError';\nimport { WeaknessResource } from './resources/WeaknessResource';\nimport { CategoryResource } from './resources/CategoryResource';\nimport { ViewResource } from './resources/ViewResource';\nimport type { CweVersion } from './domain/Version';\nimport type { CweEntry } from './domain/CweEntry';\n\nconst DEFAULT_BASE_URL = 'https://cwe-api.mitre.org/api/v1';\n\n/**\n * Payload emitted on every HTTP request made by {@link CweClient}.\n */\nexport interface RequestEvent {\n /** Full URL that was requested */\n url: string;\n /** HTTP method used */\n method: 'GET';\n /** Timestamp when the request started */\n startedAt: Date;\n /** Timestamp when the request finished (success or error) */\n finishedAt: Date;\n /** Total duration in milliseconds */\n durationMs: number;\n /** HTTP status code returned by the server, if a response was received */\n statusCode?: number;\n /** Error thrown, if the request failed */\n error?: Error;\n}\n\n/** Map of supported client events to their callback signatures */\nexport interface CweClientEvents {\n request: (event: RequestEvent) => void;\n}\n\n/**\n * Constructor options for {@link CweClient}.\n */\nexport interface CweClientOptions {\n /**\n * Base URL for the CWE API (default: `'https://cwe-api.mitre.org/api/v1'`).\n * Override for mirrors or local instances.\n */\n baseUrl?: string;\n}\n\n/**\n * Main entry point for the MITRE CWE API client.\n *\n * @example\n * ```typescript\n * import { CweClient } from 'cwe-api-client';\n *\n * const cwe = new CweClient();\n *\n * // Get content version metadata\n * const version = await cwe.version();\n *\n * // Look up multiple CWEs at once\n * const entries = await cwe.lookup([74, 79]);\n *\n * // Get full weakness details\n * const weakness = await cwe.weakness(79);\n *\n * // Navigate the hierarchy\n * const parents = await cwe.weakness(74).parents(1000);\n * const descendants = await cwe.weakness(74).descendants(1000);\n *\n * // Get category and view details\n * const category = await cwe.category(189);\n * const view = await cwe.view(1425);\n * ```\n */\nexport class CweClient {\n private readonly baseUrl: string;\n private readonly listeners: Map<\n keyof CweClientEvents,\n CweClientEvents[keyof CweClientEvents][]\n > = new Map();\n\n /**\n * @param options - Optional configuration for the API base URL\n */\n constructor(options: CweClientOptions = {}) {\n this.baseUrl = (options.baseUrl ?? DEFAULT_BASE_URL).replace(/\\/$/, '');\n }\n\n /**\n * Subscribes to a client event.\n *\n * @example\n * ```typescript\n * cwe.on('request', (event) => {\n * console.log(`${event.method} ${event.url} — ${event.durationMs}ms`);\n * if (event.error) console.error('Request failed:', event.error);\n * });\n * ```\n */\n on<K extends keyof CweClientEvents>(event: K, callback: CweClientEvents[K]): this {\n const callbacks = this.listeners.get(event) ?? [];\n callbacks.push(callback);\n this.listeners.set(event, callbacks);\n return this;\n }\n\n private emit<K extends keyof CweClientEvents>(\n event: K,\n payload: Parameters<CweClientEvents[K]>[0],\n ): void {\n const callbacks = this.listeners.get(event) ?? [];\n for (const cb of callbacks) {\n (cb as (p: typeof payload) => void)(payload);\n }\n }\n\n /**\n * Performs a GET request to the CWE API.\n *\n * @param path - Path to append to the base URL (e.g. `/cwe/version`)\n * @param params - Optional query parameters\n * @internal\n */\n async request<T>(\n path: string,\n params?: Record<string, string | number | boolean>,\n ): Promise<T> {\n const url = buildUrl(`${this.baseUrl}${path}`, params);\n const startedAt = new Date();\n let statusCode: number | undefined;\n try {\n const response = await fetch(url, {\n headers: { Accept: 'application/json' },\n });\n statusCode = response.status;\n if (!response.ok) {\n throw new CweApiError(response.status, response.statusText);\n }\n const data = await response.json() as T;\n this.emit('request', {\n url,\n method: 'GET',\n startedAt,\n finishedAt: new Date(),\n durationMs: Date.now() - startedAt.getTime(),\n statusCode,\n });\n return data;\n } catch (err) {\n const finishedAt = new Date();\n this.emit('request', {\n url,\n method: 'GET',\n startedAt,\n finishedAt,\n durationMs: finishedAt.getTime() - startedAt.getTime(),\n statusCode,\n error: err instanceof Error ? err : new Error(String(err)),\n });\n throw err;\n }\n }\n\n /**\n * Fetches CWE content version metadata.\n *\n * `GET /cwe/version`\n *\n * @returns Version metadata including content version, date, and counts\n *\n * @example\n * ```typescript\n * const v = await cwe.version();\n * console.log(v.ContentVersion); // '4.19.1'\n * console.log(v.TotalWeaknesses); // 969\n * ```\n */\n async version(): Promise<CweVersion> {\n return this.request<CweVersion>('/cwe/version');\n }\n\n /**\n * Looks up multiple CWE entries by their numeric IDs in a single request.\n *\n * `GET /cwe/{ids}` (comma-separated)\n *\n * @param ids - Array of CWE numeric IDs (e.g. `[74, 79]`)\n * @returns Array of lightweight CWE entries with type and ID\n *\n * @example\n * ```typescript\n * const entries = await cwe.lookup([74, 79]);\n * entries.forEach(e => console.log(e.ID, e.Type));\n * ```\n */\n async lookup(ids: number[]): Promise<CweEntry[]> {\n return this.request<CweEntry[]>(`/cwe/${ids.join(',')}`);\n }\n\n /**\n * Returns a {@link WeaknessResource} for a given CWE weakness ID, providing\n * access to weakness details and its hierarchy (parents, children, ancestors,\n * descendants).\n *\n * The returned resource can be awaited directly to fetch the full weakness,\n * or chained to access hierarchy methods.\n *\n * @param id - The CWE weakness numeric ID (e.g. `79`)\n * @returns A chainable weakness resource\n *\n * @example\n * ```typescript\n * const weakness = await cwe.weakness(79);\n * const parents = await cwe.weakness(74).parents(1000);\n * const tree = await cwe.weakness(74).descendants(1000);\n * ```\n */\n weakness(id: number): WeaknessResource {\n return new WeaknessResource(\n <T>(path: string, params?: Record<string, string | number | boolean>) =>\n this.request<T>(path, params),\n id,\n );\n }\n\n /**\n * Returns a {@link CategoryResource} for a given CWE category ID, providing\n * access to category details.\n *\n * The returned resource can be awaited directly to fetch the full category.\n *\n * @param id - The CWE category numeric ID (e.g. `189`)\n * @returns A chainable category resource\n *\n * @example\n * ```typescript\n * const category = await cwe.category(189);\n * console.log(category.Name); // 'Numeric Errors'\n * ```\n */\n category(id: number): CategoryResource {\n return new CategoryResource(\n <T>(path: string, params?: Record<string, string | number | boolean>) =>\n this.request<T>(path, params),\n id,\n );\n }\n\n /**\n * Returns a {@link ViewResource} for a given CWE view ID, providing access\n * to view details.\n *\n * The returned resource can be awaited directly to fetch the full view.\n *\n * @param id - The CWE view numeric ID (e.g. `1425`)\n * @returns A chainable view resource\n *\n * @example\n * ```typescript\n * const view = await cwe.view(1425);\n * console.log(view.Name); // 'Weaknesses in the 2023 CWE Top 25...'\n * ```\n */\n view(id: number): ViewResource {\n return new ViewResource(\n <T>(path: string, params?: Record<string, string | number | boolean>) =>\n this.request<T>(path, params),\n id,\n );\n }\n}\n\n/**\n * Appends query parameters to a URL string, skipping `undefined` values.\n * @internal\n */\nfunction buildUrl(base: string, params?: Record<string, string | number | boolean>): string {\n if (!params) return base;\n const entries = Object.entries(params).filter(([, v]) => v !== undefined);\n if (entries.length === 0) return base;\n const search = new URLSearchParams(entries.map(([k, v]) => [k, String(v)]));\n return `${base}?${search.toString()}`;\n}\n"],"mappings":"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","names":[]}
package/package.json CHANGED
@@ -1,8 +1,40 @@
1
1
  {
2
2
  "name": "cwe-api-client",
3
- "version": "0.0.1",
4
- "description": "",
5
- "homepage": "https://github.com/ElJijuna/cwe-api-client#readme",
3
+ "version": "1.0.1",
4
+ "type": "module",
5
+ "description": "TypeScript client for the MITRE CWE REST API",
6
+ "main": "dist/index.cjs",
7
+ "module": "dist/index.js",
8
+ "types": "dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js",
13
+ "require": "./dist/index.cjs"
14
+ }
15
+ },
16
+ "files": [
17
+ "dist"
18
+ ],
19
+ "scripts": {
20
+ "build": "tsup",
21
+ "docs": "typedoc",
22
+ "test": "jest",
23
+ "test:coverage": "jest --coverage",
24
+ "test:client": "npm run build && node test-client/test.js"
25
+ },
26
+ "keywords": [
27
+ "cwe",
28
+ "mitre",
29
+ "security",
30
+ "vulnerability",
31
+ "api-client",
32
+ "rest",
33
+ "typescript"
34
+ ],
35
+ "license": "MIT",
36
+ "author": "pilmee (pilmee@gmail.com)",
37
+ "homepage": "https://eljijuna.github.io/cwe-api-client",
6
38
  "bugs": {
7
39
  "url": "https://github.com/ElJijuna/cwe-api-client/issues"
8
40
  },
@@ -10,11 +42,16 @@
10
42
  "type": "git",
11
43
  "url": "git+https://github.com/ElJijuna/cwe-api-client.git"
12
44
  },
13
- "license": "MIT",
14
- "author": "pilmee (pilmee@gmail.com)",
15
- "type": "module",
16
- "main": "index.js",
17
- "scripts": {
18
- "test": "jest"
45
+ "devDependencies": {
46
+ "@semantic-release/changelog": "^6.0.3",
47
+ "@semantic-release/git": "^10.0.1",
48
+ "@types/jest": "^29.5.14",
49
+ "jest": "^29.7.0",
50
+ "semantic-release": "^24.2.3",
51
+ "ts-jest": "^29.3.0",
52
+ "ts-node": "^10.9.2",
53
+ "tsup": "^8.4.0",
54
+ "typedoc": "^0.27.9",
55
+ "typescript": "^5.8.3"
19
56
  }
20
57
  }