nobetci-eczane-api 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 Mehmet Erdoğan (eczaneler.org)
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,226 @@
1
+ # 💊 Nöbetçi Eczane API (SDK)
2
+
3
+ Türkiye ve KKTC genelindeki tüm güncel nöbetçi eczane verilerine, il/ilçe sorgularına ve konum (enlem/boylam) bazlı en yakın eczane lokasyonlarına programatik erişim sağlayan resmi **TypeScript & JavaScript** istemcisi.
4
+
5
+ [![npm version](https://img.shields.io/npm/v/nobetci-eczane-api.svg?style=flat-square&color=dc2626)](https://www.npmjs.com/package/nobetci-eczane-api)
6
+ [![license](https://img.shields.io/npm/l/nobetci-eczane-api.svg?style=flat-square)](https://github.com/mehmeterdogan/nobetci-eczane-api/blob/main/LICENSE)
7
+ [![TypeScript](https://img.shields.io/badge/TypeScript-Ready-blue.svg?style=flat-square)](https://www.typescriptlang.org/)
8
+ [![Zero Dependencies](https://img.shields.io/badge/dependencies-0-success.svg?style=flat-square)](https://bundlephobia.com/package/nobetci-eczane-api)
9
+
10
+ ---
11
+
12
+ ## ✨ Özellikler
13
+
14
+ * 🛡️ **%100 Tip Güvenliği:** TypeScript ile yazılmıştır. Tam otomatik tamamlama ve tip referansı.
15
+ * 🚀 **Sıfır Dış Bağımlılık (0 Dependency):** Yerel `fetch` kullanır; bundle boyutunu şişirmez (< 8 KB).
16
+ * 🌐 **Evrensel (Universal/Isomorphic):** Node.js 18+, Next.js (App Router & Pages Router), React, Vue, Svelte, Cloudflare Workers ve Edge Runtime uyumlu.
17
+ * 🔄 **Hem ESM Hem CommonJS:** `import` ve `require()` desteği.
18
+ * 📍 **Konum Bazlı Mesafe Hesabı:** Harita ve navigasyon entegrasyonu için optimize koordinat ve mesafe desteği.
19
+ * ⚡ **Akıllı Önbellek & Yüksek Hız:** Milisaniye seviyesinde yanıt süreleri.
20
+
21
+ ---
22
+
23
+ ## 📦 Kurulum
24
+
25
+ ```bash
26
+ # npm ile
27
+ npm install nobetci-eczane-api
28
+
29
+ # yarn ile
30
+ yarn add nobetci-eczane-api
31
+
32
+ # pnpm ile
33
+ pnpm add nobetci-eczane-api
34
+ ```
35
+
36
+ ---
37
+
38
+ ## 🔑 Hızlı Başlangıç
39
+
40
+ API anahtarınızı [Eczaneler.ORG](https://eczaneler.org/nobetci-eczane-api) üzerinden veya Telegram ([@mehmeterdogannet](https://t.me/mehmeterdogannet)) üzerinden ücretsiz test anahtarı alarak temin edebilirsiniz.
41
+
42
+ ### TypeScript / ES Modules (Next.js, Vite, Node 18+)
43
+
44
+ ```typescript
45
+ import { EczaneAPI } from 'nobetci-eczane-api';
46
+
47
+ const api = new EczaneAPI({
48
+ apiKey: process.env.ECZANE_API_KEY!
49
+ });
50
+
51
+ async function main() {
52
+ // 1. İstanbul'daki (34) nöbetçi eczaneleri çek
53
+ const { data: istanbulNobetciler } = await api.getSentryByCity(34, { limit: 25 });
54
+ console.log('İstanbul Nöbetçi Eczaneleri:', istanbulNobetciler);
55
+
56
+ // 2. Kadıköy'deki (440) nöbetçi eczaneleri çek
57
+ const { data: kadikoyNobetciler } = await api.getSentryByDistrict(34, 440);
58
+ console.log('Kadıköy Nöbetçi Eczaneleri:', kadikoyNobetciler);
59
+
60
+ // 3. Konumuma en yakın 5 nöbetçi eczane
61
+ const { data: enYakinlar } = await api.getNearby({
62
+ lat: 41.0082,
63
+ lon: 28.9784,
64
+ isSentry: true,
65
+ limit: 5
66
+ });
67
+ console.log('En Yakın Eczaneler:', enYakinlar);
68
+ }
69
+
70
+ main();
71
+ ```
72
+
73
+ ### CommonJS (Node.js)
74
+
75
+ ```javascript
76
+ const { EczaneAPI } = require('nobetci-eczane-api');
77
+
78
+ const api = new EczaneAPI({
79
+ apiKey: 'SENIN_API_ANAHTARIN'
80
+ });
81
+
82
+ api.getSentryByCity('ankara')
83
+ .then(response => console.log(response.data))
84
+ .catch(err => console.error(err));
85
+ ```
86
+
87
+ ---
88
+
89
+ ## 📖 API Metodları Referansı
90
+
91
+ ### 1. `getSentryPharmacies(options?)`
92
+ Türkiye genelinde o gün nöbetçi olan tüm eczaneleri sayfalı listeler.
93
+
94
+ ```typescript
95
+ const result = await api.getSentryPharmacies({ page: 1, limit: 50 });
96
+ console.log(`Toplam ${result.pagination?.total} nöbetçi eczane listelendi.`);
97
+ ```
98
+
99
+ ### 2. `getSentryByCity(city, options?)`
100
+ Belirtilen ildeki nöbetçi eczaneleri listeler. `city` parametresi plaka ID'si (`34`) veya slug (`"istanbul"`) olabilir.
101
+
102
+ ```typescript
103
+ const response = await api.getSentryByCity('izmir', { limit: 30 });
104
+ ```
105
+
106
+ ### 3. `getSentryByDistrict(city, district, options?)`
107
+ Belirtilen ilçedeki nöbetçi eczaneleri listeler.
108
+
109
+ ```typescript
110
+ const response = await api.getSentryByDistrict(34, 440); // 34: İstanbul, 440: Kadıköy
111
+ // Veya slug ile:
112
+ const response2 = await api.getSentryByDistrict('istanbul', 'besiktas');
113
+ ```
114
+
115
+ ### 4. `getNearby(options)`
116
+ Kullanıcının koordinatlarına göre en yakından uzağa doğru eczaneleri listeler.
117
+
118
+ ```typescript
119
+ const response = await api.getNearby({
120
+ lat: 41.0082,
121
+ lon: 28.9784,
122
+ isSentry: true, // Sadece nöbetçiler (opsiyonel, varsayılan: true)
123
+ limit: 10 // Maksimum 50
124
+ });
125
+ ```
126
+
127
+ ### 5. `getCities()` & `getDistricts(cityId)`
128
+ Sistemdeki tüm il ve ilçe ID/Slug listesini getirir.
129
+
130
+ ```typescript
131
+ const { data: iller } = await api.getCities();
132
+ const { data: ilceler } = await api.getDistricts(34);
133
+ ```
134
+
135
+ ### 6. `getAccountInfo()`
136
+ Kalan kullanım, kota, kayıt ve yetki durumunu kontrol eder.
137
+
138
+ ```typescript
139
+ const account = await api.getAccountInfo();
140
+ console.log(`Hesap Durumu: ${account.status}, Kalan Gün: ${account.remaining_days}`);
141
+ ```
142
+
143
+ ### 7. `updateWhitelist(ips)`
144
+ API erişimi için dinamik IP Whitelist tanımını günceller.
145
+
146
+ ```typescript
147
+ await api.updateWhitelist('5.132.*');
148
+ ```
149
+
150
+ ### 8. `getAllPharmacies(options?)` (Premium)
151
+ Türkiye'deki tüm aktif eczane verilerine (+30.000) erişim sağlar.
152
+
153
+ ```typescript
154
+ const result = await api.getAllPharmacies({ page: 1, limit: 50 });
155
+ ```
156
+
157
+ ---
158
+
159
+ ## 🛡️ Hata Yönetimi (Error Handling)
160
+
161
+ SDK, standart HTTP hatalarını yakalayıp özel hata sınıflarına dönüştürür:
162
+
163
+ ```typescript
164
+ import {
165
+ EczaneAPI,
166
+ AuthenticationError,
167
+ RateLimitError,
168
+ InvalidRequestError,
169
+ EczaneAPIError
170
+ } from 'nobetci-eczane-api';
171
+
172
+ try {
173
+ const data = await api.getSentryByCity(34);
174
+ } catch (error) {
175
+ if (error instanceof AuthenticationError) {
176
+ console.error('API anahtarı hatalı veya geçersiz.');
177
+ } else if (error instanceof RateLimitError) {
178
+ console.error(`İstek limiti aşıldı! Sıfırlanma süresi: ${error.resetInSeconds}s`);
179
+ } else if (error instanceof InvalidRequestError) {
180
+ console.error('Geçersiz parametre:', error.message);
181
+ } else if (error instanceof EczaneAPIError) {
182
+ console.error(`API Hatası [${error.status}]:`, error.message);
183
+ }
184
+ }
185
+ ```
186
+
187
+ ---
188
+
189
+ ## ⚡ Next.js App Router Entegrasyon Örneği
190
+
191
+ ```typescript
192
+ // app/api/nobetci-eczaneler/route.ts
193
+ import { NextResponse } from 'next/server';
194
+ import { EczaneAPI } from 'nobetci-eczane-api';
195
+
196
+ const api = new EczaneAPI({
197
+ apiKey: process.env.ECZANE_API_KEY!
198
+ });
199
+
200
+ export async function GET(request: Request) {
201
+ const { searchParams } = new URL(request.url);
202
+ const city = searchParams.get('city') || '34';
203
+
204
+ try {
205
+ const result = await api.getSentryByCity(city);
206
+ return NextResponse.json(result);
207
+ } catch (error: any) {
208
+ return NextResponse.json(
209
+ { error: error.message },
210
+ { status: error.status || 500 }
211
+ );
212
+ }
213
+ }
214
+ ```
215
+
216
+ ---
217
+
218
+ ## 📄 Lisans
219
+
220
+ Bu kütüphane [MIT Lisansı](LICENSE) altında sunulmaktadır.
221
+
222
+ ## 💬 Destek ve İletişim
223
+
224
+ * **Dokümantasyon:** [eczaneler.org/nobetci-eczane-api](https://eczaneler.org/nobetci-eczane-api)
225
+ * **Telegram Destek:** [@mehmeterdogannet](https://t.me/mehmeterdogannet)
226
+ * **Web Sitesi:** [eczaneler.org](https://eczaneler.org)
@@ -0,0 +1,321 @@
1
+ /**
2
+ * Nöbetçi Eczane API TypeScript Types & Interfaces
3
+ */
4
+ /**
5
+ * Coğrafi koordinat nesnesi
6
+ */
7
+ interface Coordinates {
8
+ lat: number;
9
+ lng: number;
10
+ }
11
+ /**
12
+ * Eczane veri modeli (Nöbetçi ve Standart)
13
+ */
14
+ interface Pharmacy {
15
+ id: number;
16
+ city_id: number;
17
+ district_id: number;
18
+ name: string;
19
+ phone: string;
20
+ city: string;
21
+ district: string;
22
+ locality?: string;
23
+ address: string;
24
+ address_description?: string;
25
+ sentry_date?: string;
26
+ is_sentry: boolean;
27
+ workingHours?: string;
28
+ note?: string;
29
+ status?: string;
30
+ coordinates: Coordinates;
31
+ map_link: string;
32
+ /** Konum bazlı aramalarda kullanıcıya olan mesafe (km cinsinden) */
33
+ distance?: number;
34
+ }
35
+ /**
36
+ * Sayfalama (Pagination) meta nesnesi
37
+ */
38
+ interface PaginationMeta {
39
+ total: number;
40
+ per_page: number;
41
+ current_page: number;
42
+ total_pages: number;
43
+ has_more: boolean;
44
+ prev_page: number | null;
45
+ next_page: number | null;
46
+ }
47
+ /**
48
+ * Standart API liste yanıtı
49
+ */
50
+ interface ApiResponse<T = Pharmacy[]> {
51
+ status?: string;
52
+ city_id?: number;
53
+ district_id?: number;
54
+ data: T;
55
+ pagination?: PaginationMeta;
56
+ }
57
+ /**
58
+ * İl Veri Modeli
59
+ */
60
+ interface City {
61
+ id: number;
62
+ name: string;
63
+ slug: string;
64
+ plate: string | number;
65
+ }
66
+ /**
67
+ * İlçe Veri Modeli
68
+ */
69
+ interface District {
70
+ id: number;
71
+ city_id: number;
72
+ name: string;
73
+ slug: string;
74
+ }
75
+ /**
76
+ * API Hesap & Kota Durumu Modeli
77
+ */
78
+ interface AccountInfo {
79
+ api_key: string;
80
+ status: string;
81
+ registered_at?: string;
82
+ expires_at?: string;
83
+ remaining_days?: number;
84
+ allowed_ips?: string[];
85
+ rate_limit?: {
86
+ limit: number;
87
+ remaining: number;
88
+ reset_in_seconds?: number;
89
+ };
90
+ }
91
+ /**
92
+ * IP Whitelist Güncelleme Yanıt Modeli
93
+ */
94
+ interface WhitelistUpdateResponse {
95
+ status: 'success' | 'error';
96
+ message: string;
97
+ allowed_ips?: string[];
98
+ }
99
+ /**
100
+ * Sayfalama ve limit parametreleri
101
+ */
102
+ interface PaginationOptions {
103
+ /** Sayfa numarası (varsayılan: 1) */
104
+ page?: number;
105
+ /** Sayfa başına kayıt sayısı (1 - 50, varsayılan: 25) */
106
+ limit?: number;
107
+ /** Özel AbortSignal (istek iptali için) */
108
+ signal?: AbortSignal;
109
+ }
110
+ /**
111
+ * Konum bazlı en yakın eczane arama parametreleri
112
+ */
113
+ interface NearbyOptions {
114
+ /** Kullanıcı enlemi (Latitude) */
115
+ lat: number;
116
+ /** Kullanıcı boylamı (Longitude) */
117
+ lon: number;
118
+ /** Yalnızca nöbetçi eczaneler mi aransın? (varsayılan: true) */
119
+ isSentry?: boolean;
120
+ /** Maksimum döndürülecek eczane sayısı (varsayılan: 10, maks: 50) */
121
+ limit?: number;
122
+ /** Arama yarıçapı kilometre cinsinden (opsiyonel) */
123
+ radius?: number;
124
+ /** Özel AbortSignal (istek iptali için) */
125
+ signal?: AbortSignal;
126
+ }
127
+ /**
128
+ * SDK Yapılandırma Seçenekleri
129
+ */
130
+ interface EczaneAPIOptions {
131
+ /** Eczaneler.ORG v2 REST API Anahtarı */
132
+ apiKey: string;
133
+ /** Özel API Base URL (Varsayılan: https://eczaneler.org/api/v2) */
134
+ baseUrl?: string;
135
+ /** İstek zaman aşımı süresi milisaniye cinsinden (varsayılan: 10000ms - 10s) */
136
+ timeout?: number;
137
+ /** Özel HTTP istek başlıkları */
138
+ headers?: Record<string, string>;
139
+ }
140
+
141
+ /**
142
+ * Eczaneler.ORG v2 REST API İstemcisi
143
+ */
144
+ declare class EczaneAPI {
145
+ private readonly apiKey;
146
+ private readonly baseUrl;
147
+ private readonly timeout;
148
+ private readonly customHeaders;
149
+ /**
150
+ * Yeni bir EczaneAPI istemcisi örneği oluşturur
151
+ * @param options Yapılandırma ayarları
152
+ *
153
+ * @example
154
+ * ```typescript
155
+ * import { EczaneAPI } from 'nobetci-eczane-api';
156
+ *
157
+ * const api = new EczaneAPI({
158
+ * apiKey: 'SENIN_API_ANAHTARIN'
159
+ * });
160
+ * ```
161
+ */
162
+ constructor(options: EczaneAPIOptions | string);
163
+ /**
164
+ * Türkiye genelinde o gün nöbetçi olan tüm eczaneleri listeler
165
+ *
166
+ * @param options Sayfalama ve limit ayarları (?page=1&limit=25)
167
+ * @returns Sayfalanmış nöbetçi eczane listesi
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const result = await api.getSentryPharmacies({ page: 1, limit: 50 });
172
+ * console.log(`Toplam ${result.pagination?.total} nöbetçi eczane bulundu.`);
173
+ * ```
174
+ */
175
+ getSentryPharmacies(options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
176
+ /**
177
+ * Belirtilen ildeki nöbetçi eczaneleri listeler
178
+ *
179
+ * @param city İl adı / slug'ı (örn: "istanbul", "ankara") VEYA Plaka/Şehir ID (örn: 34, 6)
180
+ * @param options Sayfalama ve limit ayarları
181
+ * @returns İldeki nöbetçi eczane listesi
182
+ *
183
+ * @example
184
+ * ```typescript
185
+ * // Şehir ID (34 - İstanbul) ile nöbetçileri çekme
186
+ * const result = await api.getSentryByCity(34, { limit: 50 });
187
+ *
188
+ * // Slug ile çekme
189
+ * const izmir = await api.getSentryByCity('izmir');
190
+ * ```
191
+ */
192
+ getSentryByCity(city: string | number, options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
193
+ /**
194
+ * Belirtilen ilçedeki nöbetçi eczaneleri listeler
195
+ *
196
+ * @param city İl slug veya ID (örn: "istanbul" veya 34)
197
+ * @param district İlçe slug veya ID (örn: "kadikoy" veya 440)
198
+ * @param options Sayfalama ve limit ayarları
199
+ * @returns İlçedeki nöbetçi eczane listesi
200
+ *
201
+ * @example
202
+ * ```typescript
203
+ * const result = await api.getSentryByDistrict(34, 440);
204
+ * // Veya slug ile:
205
+ * const result2 = await api.getSentryByDistrict('istanbul', 'kadikoy');
206
+ * ```
207
+ */
208
+ getSentryByDistrict(city: string | number, district: string | number, options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
209
+ /**
210
+ * Coğrafi koordinatlara (enlem / boylam) göre en yakın eczaneleri mesafeye göre sıralı getirir
211
+ *
212
+ * @param options Enlem (lat), boylam (lon), isSentry ve limit ayarları
213
+ * @returns En yakından uzağa sıralanmış eczane listesi
214
+ *
215
+ * @example
216
+ * ```typescript
217
+ * const result = await api.getNearby({
218
+ * lat: 41.0082,
219
+ * lon: 28.9784,
220
+ * isSentry: true,
221
+ * limit: 10
222
+ * });
223
+ * ```
224
+ */
225
+ getNearby(options: NearbyOptions): Promise<ApiResponse<Pharmacy[]>>;
226
+ /**
227
+ * Desteklenen tüm il listesini döner
228
+ *
229
+ * @example
230
+ * ```typescript
231
+ * const { data: cities } = await api.getCities();
232
+ * ```
233
+ */
234
+ getCities(signal?: AbortSignal): Promise<ApiResponse<City[]>>;
235
+ /**
236
+ * Belirtilen ile ait ilçe listesini döner
237
+ *
238
+ * @param cityId İl ID (örn: 34 - İstanbul)
239
+ *
240
+ * @example
241
+ * ```typescript
242
+ * const { data: districts } = await api.getDistricts(34);
243
+ * ```
244
+ */
245
+ getDistricts(cityId: number, signal?: AbortSignal): Promise<ApiResponse<District[]>>;
246
+ /**
247
+ * API anahtarınıza ait kalan kullanım, kota ve yetki bilgilerini döner
248
+ *
249
+ * @example
250
+ * ```typescript
251
+ * const account = await api.getAccountInfo();
252
+ * console.log(`Kalan süre: ${account.remaining_days} gün`);
253
+ * ```
254
+ */
255
+ getAccountInfo(signal?: AbortSignal): Promise<AccountInfo>;
256
+ /**
257
+ * Dinamik IP Whitelist tanımını günceller
258
+ *
259
+ * @param ips İzin verilecek IP adresi veya maskesi (örn: "5.132.*" veya "195.175.20.10")
260
+ *
261
+ * @example
262
+ * ```typescript
263
+ * const res = await api.updateWhitelist('5.132.*');
264
+ * ```
265
+ */
266
+ updateWhitelist(ips: string, signal?: AbortSignal): Promise<WhitelistUpdateResponse>;
267
+ /**
268
+ * Türkiye'deki tüm eczaneleri (nöbetçi olmayanlar dahil +30.000) sayfalı olarak döner (Premium Servis)
269
+ *
270
+ * @param options Sayfalama ve limit ayarları
271
+ *
272
+ * @example
273
+ * ```typescript
274
+ * const result = await api.getAllPharmacies({ page: 1, limit: 50 });
275
+ * ```
276
+ */
277
+ getAllPharmacies(options?: PaginationOptions): Promise<ApiResponse<Pharmacy[]>>;
278
+ /**
279
+ * Merkezi HTTP İstek Yürütücüsü (Native fetch)
280
+ */
281
+ private request;
282
+ /**
283
+ * HTTP Hata Durumlarını Özelleştirilmiş Hata Sınıflarına Çevirir
284
+ */
285
+ private handleHttpError;
286
+ /**
287
+ * Sayfalama query string oluşturucu
288
+ */
289
+ private buildPaginationQuery;
290
+ }
291
+
292
+ /**
293
+ * Nöbetçi Eczane API Özel Hata Sınıfları
294
+ */
295
+ declare class EczaneAPIError extends Error {
296
+ readonly status: number;
297
+ readonly code?: string;
298
+ readonly details?: unknown;
299
+ constructor(message: string, status?: number, code?: string, details?: unknown);
300
+ }
301
+ declare class AuthenticationError extends EczaneAPIError {
302
+ constructor(message?: string);
303
+ }
304
+ declare class ForbiddenError extends EczaneAPIError {
305
+ constructor(message?: string);
306
+ }
307
+ declare class NotFoundError extends EczaneAPIError {
308
+ constructor(message?: string);
309
+ }
310
+ declare class RateLimitError extends EczaneAPIError {
311
+ readonly resetInSeconds?: number;
312
+ constructor(message?: string, resetInSeconds?: number);
313
+ }
314
+ declare class InvalidRequestError extends EczaneAPIError {
315
+ constructor(message?: string, details?: unknown);
316
+ }
317
+ declare class TimeoutError extends EczaneAPIError {
318
+ constructor(message?: string);
319
+ }
320
+
321
+ export { type AccountInfo, type ApiResponse, AuthenticationError, type City, type Coordinates, type District, EczaneAPI, EczaneAPIError, type EczaneAPIOptions, ForbiddenError, InvalidRequestError, type NearbyOptions, NotFoundError, type PaginationMeta, type PaginationOptions, type Pharmacy, RateLimitError, TimeoutError, type WhitelistUpdateResponse, EczaneAPI as default };