newtqnia-node 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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 NewTqnia Offical account
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,121 @@
1
+ # NewTqnia Node.js SDK
2
+
3
+ [![CI](https://github.com/newtqnia/newtqnia-node/actions/workflows/ci.yml/badge.svg)](https://github.com/newtqnia/newtqnia-node/actions/workflows/ci.yml)
4
+ [![npm](https://img.shields.io/npm/v/newtqnia-node)](https://www.npmjs.com/package/newtqnia-node)
5
+ [![Node](https://img.shields.io/node/v/newtqnia-node)](package.json)
6
+ [![License](https://img.shields.io/badge/license-MIT-blue)](LICENSE)
7
+
8
+ The official, TypeScript-first SDK for the [NewTqnia technology news API](https://newtqnia.com/en/developers). It supports Node.js 18+, native Fetch, ESM and CommonJS, strong types, cancellation, timeouts, typed errors, and safe retries with no runtime dependencies.
9
+
10
+ ![Live NewTqnia Node.js SDK demo](docs/images/node-sdk-live-demo.png)
11
+
12
+ > When displaying API content, preserve returned article URLs and show the attribution supplied in the response, normally a visible [Powered by NewTqnia](https://newtqnia.com/en) link.
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install newtqnia-node
18
+ ```
19
+
20
+ ## Quick start
21
+
22
+ ```ts
23
+ import { NewTqniaClient } from "newtqnia-node";
24
+
25
+ const client = new NewTqniaClient({
26
+ apiKey: process.env.NEWTQNIA_API_KEY,
27
+ });
28
+
29
+ const digest = await client.news.latest({ locale: "en", limit: 5 });
30
+ for (const article of digest.articles) {
31
+ console.log(article.title, article.url);
32
+ }
33
+ ```
34
+
35
+ The public API currently exposes the two endpoints in NewTqnia's [OpenAPI document](https://newtqnia.com/openapi.yaml):
36
+
37
+ ```ts
38
+ await client.news.today({ locale: "ar", limit: 10 });
39
+ await client.news.latest({ category: "ai" });
40
+ ```
41
+
42
+ The API contract does not currently define free-text search, individual article lookup, category listing, or pagination. The SDK deliberately does not synthesize those operations. Category slugs can be passed to either collection as shown above.
43
+
44
+ ## Authentication
45
+
46
+ The public digest can be used without a key. When `apiKey` is provided, the SDK sends it as a Bearer token. Keep keys in environment variables and never ship them to browsers.
47
+
48
+ ## Configuration
49
+
50
+ ```ts
51
+ const client = new NewTqniaClient({
52
+ apiKey: process.env.NEWTQNIA_API_KEY,
53
+ baseUrl: "https://newtqnia.com",
54
+ timeout: 15_000,
55
+ retry: { maxRetries: 3, initialDelayMs: 500, maxDelayMs: 8_000 },
56
+ userAgent: "my-service/2.0",
57
+ defaultHeaders: { "X-Application": "my-service" },
58
+ fetch: customFetch,
59
+ signal: applicationShutdownSignal,
60
+ });
61
+ ```
62
+
63
+ Retries use exponential backoff for temporary network failures and HTTP 429, 500, 502, 503, and 504 responses. `Retry-After` is honored. Authentication and validation failures are never retried.
64
+
65
+ Cancel one request with an `AbortController`:
66
+
67
+ ```ts
68
+ const controller = new AbortController();
69
+ const pending = client.news.latest({ signal: controller.signal });
70
+ controller.abort();
71
+ await pending;
72
+ ```
73
+
74
+ ## Error handling
75
+
76
+ ```ts
77
+ import { NewTqniaError, RateLimitError } from "newtqnia-node";
78
+
79
+ try {
80
+ await client.news.latest();
81
+ } catch (error) {
82
+ if (error instanceof RateLimitError) console.error(error.retryAfter);
83
+ else if (error instanceof NewTqniaError) {
84
+ console.error(error.status, error.requestId, error.code, error.message);
85
+ }
86
+ }
87
+ ```
88
+
89
+ Exported subclasses are `AuthenticationError`, `AuthorizationError`, `ValidationError`, `NotFoundError`, `ConflictError`, `RateLimitError`, `ServerError`, `TimeoutError`, and `NetworkError`.
90
+
91
+ ## TypeScript and modules
92
+
93
+ Request and response types are exported from the single package entry point. Low-level types are generated from `openapi/openapi.yaml` and isolated under `src/generated`; the public API exposes ergonomic names such as `Digest`, `Article`, and `NewsListParams`.
94
+
95
+ ```ts
96
+ import type { Article, Digest, NewsListParams } from "newtqnia-node";
97
+ const sdk = require("newtqnia-node"); // CommonJS is also supported
98
+ ```
99
+
100
+ ## Examples
101
+
102
+ Runnable TypeScript examples cover [latest news](examples/latest-news.ts), [category filtering](examples/categories.ts), [retry configuration](examples/retries.ts), [AbortController](examples/cancellation.ts), and [error handling](examples/error-handling.ts).
103
+
104
+ ## Development
105
+
106
+ ```bash
107
+ npm install
108
+ npm run generate
109
+ npm run check
110
+ npm run docs
111
+ npm run openapi:check
112
+ ```
113
+
114
+ See [CONTRIBUTING.md](CONTRIBUTING.md) and [SECURITY.md](SECURITY.md). The SDK follows Semantic Versioning and is MIT licensed.
115
+
116
+ ## Related resources
117
+
118
+ - [NewTqnia](https://newtqnia.com/) — current technology coverage
119
+ - [Developer documentation](https://newtqnia.com/en/developers) — endpoints, limits, response formats, and terms
120
+ - [Embeddable news widget](https://newtqnia.com/en/widget) — responsive headlines without a custom integration
121
+ - [NewTqnia Start Chrome extension](https://newtqnia.com/en/start) — technology coverage on the browser new-tab page
package/dist/index.cjs ADDED
@@ -0,0 +1,252 @@
1
+ 'use strict';
2
+
3
+ // src/errors/index.ts
4
+ var NewTqniaError = class extends Error {
5
+ status;
6
+ requestId;
7
+ retryAfter;
8
+ code;
9
+ constructor(message, options = {}) {
10
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
11
+ this.name = new.target.name;
12
+ this.status = options.status;
13
+ this.requestId = options.requestId;
14
+ this.retryAfter = options.retryAfter;
15
+ this.code = options.code;
16
+ }
17
+ };
18
+ var AuthenticationError = class extends NewTqniaError {
19
+ };
20
+ var AuthorizationError = class extends NewTqniaError {
21
+ };
22
+ var ValidationError = class extends NewTqniaError {
23
+ };
24
+ var NotFoundError = class extends NewTqniaError {
25
+ };
26
+ var ConflictError = class extends NewTqniaError {
27
+ };
28
+ var RateLimitError = class extends NewTqniaError {
29
+ };
30
+ var ServerError = class extends NewTqniaError {
31
+ };
32
+ var TimeoutError = class extends NewTqniaError {
33
+ };
34
+ var NetworkError = class extends NewTqniaError {
35
+ };
36
+
37
+ // src/resources/news.ts
38
+ var NewsResource = class {
39
+ /** @internal */
40
+ constructor(transport) {
41
+ this.transport = transport;
42
+ }
43
+ transport;
44
+ /** Get articles published today using the Asia/Dubai day boundary. */
45
+ today(params = {}) {
46
+ return this.get("today", params);
47
+ }
48
+ /** Get the latest published articles. */
49
+ latest(params = {}) {
50
+ return this.get("latest", params);
51
+ }
52
+ get(collection, params) {
53
+ if (params.locale !== void 0 && params.locale !== "en" && params.locale !== "ar") {
54
+ throw new ValidationError('locale must be "en" or "ar".');
55
+ }
56
+ if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 10)) {
57
+ throw new ValidationError("limit must be an integer between 1 and 10.");
58
+ }
59
+ if (params.category !== void 0 && params.category.trim() === "") {
60
+ throw new ValidationError("category cannot be empty.");
61
+ }
62
+ return this.transport.request({
63
+ method: "GET",
64
+ path: `/v1/news/${collection}`,
65
+ query: { locale: params.locale, limit: params.limit, category: params.category },
66
+ ...params.signal ? { signal: params.signal } : {}
67
+ });
68
+ }
69
+ };
70
+
71
+ // src/utils/query.ts
72
+ function serializeQuery(values) {
73
+ const search = new URLSearchParams();
74
+ for (const [key, value] of Object.entries(values)) {
75
+ if (value !== void 0) search.append(key, String(value));
76
+ }
77
+ return search.toString();
78
+ }
79
+
80
+ // src/transport/transport.ts
81
+ var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
82
+ var Transport = class {
83
+ constructor(options) {
84
+ this.options = options;
85
+ }
86
+ options;
87
+ async request(request) {
88
+ const query = request.query ? serializeQuery(request.query) : "";
89
+ const url = `${this.options.baseUrl}${request.path}${query ? `?${query}` : ""}`;
90
+ let lastError;
91
+ for (let attempt = 0; attempt <= this.options.retry.maxRetries; attempt += 1) {
92
+ try {
93
+ const response = await this.fetchOnce(url, request);
94
+ if (response.ok) return await this.decode(response);
95
+ if (RETRYABLE.has(response.status) && attempt < this.options.retry.maxRetries) {
96
+ await this.pause(this.retryDelay(response, attempt), request.signal);
97
+ continue;
98
+ }
99
+ throw await this.apiError(response);
100
+ } catch (error) {
101
+ if (error instanceof NewTqniaError) throw error;
102
+ if (this.wasCancelled(request.signal)) throw error;
103
+ if (this.isTimeout(error)) throw new TimeoutError(`Request timed out after ${this.options.timeout}ms`, { cause: error });
104
+ lastError = error;
105
+ if (attempt < this.options.retry.maxRetries) {
106
+ await this.pause(this.backoff(attempt), request.signal);
107
+ continue;
108
+ }
109
+ }
110
+ }
111
+ throw new NetworkError("Unable to reach the NewTqnia API.", { cause: lastError });
112
+ }
113
+ async fetchOnce(url, request) {
114
+ const timeout = AbortSignal.timeout(this.options.timeout);
115
+ const signals = [timeout, this.options.signal, request.signal].filter((signal2) => signal2 !== void 0);
116
+ const signal = this.combineSignals(signals);
117
+ const headers = new Headers(this.options.headers);
118
+ headers.set("Accept", "application/json");
119
+ headers.set("User-Agent", this.options.userAgent);
120
+ if (this.options.apiKey) headers.set("Authorization", `Bearer ${this.options.apiKey}`);
121
+ return this.options.fetch(url, { method: request.method, headers, signal: signal ?? null });
122
+ }
123
+ async decode(response) {
124
+ try {
125
+ return await response.json();
126
+ } catch (cause) {
127
+ throw new NewTqniaError("The API returned invalid JSON.", {
128
+ status: response.status,
129
+ requestId: this.requestId(response),
130
+ cause
131
+ });
132
+ }
133
+ }
134
+ async apiError(response) {
135
+ const data = await response.json().catch(() => ({}));
136
+ const message = typeof data.message === "string" ? data.message : `NewTqnia API request failed with status ${response.status}.`;
137
+ const retryAfter = this.parseRetryAfter(response.headers.get("retry-after"));
138
+ const options = {
139
+ status: response.status,
140
+ requestId: this.requestId(response),
141
+ retryAfter,
142
+ code: typeof data.code === "string" ? data.code : void 0
143
+ };
144
+ if (response.status === 401) return new AuthenticationError(message, options);
145
+ if (response.status === 403) return new AuthorizationError(message, options);
146
+ if (response.status === 404) return new NotFoundError(message, options);
147
+ if (response.status === 409) return new ConflictError(message, options);
148
+ if (response.status === 422 || response.status === 400) return new ValidationError(message, options);
149
+ if (response.status === 429) return new RateLimitError(message, options);
150
+ if (response.status >= 500) return new ServerError(message, options);
151
+ return new NewTqniaError(message, options);
152
+ }
153
+ retryDelay(response, attempt) {
154
+ return this.parseRetryAfter(response.headers.get("retry-after")) ?? this.backoff(attempt);
155
+ }
156
+ parseRetryAfter(value) {
157
+ if (!value) return void 0;
158
+ const seconds = Number(value);
159
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
160
+ const date = Date.parse(value);
161
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
162
+ }
163
+ backoff(attempt) {
164
+ const raw = this.options.retry.initialDelayMs * 2 ** attempt;
165
+ return Math.min(raw, this.options.retry.maxDelayMs);
166
+ }
167
+ async pause(ms, signal) {
168
+ await new Promise((resolve, reject) => {
169
+ const timer = setTimeout(resolve, ms);
170
+ signal?.addEventListener("abort", () => {
171
+ clearTimeout(timer);
172
+ reject(signal.reason);
173
+ }, { once: true });
174
+ });
175
+ }
176
+ requestId(response) {
177
+ return response.headers.get("x-request-id") ?? void 0;
178
+ }
179
+ combineSignals(signals) {
180
+ if (signals.length === 0) return void 0;
181
+ if (signals.length === 1) return signals[0];
182
+ const controller = new AbortController();
183
+ const abort = (signal) => {
184
+ if (!controller.signal.aborted) controller.abort(signal.reason);
185
+ };
186
+ for (const signal of signals) {
187
+ if (signal.aborted) {
188
+ abort(signal);
189
+ break;
190
+ }
191
+ signal.addEventListener("abort", () => abort(signal), { once: true });
192
+ }
193
+ return controller.signal;
194
+ }
195
+ wasCancelled(signal) {
196
+ return Boolean(signal?.aborted || this.options.signal?.aborted);
197
+ }
198
+ isTimeout(error) {
199
+ return error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError") && !this.options.signal?.aborted;
200
+ }
201
+ };
202
+
203
+ // src/client/client.ts
204
+ var NewTqniaClient = class {
205
+ /** Localized news endpoints. */
206
+ news;
207
+ constructor(options = {}) {
208
+ const baseUrl = (options.baseUrl ?? "https://newtqnia.com").replace(/\/+$/, "");
209
+ this.validate(baseUrl, options.timeout);
210
+ const transport = new Transport({
211
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
212
+ baseUrl,
213
+ timeout: options.timeout ?? 3e4,
214
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
215
+ retry: {
216
+ maxRetries: options.retry?.maxRetries ?? 2,
217
+ initialDelayMs: options.retry?.initialDelayMs ?? 500,
218
+ maxDelayMs: options.retry?.maxDelayMs ?? 8e3
219
+ },
220
+ userAgent: options.userAgent ?? "newtqnia-node/1.0.0",
221
+ headers: options.defaultHeaders ?? {},
222
+ ...options.signal ? { signal: options.signal } : {}
223
+ });
224
+ this.news = new NewsResource(transport);
225
+ }
226
+ validate(baseUrl, timeout) {
227
+ let url;
228
+ try {
229
+ url = new URL(baseUrl);
230
+ } catch {
231
+ throw new ValidationError("baseUrl must be an absolute HTTP(S) URL.");
232
+ }
233
+ if (!["http:", "https:"].includes(url.protocol)) throw new ValidationError("baseUrl must be an absolute HTTP(S) URL.");
234
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
235
+ if (url.protocol !== "https:" && !local) throw new ValidationError("baseUrl must use HTTPS except for local development.");
236
+ if (timeout !== void 0 && (!Number.isFinite(timeout) || timeout <= 0)) throw new ValidationError("timeout must be a positive number.");
237
+ }
238
+ };
239
+
240
+ exports.AuthenticationError = AuthenticationError;
241
+ exports.AuthorizationError = AuthorizationError;
242
+ exports.ConflictError = ConflictError;
243
+ exports.NetworkError = NetworkError;
244
+ exports.NewTqniaClient = NewTqniaClient;
245
+ exports.NewTqniaError = NewTqniaError;
246
+ exports.NotFoundError = NotFoundError;
247
+ exports.RateLimitError = RateLimitError;
248
+ exports.ServerError = ServerError;
249
+ exports.TimeoutError = TimeoutError;
250
+ exports.ValidationError = ValidationError;
251
+ //# sourceMappingURL=index.cjs.map
252
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/index.ts","../src/resources/news.ts","../src/utils/query.ts","../src/transport/transport.ts","../src/client/client.ts"],"names":["signal"],"mappings":"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","file":"index.cjs","sourcesContent":["export interface ErrorOptions {\n status?: number | undefined;\n requestId?: string | undefined;\n retryAfter?: number | undefined;\n code?: string | undefined;\n cause?: unknown | undefined;\n}\n\n/** Base class for all SDK errors. */\nexport class NewTqniaError extends Error {\n readonly status: number | undefined;\n readonly requestId: string | undefined;\n readonly retryAfter: number | undefined;\n readonly code: string | undefined;\n\n constructor(message: string, options: ErrorOptions = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n this.retryAfter = options.retryAfter;\n this.code = options.code;\n }\n}\nexport class AuthenticationError extends NewTqniaError {}\nexport class AuthorizationError extends NewTqniaError {}\nexport class ValidationError extends NewTqniaError {}\nexport class NotFoundError extends NewTqniaError {}\nexport class ConflictError extends NewTqniaError {}\nexport class RateLimitError extends NewTqniaError {}\nexport class ServerError extends NewTqniaError {}\nexport class TimeoutError extends NewTqniaError {}\nexport class NetworkError extends NewTqniaError {}\n","import { ValidationError } from \"../errors/index.js\";\nimport type { Digest, NewsListParams } from \"../models/index.js\";\nimport type { Transport } from \"../transport/transport.js\";\n\n/** Access localized NewTqnia news collections. */\nexport class NewsResource {\n /** @internal */\n constructor(private readonly transport: Transport) {}\n\n /** Get articles published today using the Asia/Dubai day boundary. */\n today(params: NewsListParams = {}): Promise<Digest> {\n return this.get(\"today\", params);\n }\n\n /** Get the latest published articles. */\n latest(params: NewsListParams = {}): Promise<Digest> {\n return this.get(\"latest\", params);\n }\n\n private get(collection: \"today\" | \"latest\", params: NewsListParams): Promise<Digest> {\n if (params.locale !== undefined && params.locale !== \"en\" && params.locale !== \"ar\") {\n throw new ValidationError('locale must be \"en\" or \"ar\".');\n }\n if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 10)) {\n throw new ValidationError(\"limit must be an integer between 1 and 10.\");\n }\n if (params.category !== undefined && params.category.trim() === \"\") {\n throw new ValidationError(\"category cannot be empty.\");\n }\n return this.transport.request<Digest>({\n method: \"GET\",\n path: `/v1/news/${collection}`,\n query: { locale: params.locale, limit: params.limit, category: params.category },\n ...(params.signal ? { signal: params.signal } : {}),\n });\n }\n}\n","export function serializeQuery(values: Record<string, string | number | boolean | undefined>): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(values)) {\n if (value !== undefined) search.append(key, String(value));\n }\n return search.toString();\n}\n","import {\n AuthenticationError, AuthorizationError, ConflictError, NetworkError, NewTqniaError,\n NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError,\n} from \"../errors/index.js\";\nimport { serializeQuery } from \"../utils/query.js\";\nimport type { Fetch, RequestOptions, RetryOptions } from \"./types.js\";\n\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\n\nexport interface TransportOptions {\n apiKey?: string;\n baseUrl: string;\n timeout: number;\n fetch: Fetch;\n retry: Required<RetryOptions>;\n userAgent: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport class Transport {\n constructor(private readonly options: TransportOptions) {}\n\n async request<T>(request: RequestOptions): Promise<T> {\n const query = request.query ? serializeQuery(request.query) : \"\";\n const url = `${this.options.baseUrl}${request.path}${query ? `?${query}` : \"\"}`;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.options.retry.maxRetries; attempt += 1) {\n try {\n const response = await this.fetchOnce(url, request);\n if (response.ok) return await this.decode<T>(response);\n if (RETRYABLE.has(response.status) && attempt < this.options.retry.maxRetries) {\n await this.pause(this.retryDelay(response, attempt), request.signal);\n continue;\n }\n throw await this.apiError(response);\n } catch (error) {\n if (error instanceof NewTqniaError) throw error;\n if (this.wasCancelled(request.signal)) throw error;\n if (this.isTimeout(error)) throw new TimeoutError(`Request timed out after ${this.options.timeout}ms`, { cause: error });\n lastError = error;\n if (attempt < this.options.retry.maxRetries) {\n await this.pause(this.backoff(attempt), request.signal);\n continue;\n }\n }\n }\n throw new NetworkError(\"Unable to reach the NewTqnia API.\", { cause: lastError });\n }\n\n private async fetchOnce(url: string, request: RequestOptions): Promise<Response> {\n const timeout = AbortSignal.timeout(this.options.timeout);\n const signals = [timeout, this.options.signal, request.signal].filter((signal): signal is AbortSignal => signal !== undefined);\n const signal = this.combineSignals(signals);\n const headers = new Headers(this.options.headers);\n headers.set(\"Accept\", \"application/json\");\n headers.set(\"User-Agent\", this.options.userAgent);\n if (this.options.apiKey) headers.set(\"Authorization\", `Bearer ${this.options.apiKey}`);\n return this.options.fetch(url, { method: request.method, headers, signal: signal ?? null });\n }\n\n private async decode<T>(response: Response): Promise<T> {\n try {\n return await response.json() as T;\n } catch (cause) {\n throw new NewTqniaError(\"The API returned invalid JSON.\", {\n status: response.status, requestId: this.requestId(response), cause,\n });\n }\n }\n\n private async apiError(response: Response): Promise<NewTqniaError> {\n const data = await response.json().catch(() => ({})) as Record<string, unknown>;\n const message = typeof data.message === \"string\" ? data.message : `NewTqnia API request failed with status ${response.status}.`;\n const retryAfter = this.parseRetryAfter(response.headers.get(\"retry-after\"));\n const options = {\n status: response.status,\n requestId: this.requestId(response),\n retryAfter,\n code: typeof data.code === \"string\" ? data.code : undefined,\n };\n if (response.status === 401) return new AuthenticationError(message, options);\n if (response.status === 403) return new AuthorizationError(message, options);\n if (response.status === 404) return new NotFoundError(message, options);\n if (response.status === 409) return new ConflictError(message, options);\n if (response.status === 422 || response.status === 400) return new ValidationError(message, options);\n if (response.status === 429) return new RateLimitError(message, options);\n if (response.status >= 500) return new ServerError(message, options);\n return new NewTqniaError(message, options);\n }\n\n private retryDelay(response: Response, attempt: number): number {\n return this.parseRetryAfter(response.headers.get(\"retry-after\")) ?? this.backoff(attempt);\n }\n private parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n }\n private backoff(attempt: number): number {\n const raw = this.options.retry.initialDelayMs * (2 ** attempt);\n return Math.min(raw, this.options.retry.maxDelayMs);\n }\n private async pause(ms: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\"abort\", () => { clearTimeout(timer); reject(signal.reason); }, { once: true });\n });\n }\n private requestId(response: Response): string | undefined {\n return response.headers.get(\"x-request-id\") ?? undefined;\n }\n private combineSignals(signals: AbortSignal[]): AbortSignal | undefined {\n if (signals.length === 0) return undefined;\n if (signals.length === 1) return signals[0];\n const controller = new AbortController();\n const abort = (signal: AbortSignal) => {\n if (!controller.signal.aborted) controller.abort(signal.reason);\n };\n for (const signal of signals) {\n if (signal.aborted) {\n abort(signal);\n break;\n }\n signal.addEventListener(\"abort\", () => abort(signal), { once: true });\n }\n return controller.signal;\n }\n private wasCancelled(signal?: AbortSignal): boolean {\n return Boolean(signal?.aborted || this.options.signal?.aborted);\n }\n private isTimeout(error: unknown): boolean {\n return error instanceof DOMException && (error.name === \"TimeoutError\" || error.name === \"AbortError\")\n && !this.options.signal?.aborted;\n }\n}\n","import { ValidationError } from \"../errors/index.js\";\nimport { NewsResource } from \"../resources/news.js\";\nimport { Transport } from \"../transport/transport.js\";\nimport type { Fetch, RetryOptions } from \"../transport/types.js\";\n\nexport interface NewTqniaClientOptions {\n /** Optional bearer API key. */\n apiKey?: string;\n /** API origin. @default \"https://newtqnia.com\" */\n baseUrl?: string;\n /** Request timeout in milliseconds. @default 30000 */\n timeout?: number;\n /** Cancels every request made by this client. */\n signal?: AbortSignal;\n /** Custom Fetch-compatible implementation. */\n fetch?: Fetch;\n /** Retry behavior for rate limits, server errors, and temporary network failures. */\n retry?: RetryOptions;\n /** User-Agent header. @default \"newtqnia-node/1.0.0\" */\n userAgent?: string;\n /** Headers included with every request. */\n defaultHeaders?: Record<string, string>;\n}\n\n/** Official NewTqnia API client. */\nexport class NewTqniaClient {\n /** Localized news endpoints. */\n readonly news: NewsResource;\n\n constructor(options: NewTqniaClientOptions = {}) {\n const baseUrl = (options.baseUrl ?? \"https://newtqnia.com\").replace(/\\/+$/, \"\");\n this.validate(baseUrl, options.timeout);\n const transport = new Transport({\n ...(options.apiKey ? { apiKey: options.apiKey } : {}),\n baseUrl,\n timeout: options.timeout ?? 30_000,\n fetch: options.fetch ?? globalThis.fetch.bind(globalThis),\n retry: {\n maxRetries: options.retry?.maxRetries ?? 2,\n initialDelayMs: options.retry?.initialDelayMs ?? 500,\n maxDelayMs: options.retry?.maxDelayMs ?? 8_000,\n },\n userAgent: options.userAgent ?? \"newtqnia-node/1.0.0\",\n headers: options.defaultHeaders ?? {},\n ...(options.signal ? { signal: options.signal } : {}),\n });\n this.news = new NewsResource(transport);\n }\n\n private validate(baseUrl: string, timeout?: number): void {\n let url: URL;\n try { url = new URL(baseUrl); } catch { throw new ValidationError(\"baseUrl must be an absolute HTTP(S) URL.\"); }\n if (![\"http:\", \"https:\"].includes(url.protocol)) throw new ValidationError(\"baseUrl must be an absolute HTTP(S) URL.\");\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !local) throw new ValidationError(\"baseUrl must use HTTPS except for local development.\");\n if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) throw new ValidationError(\"timeout must be a positive number.\");\n }\n}\n"]}
@@ -0,0 +1,204 @@
1
+ interface components {
2
+ schemas: {
3
+ Digest: {
4
+ /** @constant */
5
+ api_version: "v1";
6
+ /** @enum {string} */
7
+ collection: "today" | "latest";
8
+ /** Format: date */
9
+ date?: string;
10
+ /** @constant */
11
+ timezone: "Asia/Dubai";
12
+ /** @enum {string} */
13
+ locale: "en" | "ar";
14
+ /** @enum {string} */
15
+ direction: "ltr" | "rtl";
16
+ publisher: components["schemas"]["LinkLabel"];
17
+ attribution: {
18
+ text: string;
19
+ /** Format: uri */
20
+ url: string;
21
+ required: boolean;
22
+ };
23
+ /** Format: date-time */
24
+ generated_at: string;
25
+ articles: components["schemas"]["Article"][];
26
+ _links: {
27
+ [key: string]: string;
28
+ };
29
+ };
30
+ Article: {
31
+ id: number;
32
+ title: string;
33
+ summary: string;
34
+ category: {
35
+ slug: string;
36
+ name: string;
37
+ };
38
+ /** Format: uri */
39
+ image: string;
40
+ /** Format: uri */
41
+ url: string;
42
+ /** Format: date-time */
43
+ published_at: string;
44
+ read_time: number;
45
+ };
46
+ LinkLabel: {
47
+ name: string;
48
+ /** Format: uri */
49
+ url: string;
50
+ };
51
+ };
52
+ responses: never;
53
+ parameters: {
54
+ Locale: "en" | "ar";
55
+ Limit: number;
56
+ /** @description Optional category slug. */
57
+ Category: string;
58
+ };
59
+ requestBodies: never;
60
+ headers: {
61
+ /** @description Validator for conditional requests using If-None-Match. */
62
+ ETag: string;
63
+ /** @description Public caching instructions. */
64
+ CacheControl: string;
65
+ };
66
+ pathItems: never;
67
+ }
68
+
69
+ /** A localized NewTqnia article. */
70
+ type Article = components["schemas"]["Article"];
71
+ /** A localized collection of NewTqnia articles and attribution metadata. */
72
+ type Digest = components["schemas"]["Digest"];
73
+ /** A publisher name and URL. */
74
+ type LinkLabel = components["schemas"]["LinkLabel"];
75
+ /** Supported response locale. */
76
+ type Locale = Digest["locale"];
77
+ /** Filters accepted by both news collections. */
78
+ interface NewsListParams {
79
+ /** Response language. @default "en" */
80
+ locale?: Locale;
81
+ /** Number of articles, from 1 through 10. @default 10 */
82
+ limit?: number;
83
+ /** Optional category slug. */
84
+ category?: string;
85
+ /** Cancels this request without affecting other client requests. */
86
+ signal?: AbortSignal;
87
+ }
88
+
89
+ type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
90
+ interface RetryOptions {
91
+ /** Maximum retry attempts after the initial request. @default 2 */
92
+ maxRetries?: number;
93
+ /** Initial exponential-backoff delay in milliseconds. @default 500 */
94
+ initialDelayMs?: number;
95
+ /** Maximum backoff delay in milliseconds. @default 8000 */
96
+ maxDelayMs?: number;
97
+ }
98
+ interface RequestOptions {
99
+ method: "GET";
100
+ path: string;
101
+ query?: Record<string, string | number | boolean | undefined>;
102
+ signal?: AbortSignal;
103
+ }
104
+
105
+ interface TransportOptions {
106
+ apiKey?: string;
107
+ baseUrl: string;
108
+ timeout: number;
109
+ fetch: Fetch;
110
+ retry: Required<RetryOptions>;
111
+ userAgent: string;
112
+ headers: Record<string, string>;
113
+ signal?: AbortSignal;
114
+ }
115
+ declare class Transport {
116
+ private readonly options;
117
+ constructor(options: TransportOptions);
118
+ request<T>(request: RequestOptions): Promise<T>;
119
+ private fetchOnce;
120
+ private decode;
121
+ private apiError;
122
+ private retryDelay;
123
+ private parseRetryAfter;
124
+ private backoff;
125
+ private pause;
126
+ private requestId;
127
+ private combineSignals;
128
+ private wasCancelled;
129
+ private isTimeout;
130
+ }
131
+
132
+ /** Access localized NewTqnia news collections. */
133
+ declare class NewsResource {
134
+ private readonly transport;
135
+ /** @internal */
136
+ constructor(transport: Transport);
137
+ /** Get articles published today using the Asia/Dubai day boundary. */
138
+ today(params?: NewsListParams): Promise<Digest>;
139
+ /** Get the latest published articles. */
140
+ latest(params?: NewsListParams): Promise<Digest>;
141
+ private get;
142
+ }
143
+
144
+ interface NewTqniaClientOptions {
145
+ /** Optional bearer API key. */
146
+ apiKey?: string;
147
+ /** API origin. @default "https://newtqnia.com" */
148
+ baseUrl?: string;
149
+ /** Request timeout in milliseconds. @default 30000 */
150
+ timeout?: number;
151
+ /** Cancels every request made by this client. */
152
+ signal?: AbortSignal;
153
+ /** Custom Fetch-compatible implementation. */
154
+ fetch?: Fetch;
155
+ /** Retry behavior for rate limits, server errors, and temporary network failures. */
156
+ retry?: RetryOptions;
157
+ /** User-Agent header. @default "newtqnia-node/1.0.0" */
158
+ userAgent?: string;
159
+ /** Headers included with every request. */
160
+ defaultHeaders?: Record<string, string>;
161
+ }
162
+ /** Official NewTqnia API client. */
163
+ declare class NewTqniaClient {
164
+ /** Localized news endpoints. */
165
+ readonly news: NewsResource;
166
+ constructor(options?: NewTqniaClientOptions);
167
+ private validate;
168
+ }
169
+
170
+ interface ErrorOptions {
171
+ status?: number | undefined;
172
+ requestId?: string | undefined;
173
+ retryAfter?: number | undefined;
174
+ code?: string | undefined;
175
+ cause?: unknown | undefined;
176
+ }
177
+ /** Base class for all SDK errors. */
178
+ declare class NewTqniaError extends Error {
179
+ readonly status: number | undefined;
180
+ readonly requestId: string | undefined;
181
+ readonly retryAfter: number | undefined;
182
+ readonly code: string | undefined;
183
+ constructor(message: string, options?: ErrorOptions);
184
+ }
185
+ declare class AuthenticationError extends NewTqniaError {
186
+ }
187
+ declare class AuthorizationError extends NewTqniaError {
188
+ }
189
+ declare class ValidationError extends NewTqniaError {
190
+ }
191
+ declare class NotFoundError extends NewTqniaError {
192
+ }
193
+ declare class ConflictError extends NewTqniaError {
194
+ }
195
+ declare class RateLimitError extends NewTqniaError {
196
+ }
197
+ declare class ServerError extends NewTqniaError {
198
+ }
199
+ declare class TimeoutError extends NewTqniaError {
200
+ }
201
+ declare class NetworkError extends NewTqniaError {
202
+ }
203
+
204
+ export { type Article, AuthenticationError, AuthorizationError, ConflictError, type Digest, type Fetch, type LinkLabel, type Locale, NetworkError, NewTqniaClient, type NewTqniaClientOptions, NewTqniaError, type NewsListParams, NotFoundError, RateLimitError, type RetryOptions, ServerError, TimeoutError, ValidationError };
@@ -0,0 +1,204 @@
1
+ interface components {
2
+ schemas: {
3
+ Digest: {
4
+ /** @constant */
5
+ api_version: "v1";
6
+ /** @enum {string} */
7
+ collection: "today" | "latest";
8
+ /** Format: date */
9
+ date?: string;
10
+ /** @constant */
11
+ timezone: "Asia/Dubai";
12
+ /** @enum {string} */
13
+ locale: "en" | "ar";
14
+ /** @enum {string} */
15
+ direction: "ltr" | "rtl";
16
+ publisher: components["schemas"]["LinkLabel"];
17
+ attribution: {
18
+ text: string;
19
+ /** Format: uri */
20
+ url: string;
21
+ required: boolean;
22
+ };
23
+ /** Format: date-time */
24
+ generated_at: string;
25
+ articles: components["schemas"]["Article"][];
26
+ _links: {
27
+ [key: string]: string;
28
+ };
29
+ };
30
+ Article: {
31
+ id: number;
32
+ title: string;
33
+ summary: string;
34
+ category: {
35
+ slug: string;
36
+ name: string;
37
+ };
38
+ /** Format: uri */
39
+ image: string;
40
+ /** Format: uri */
41
+ url: string;
42
+ /** Format: date-time */
43
+ published_at: string;
44
+ read_time: number;
45
+ };
46
+ LinkLabel: {
47
+ name: string;
48
+ /** Format: uri */
49
+ url: string;
50
+ };
51
+ };
52
+ responses: never;
53
+ parameters: {
54
+ Locale: "en" | "ar";
55
+ Limit: number;
56
+ /** @description Optional category slug. */
57
+ Category: string;
58
+ };
59
+ requestBodies: never;
60
+ headers: {
61
+ /** @description Validator for conditional requests using If-None-Match. */
62
+ ETag: string;
63
+ /** @description Public caching instructions. */
64
+ CacheControl: string;
65
+ };
66
+ pathItems: never;
67
+ }
68
+
69
+ /** A localized NewTqnia article. */
70
+ type Article = components["schemas"]["Article"];
71
+ /** A localized collection of NewTqnia articles and attribution metadata. */
72
+ type Digest = components["schemas"]["Digest"];
73
+ /** A publisher name and URL. */
74
+ type LinkLabel = components["schemas"]["LinkLabel"];
75
+ /** Supported response locale. */
76
+ type Locale = Digest["locale"];
77
+ /** Filters accepted by both news collections. */
78
+ interface NewsListParams {
79
+ /** Response language. @default "en" */
80
+ locale?: Locale;
81
+ /** Number of articles, from 1 through 10. @default 10 */
82
+ limit?: number;
83
+ /** Optional category slug. */
84
+ category?: string;
85
+ /** Cancels this request without affecting other client requests. */
86
+ signal?: AbortSignal;
87
+ }
88
+
89
+ type Fetch = (input: RequestInfo | URL, init?: RequestInit) => Promise<Response>;
90
+ interface RetryOptions {
91
+ /** Maximum retry attempts after the initial request. @default 2 */
92
+ maxRetries?: number;
93
+ /** Initial exponential-backoff delay in milliseconds. @default 500 */
94
+ initialDelayMs?: number;
95
+ /** Maximum backoff delay in milliseconds. @default 8000 */
96
+ maxDelayMs?: number;
97
+ }
98
+ interface RequestOptions {
99
+ method: "GET";
100
+ path: string;
101
+ query?: Record<string, string | number | boolean | undefined>;
102
+ signal?: AbortSignal;
103
+ }
104
+
105
+ interface TransportOptions {
106
+ apiKey?: string;
107
+ baseUrl: string;
108
+ timeout: number;
109
+ fetch: Fetch;
110
+ retry: Required<RetryOptions>;
111
+ userAgent: string;
112
+ headers: Record<string, string>;
113
+ signal?: AbortSignal;
114
+ }
115
+ declare class Transport {
116
+ private readonly options;
117
+ constructor(options: TransportOptions);
118
+ request<T>(request: RequestOptions): Promise<T>;
119
+ private fetchOnce;
120
+ private decode;
121
+ private apiError;
122
+ private retryDelay;
123
+ private parseRetryAfter;
124
+ private backoff;
125
+ private pause;
126
+ private requestId;
127
+ private combineSignals;
128
+ private wasCancelled;
129
+ private isTimeout;
130
+ }
131
+
132
+ /** Access localized NewTqnia news collections. */
133
+ declare class NewsResource {
134
+ private readonly transport;
135
+ /** @internal */
136
+ constructor(transport: Transport);
137
+ /** Get articles published today using the Asia/Dubai day boundary. */
138
+ today(params?: NewsListParams): Promise<Digest>;
139
+ /** Get the latest published articles. */
140
+ latest(params?: NewsListParams): Promise<Digest>;
141
+ private get;
142
+ }
143
+
144
+ interface NewTqniaClientOptions {
145
+ /** Optional bearer API key. */
146
+ apiKey?: string;
147
+ /** API origin. @default "https://newtqnia.com" */
148
+ baseUrl?: string;
149
+ /** Request timeout in milliseconds. @default 30000 */
150
+ timeout?: number;
151
+ /** Cancels every request made by this client. */
152
+ signal?: AbortSignal;
153
+ /** Custom Fetch-compatible implementation. */
154
+ fetch?: Fetch;
155
+ /** Retry behavior for rate limits, server errors, and temporary network failures. */
156
+ retry?: RetryOptions;
157
+ /** User-Agent header. @default "newtqnia-node/1.0.0" */
158
+ userAgent?: string;
159
+ /** Headers included with every request. */
160
+ defaultHeaders?: Record<string, string>;
161
+ }
162
+ /** Official NewTqnia API client. */
163
+ declare class NewTqniaClient {
164
+ /** Localized news endpoints. */
165
+ readonly news: NewsResource;
166
+ constructor(options?: NewTqniaClientOptions);
167
+ private validate;
168
+ }
169
+
170
+ interface ErrorOptions {
171
+ status?: number | undefined;
172
+ requestId?: string | undefined;
173
+ retryAfter?: number | undefined;
174
+ code?: string | undefined;
175
+ cause?: unknown | undefined;
176
+ }
177
+ /** Base class for all SDK errors. */
178
+ declare class NewTqniaError extends Error {
179
+ readonly status: number | undefined;
180
+ readonly requestId: string | undefined;
181
+ readonly retryAfter: number | undefined;
182
+ readonly code: string | undefined;
183
+ constructor(message: string, options?: ErrorOptions);
184
+ }
185
+ declare class AuthenticationError extends NewTqniaError {
186
+ }
187
+ declare class AuthorizationError extends NewTqniaError {
188
+ }
189
+ declare class ValidationError extends NewTqniaError {
190
+ }
191
+ declare class NotFoundError extends NewTqniaError {
192
+ }
193
+ declare class ConflictError extends NewTqniaError {
194
+ }
195
+ declare class RateLimitError extends NewTqniaError {
196
+ }
197
+ declare class ServerError extends NewTqniaError {
198
+ }
199
+ declare class TimeoutError extends NewTqniaError {
200
+ }
201
+ declare class NetworkError extends NewTqniaError {
202
+ }
203
+
204
+ export { type Article, AuthenticationError, AuthorizationError, ConflictError, type Digest, type Fetch, type LinkLabel, type Locale, NetworkError, NewTqniaClient, type NewTqniaClientOptions, NewTqniaError, type NewsListParams, NotFoundError, RateLimitError, type RetryOptions, ServerError, TimeoutError, ValidationError };
package/dist/index.js ADDED
@@ -0,0 +1,240 @@
1
+ // src/errors/index.ts
2
+ var NewTqniaError = class extends Error {
3
+ status;
4
+ requestId;
5
+ retryAfter;
6
+ code;
7
+ constructor(message, options = {}) {
8
+ super(message, options.cause === void 0 ? void 0 : { cause: options.cause });
9
+ this.name = new.target.name;
10
+ this.status = options.status;
11
+ this.requestId = options.requestId;
12
+ this.retryAfter = options.retryAfter;
13
+ this.code = options.code;
14
+ }
15
+ };
16
+ var AuthenticationError = class extends NewTqniaError {
17
+ };
18
+ var AuthorizationError = class extends NewTqniaError {
19
+ };
20
+ var ValidationError = class extends NewTqniaError {
21
+ };
22
+ var NotFoundError = class extends NewTqniaError {
23
+ };
24
+ var ConflictError = class extends NewTqniaError {
25
+ };
26
+ var RateLimitError = class extends NewTqniaError {
27
+ };
28
+ var ServerError = class extends NewTqniaError {
29
+ };
30
+ var TimeoutError = class extends NewTqniaError {
31
+ };
32
+ var NetworkError = class extends NewTqniaError {
33
+ };
34
+
35
+ // src/resources/news.ts
36
+ var NewsResource = class {
37
+ /** @internal */
38
+ constructor(transport) {
39
+ this.transport = transport;
40
+ }
41
+ transport;
42
+ /** Get articles published today using the Asia/Dubai day boundary. */
43
+ today(params = {}) {
44
+ return this.get("today", params);
45
+ }
46
+ /** Get the latest published articles. */
47
+ latest(params = {}) {
48
+ return this.get("latest", params);
49
+ }
50
+ get(collection, params) {
51
+ if (params.locale !== void 0 && params.locale !== "en" && params.locale !== "ar") {
52
+ throw new ValidationError('locale must be "en" or "ar".');
53
+ }
54
+ if (params.limit !== void 0 && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 10)) {
55
+ throw new ValidationError("limit must be an integer between 1 and 10.");
56
+ }
57
+ if (params.category !== void 0 && params.category.trim() === "") {
58
+ throw new ValidationError("category cannot be empty.");
59
+ }
60
+ return this.transport.request({
61
+ method: "GET",
62
+ path: `/v1/news/${collection}`,
63
+ query: { locale: params.locale, limit: params.limit, category: params.category },
64
+ ...params.signal ? { signal: params.signal } : {}
65
+ });
66
+ }
67
+ };
68
+
69
+ // src/utils/query.ts
70
+ function serializeQuery(values) {
71
+ const search = new URLSearchParams();
72
+ for (const [key, value] of Object.entries(values)) {
73
+ if (value !== void 0) search.append(key, String(value));
74
+ }
75
+ return search.toString();
76
+ }
77
+
78
+ // src/transport/transport.ts
79
+ var RETRYABLE = /* @__PURE__ */ new Set([429, 500, 502, 503, 504]);
80
+ var Transport = class {
81
+ constructor(options) {
82
+ this.options = options;
83
+ }
84
+ options;
85
+ async request(request) {
86
+ const query = request.query ? serializeQuery(request.query) : "";
87
+ const url = `${this.options.baseUrl}${request.path}${query ? `?${query}` : ""}`;
88
+ let lastError;
89
+ for (let attempt = 0; attempt <= this.options.retry.maxRetries; attempt += 1) {
90
+ try {
91
+ const response = await this.fetchOnce(url, request);
92
+ if (response.ok) return await this.decode(response);
93
+ if (RETRYABLE.has(response.status) && attempt < this.options.retry.maxRetries) {
94
+ await this.pause(this.retryDelay(response, attempt), request.signal);
95
+ continue;
96
+ }
97
+ throw await this.apiError(response);
98
+ } catch (error) {
99
+ if (error instanceof NewTqniaError) throw error;
100
+ if (this.wasCancelled(request.signal)) throw error;
101
+ if (this.isTimeout(error)) throw new TimeoutError(`Request timed out after ${this.options.timeout}ms`, { cause: error });
102
+ lastError = error;
103
+ if (attempt < this.options.retry.maxRetries) {
104
+ await this.pause(this.backoff(attempt), request.signal);
105
+ continue;
106
+ }
107
+ }
108
+ }
109
+ throw new NetworkError("Unable to reach the NewTqnia API.", { cause: lastError });
110
+ }
111
+ async fetchOnce(url, request) {
112
+ const timeout = AbortSignal.timeout(this.options.timeout);
113
+ const signals = [timeout, this.options.signal, request.signal].filter((signal2) => signal2 !== void 0);
114
+ const signal = this.combineSignals(signals);
115
+ const headers = new Headers(this.options.headers);
116
+ headers.set("Accept", "application/json");
117
+ headers.set("User-Agent", this.options.userAgent);
118
+ if (this.options.apiKey) headers.set("Authorization", `Bearer ${this.options.apiKey}`);
119
+ return this.options.fetch(url, { method: request.method, headers, signal: signal ?? null });
120
+ }
121
+ async decode(response) {
122
+ try {
123
+ return await response.json();
124
+ } catch (cause) {
125
+ throw new NewTqniaError("The API returned invalid JSON.", {
126
+ status: response.status,
127
+ requestId: this.requestId(response),
128
+ cause
129
+ });
130
+ }
131
+ }
132
+ async apiError(response) {
133
+ const data = await response.json().catch(() => ({}));
134
+ const message = typeof data.message === "string" ? data.message : `NewTqnia API request failed with status ${response.status}.`;
135
+ const retryAfter = this.parseRetryAfter(response.headers.get("retry-after"));
136
+ const options = {
137
+ status: response.status,
138
+ requestId: this.requestId(response),
139
+ retryAfter,
140
+ code: typeof data.code === "string" ? data.code : void 0
141
+ };
142
+ if (response.status === 401) return new AuthenticationError(message, options);
143
+ if (response.status === 403) return new AuthorizationError(message, options);
144
+ if (response.status === 404) return new NotFoundError(message, options);
145
+ if (response.status === 409) return new ConflictError(message, options);
146
+ if (response.status === 422 || response.status === 400) return new ValidationError(message, options);
147
+ if (response.status === 429) return new RateLimitError(message, options);
148
+ if (response.status >= 500) return new ServerError(message, options);
149
+ return new NewTqniaError(message, options);
150
+ }
151
+ retryDelay(response, attempt) {
152
+ return this.parseRetryAfter(response.headers.get("retry-after")) ?? this.backoff(attempt);
153
+ }
154
+ parseRetryAfter(value) {
155
+ if (!value) return void 0;
156
+ const seconds = Number(value);
157
+ if (Number.isFinite(seconds)) return Math.max(0, seconds * 1e3);
158
+ const date = Date.parse(value);
159
+ return Number.isNaN(date) ? void 0 : Math.max(0, date - Date.now());
160
+ }
161
+ backoff(attempt) {
162
+ const raw = this.options.retry.initialDelayMs * 2 ** attempt;
163
+ return Math.min(raw, this.options.retry.maxDelayMs);
164
+ }
165
+ async pause(ms, signal) {
166
+ await new Promise((resolve, reject) => {
167
+ const timer = setTimeout(resolve, ms);
168
+ signal?.addEventListener("abort", () => {
169
+ clearTimeout(timer);
170
+ reject(signal.reason);
171
+ }, { once: true });
172
+ });
173
+ }
174
+ requestId(response) {
175
+ return response.headers.get("x-request-id") ?? void 0;
176
+ }
177
+ combineSignals(signals) {
178
+ if (signals.length === 0) return void 0;
179
+ if (signals.length === 1) return signals[0];
180
+ const controller = new AbortController();
181
+ const abort = (signal) => {
182
+ if (!controller.signal.aborted) controller.abort(signal.reason);
183
+ };
184
+ for (const signal of signals) {
185
+ if (signal.aborted) {
186
+ abort(signal);
187
+ break;
188
+ }
189
+ signal.addEventListener("abort", () => abort(signal), { once: true });
190
+ }
191
+ return controller.signal;
192
+ }
193
+ wasCancelled(signal) {
194
+ return Boolean(signal?.aborted || this.options.signal?.aborted);
195
+ }
196
+ isTimeout(error) {
197
+ return error instanceof DOMException && (error.name === "TimeoutError" || error.name === "AbortError") && !this.options.signal?.aborted;
198
+ }
199
+ };
200
+
201
+ // src/client/client.ts
202
+ var NewTqniaClient = class {
203
+ /** Localized news endpoints. */
204
+ news;
205
+ constructor(options = {}) {
206
+ const baseUrl = (options.baseUrl ?? "https://newtqnia.com").replace(/\/+$/, "");
207
+ this.validate(baseUrl, options.timeout);
208
+ const transport = new Transport({
209
+ ...options.apiKey ? { apiKey: options.apiKey } : {},
210
+ baseUrl,
211
+ timeout: options.timeout ?? 3e4,
212
+ fetch: options.fetch ?? globalThis.fetch.bind(globalThis),
213
+ retry: {
214
+ maxRetries: options.retry?.maxRetries ?? 2,
215
+ initialDelayMs: options.retry?.initialDelayMs ?? 500,
216
+ maxDelayMs: options.retry?.maxDelayMs ?? 8e3
217
+ },
218
+ userAgent: options.userAgent ?? "newtqnia-node/1.0.0",
219
+ headers: options.defaultHeaders ?? {},
220
+ ...options.signal ? { signal: options.signal } : {}
221
+ });
222
+ this.news = new NewsResource(transport);
223
+ }
224
+ validate(baseUrl, timeout) {
225
+ let url;
226
+ try {
227
+ url = new URL(baseUrl);
228
+ } catch {
229
+ throw new ValidationError("baseUrl must be an absolute HTTP(S) URL.");
230
+ }
231
+ if (!["http:", "https:"].includes(url.protocol)) throw new ValidationError("baseUrl must be an absolute HTTP(S) URL.");
232
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
233
+ if (url.protocol !== "https:" && !local) throw new ValidationError("baseUrl must use HTTPS except for local development.");
234
+ if (timeout !== void 0 && (!Number.isFinite(timeout) || timeout <= 0)) throw new ValidationError("timeout must be a positive number.");
235
+ }
236
+ };
237
+
238
+ export { AuthenticationError, AuthorizationError, ConflictError, NetworkError, NewTqniaClient, NewTqniaError, NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError };
239
+ //# sourceMappingURL=index.js.map
240
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/errors/index.ts","../src/resources/news.ts","../src/utils/query.ts","../src/transport/transport.ts","../src/client/client.ts"],"names":["signal"],"mappings":"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","file":"index.js","sourcesContent":["export interface ErrorOptions {\n status?: number | undefined;\n requestId?: string | undefined;\n retryAfter?: number | undefined;\n code?: string | undefined;\n cause?: unknown | undefined;\n}\n\n/** Base class for all SDK errors. */\nexport class NewTqniaError extends Error {\n readonly status: number | undefined;\n readonly requestId: string | undefined;\n readonly retryAfter: number | undefined;\n readonly code: string | undefined;\n\n constructor(message: string, options: ErrorOptions = {}) {\n super(message, options.cause === undefined ? undefined : { cause: options.cause });\n this.name = new.target.name;\n this.status = options.status;\n this.requestId = options.requestId;\n this.retryAfter = options.retryAfter;\n this.code = options.code;\n }\n}\nexport class AuthenticationError extends NewTqniaError {}\nexport class AuthorizationError extends NewTqniaError {}\nexport class ValidationError extends NewTqniaError {}\nexport class NotFoundError extends NewTqniaError {}\nexport class ConflictError extends NewTqniaError {}\nexport class RateLimitError extends NewTqniaError {}\nexport class ServerError extends NewTqniaError {}\nexport class TimeoutError extends NewTqniaError {}\nexport class NetworkError extends NewTqniaError {}\n","import { ValidationError } from \"../errors/index.js\";\nimport type { Digest, NewsListParams } from \"../models/index.js\";\nimport type { Transport } from \"../transport/transport.js\";\n\n/** Access localized NewTqnia news collections. */\nexport class NewsResource {\n /** @internal */\n constructor(private readonly transport: Transport) {}\n\n /** Get articles published today using the Asia/Dubai day boundary. */\n today(params: NewsListParams = {}): Promise<Digest> {\n return this.get(\"today\", params);\n }\n\n /** Get the latest published articles. */\n latest(params: NewsListParams = {}): Promise<Digest> {\n return this.get(\"latest\", params);\n }\n\n private get(collection: \"today\" | \"latest\", params: NewsListParams): Promise<Digest> {\n if (params.locale !== undefined && params.locale !== \"en\" && params.locale !== \"ar\") {\n throw new ValidationError('locale must be \"en\" or \"ar\".');\n }\n if (params.limit !== undefined && (!Number.isInteger(params.limit) || params.limit < 1 || params.limit > 10)) {\n throw new ValidationError(\"limit must be an integer between 1 and 10.\");\n }\n if (params.category !== undefined && params.category.trim() === \"\") {\n throw new ValidationError(\"category cannot be empty.\");\n }\n return this.transport.request<Digest>({\n method: \"GET\",\n path: `/v1/news/${collection}`,\n query: { locale: params.locale, limit: params.limit, category: params.category },\n ...(params.signal ? { signal: params.signal } : {}),\n });\n }\n}\n","export function serializeQuery(values: Record<string, string | number | boolean | undefined>): string {\n const search = new URLSearchParams();\n for (const [key, value] of Object.entries(values)) {\n if (value !== undefined) search.append(key, String(value));\n }\n return search.toString();\n}\n","import {\n AuthenticationError, AuthorizationError, ConflictError, NetworkError, NewTqniaError,\n NotFoundError, RateLimitError, ServerError, TimeoutError, ValidationError,\n} from \"../errors/index.js\";\nimport { serializeQuery } from \"../utils/query.js\";\nimport type { Fetch, RequestOptions, RetryOptions } from \"./types.js\";\n\nconst RETRYABLE = new Set([429, 500, 502, 503, 504]);\n\nexport interface TransportOptions {\n apiKey?: string;\n baseUrl: string;\n timeout: number;\n fetch: Fetch;\n retry: Required<RetryOptions>;\n userAgent: string;\n headers: Record<string, string>;\n signal?: AbortSignal;\n}\n\nexport class Transport {\n constructor(private readonly options: TransportOptions) {}\n\n async request<T>(request: RequestOptions): Promise<T> {\n const query = request.query ? serializeQuery(request.query) : \"\";\n const url = `${this.options.baseUrl}${request.path}${query ? `?${query}` : \"\"}`;\n let lastError: unknown;\n\n for (let attempt = 0; attempt <= this.options.retry.maxRetries; attempt += 1) {\n try {\n const response = await this.fetchOnce(url, request);\n if (response.ok) return await this.decode<T>(response);\n if (RETRYABLE.has(response.status) && attempt < this.options.retry.maxRetries) {\n await this.pause(this.retryDelay(response, attempt), request.signal);\n continue;\n }\n throw await this.apiError(response);\n } catch (error) {\n if (error instanceof NewTqniaError) throw error;\n if (this.wasCancelled(request.signal)) throw error;\n if (this.isTimeout(error)) throw new TimeoutError(`Request timed out after ${this.options.timeout}ms`, { cause: error });\n lastError = error;\n if (attempt < this.options.retry.maxRetries) {\n await this.pause(this.backoff(attempt), request.signal);\n continue;\n }\n }\n }\n throw new NetworkError(\"Unable to reach the NewTqnia API.\", { cause: lastError });\n }\n\n private async fetchOnce(url: string, request: RequestOptions): Promise<Response> {\n const timeout = AbortSignal.timeout(this.options.timeout);\n const signals = [timeout, this.options.signal, request.signal].filter((signal): signal is AbortSignal => signal !== undefined);\n const signal = this.combineSignals(signals);\n const headers = new Headers(this.options.headers);\n headers.set(\"Accept\", \"application/json\");\n headers.set(\"User-Agent\", this.options.userAgent);\n if (this.options.apiKey) headers.set(\"Authorization\", `Bearer ${this.options.apiKey}`);\n return this.options.fetch(url, { method: request.method, headers, signal: signal ?? null });\n }\n\n private async decode<T>(response: Response): Promise<T> {\n try {\n return await response.json() as T;\n } catch (cause) {\n throw new NewTqniaError(\"The API returned invalid JSON.\", {\n status: response.status, requestId: this.requestId(response), cause,\n });\n }\n }\n\n private async apiError(response: Response): Promise<NewTqniaError> {\n const data = await response.json().catch(() => ({})) as Record<string, unknown>;\n const message = typeof data.message === \"string\" ? data.message : `NewTqnia API request failed with status ${response.status}.`;\n const retryAfter = this.parseRetryAfter(response.headers.get(\"retry-after\"));\n const options = {\n status: response.status,\n requestId: this.requestId(response),\n retryAfter,\n code: typeof data.code === \"string\" ? data.code : undefined,\n };\n if (response.status === 401) return new AuthenticationError(message, options);\n if (response.status === 403) return new AuthorizationError(message, options);\n if (response.status === 404) return new NotFoundError(message, options);\n if (response.status === 409) return new ConflictError(message, options);\n if (response.status === 422 || response.status === 400) return new ValidationError(message, options);\n if (response.status === 429) return new RateLimitError(message, options);\n if (response.status >= 500) return new ServerError(message, options);\n return new NewTqniaError(message, options);\n }\n\n private retryDelay(response: Response, attempt: number): number {\n return this.parseRetryAfter(response.headers.get(\"retry-after\")) ?? this.backoff(attempt);\n }\n private parseRetryAfter(value: string | null): number | undefined {\n if (!value) return undefined;\n const seconds = Number(value);\n if (Number.isFinite(seconds)) return Math.max(0, seconds * 1000);\n const date = Date.parse(value);\n return Number.isNaN(date) ? undefined : Math.max(0, date - Date.now());\n }\n private backoff(attempt: number): number {\n const raw = this.options.retry.initialDelayMs * (2 ** attempt);\n return Math.min(raw, this.options.retry.maxDelayMs);\n }\n private async pause(ms: number, signal?: AbortSignal): Promise<void> {\n await new Promise<void>((resolve, reject) => {\n const timer = setTimeout(resolve, ms);\n signal?.addEventListener(\"abort\", () => { clearTimeout(timer); reject(signal.reason); }, { once: true });\n });\n }\n private requestId(response: Response): string | undefined {\n return response.headers.get(\"x-request-id\") ?? undefined;\n }\n private combineSignals(signals: AbortSignal[]): AbortSignal | undefined {\n if (signals.length === 0) return undefined;\n if (signals.length === 1) return signals[0];\n const controller = new AbortController();\n const abort = (signal: AbortSignal) => {\n if (!controller.signal.aborted) controller.abort(signal.reason);\n };\n for (const signal of signals) {\n if (signal.aborted) {\n abort(signal);\n break;\n }\n signal.addEventListener(\"abort\", () => abort(signal), { once: true });\n }\n return controller.signal;\n }\n private wasCancelled(signal?: AbortSignal): boolean {\n return Boolean(signal?.aborted || this.options.signal?.aborted);\n }\n private isTimeout(error: unknown): boolean {\n return error instanceof DOMException && (error.name === \"TimeoutError\" || error.name === \"AbortError\")\n && !this.options.signal?.aborted;\n }\n}\n","import { ValidationError } from \"../errors/index.js\";\nimport { NewsResource } from \"../resources/news.js\";\nimport { Transport } from \"../transport/transport.js\";\nimport type { Fetch, RetryOptions } from \"../transport/types.js\";\n\nexport interface NewTqniaClientOptions {\n /** Optional bearer API key. */\n apiKey?: string;\n /** API origin. @default \"https://newtqnia.com\" */\n baseUrl?: string;\n /** Request timeout in milliseconds. @default 30000 */\n timeout?: number;\n /** Cancels every request made by this client. */\n signal?: AbortSignal;\n /** Custom Fetch-compatible implementation. */\n fetch?: Fetch;\n /** Retry behavior for rate limits, server errors, and temporary network failures. */\n retry?: RetryOptions;\n /** User-Agent header. @default \"newtqnia-node/1.0.0\" */\n userAgent?: string;\n /** Headers included with every request. */\n defaultHeaders?: Record<string, string>;\n}\n\n/** Official NewTqnia API client. */\nexport class NewTqniaClient {\n /** Localized news endpoints. */\n readonly news: NewsResource;\n\n constructor(options: NewTqniaClientOptions = {}) {\n const baseUrl = (options.baseUrl ?? \"https://newtqnia.com\").replace(/\\/+$/, \"\");\n this.validate(baseUrl, options.timeout);\n const transport = new Transport({\n ...(options.apiKey ? { apiKey: options.apiKey } : {}),\n baseUrl,\n timeout: options.timeout ?? 30_000,\n fetch: options.fetch ?? globalThis.fetch.bind(globalThis),\n retry: {\n maxRetries: options.retry?.maxRetries ?? 2,\n initialDelayMs: options.retry?.initialDelayMs ?? 500,\n maxDelayMs: options.retry?.maxDelayMs ?? 8_000,\n },\n userAgent: options.userAgent ?? \"newtqnia-node/1.0.0\",\n headers: options.defaultHeaders ?? {},\n ...(options.signal ? { signal: options.signal } : {}),\n });\n this.news = new NewsResource(transport);\n }\n\n private validate(baseUrl: string, timeout?: number): void {\n let url: URL;\n try { url = new URL(baseUrl); } catch { throw new ValidationError(\"baseUrl must be an absolute HTTP(S) URL.\"); }\n if (![\"http:\", \"https:\"].includes(url.protocol)) throw new ValidationError(\"baseUrl must be an absolute HTTP(S) URL.\");\n const local = [\"localhost\", \"127.0.0.1\", \"[::1]\"].includes(url.hostname);\n if (url.protocol !== \"https:\" && !local) throw new ValidationError(\"baseUrl must use HTTPS except for local development.\");\n if (timeout !== undefined && (!Number.isFinite(timeout) || timeout <= 0)) throw new ValidationError(\"timeout must be a positive number.\");\n }\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,67 @@
1
+ {
2
+ "name": "newtqnia-node",
3
+ "version": "1.0.0",
4
+ "description": "Official Node.js and TypeScript SDK for the NewTqnia technology news API",
5
+ "type": "module",
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
+ "docs/images",
19
+ "README.md",
20
+ "LICENSE"
21
+ ],
22
+ "sideEffects": false,
23
+ "engines": {
24
+ "node": ">=18"
25
+ },
26
+ "scripts": {
27
+ "build": "tsup",
28
+ "dev": "tsup --watch",
29
+ "generate": "openapi-typescript openapi/openapi.yaml -o src/generated/schema.ts",
30
+ "openapi:check": "node scripts/check-openapi.mjs",
31
+ "typecheck": "tsc --noEmit",
32
+ "lint": "eslint .",
33
+ "test": "vitest run --coverage",
34
+ "docs": "typedoc",
35
+ "check": "npm run generate && npm run typecheck && npm run lint && npm test && npm run build"
36
+ },
37
+ "keywords": [
38
+ "newtqnia",
39
+ "news",
40
+ "technology",
41
+ "typescript",
42
+ "sdk"
43
+ ],
44
+ "author": "NewTqnia <hello@newtqnia.com>",
45
+ "license": "MIT",
46
+ "repository": {
47
+ "type": "git",
48
+ "url": "git+https://github.com/newtqnia/newtqnia-node.git"
49
+ },
50
+ "homepage": "https://newtqnia.com/en/developers",
51
+ "bugs": {
52
+ "url": "https://github.com/newtqnia/newtqnia-node/issues"
53
+ },
54
+ "devDependencies": {
55
+ "@eslint/js": "^9.0.0",
56
+ "@types/node": "^22.0.0",
57
+ "@vitest/coverage-v8": "^3.0.0",
58
+ "eslint": "^9.0.0",
59
+ "openapi-typescript": "^7.0.0",
60
+ "tsup": "^8.0.0",
61
+ "typedoc": "^0.28.0",
62
+ "typescript": "^5.7.0",
63
+ "typescript-eslint": "^8.0.0",
64
+ "vitest": "^3.0.0",
65
+ "yaml": "^2.9.0"
66
+ }
67
+ }