@safercity/sdk-core 0.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/README.md ADDED
@@ -0,0 +1,132 @@
1
+ # @safercity/sdk-core
2
+
3
+ Core utilities for SaferCity SDK including fetch abstraction, streaming support, and authentication helpers.
4
+
5
+ > **Note**: Most users should use `@safercity/sdk` instead. This package contains low-level utilities used internally.
6
+
7
+ ## Installation
8
+
9
+ ```bash
10
+ npm install @safercity/sdk-core
11
+ ```
12
+
13
+ ## Features
14
+
15
+ - **Type definitions** - Common types for API requests/responses
16
+ - **Authentication utilities** - Token management, JWT parsing
17
+ - **Streaming/SSE support** - Cross-platform Server-Sent Events
18
+ - **Base HTTP client** - Fetch wrapper with timeout and error handling
19
+
20
+ ## Usage
21
+
22
+ ### Stream Adapters
23
+
24
+ ```typescript
25
+ import {
26
+ WebStreamAdapter,
27
+ FetchStreamAdapter,
28
+ createStreamAdapter
29
+ } from '@safercity/sdk-core';
30
+
31
+ // Auto-detect best adapter for environment
32
+ const adapter = createStreamAdapter();
33
+
34
+ // Or use specific adapter
35
+ const webAdapter = new WebStreamAdapter(); // Browser with EventSource
36
+ const fetchAdapter = new FetchStreamAdapter(fetch); // Node.js / React Native
37
+
38
+ // Stream events
39
+ const stream = adapter.createEventSource('https://api.example.com/stream', {
40
+ headers: { Authorization: 'Bearer token' },
41
+ });
42
+
43
+ for await (const event of stream) {
44
+ console.log(event.data);
45
+ }
46
+ ```
47
+
48
+ ### Authentication Helpers
49
+
50
+ ```typescript
51
+ import {
52
+ createAuthHeader,
53
+ parseJwtPayload,
54
+ getJwtExpiration,
55
+ isTokenExpired,
56
+ } from '@safercity/sdk-core';
57
+
58
+ // Create auth header
59
+ const header = createAuthHeader('my-token'); // "Bearer my-token"
60
+
61
+ // Parse JWT (without verification)
62
+ const payload = parseJwtPayload(token);
63
+ console.log(payload?.sub); // User ID
64
+
65
+ // Check expiration
66
+ const expiresAt = getJwtExpiration(token);
67
+ if (isTokenExpired(expiresAt)) {
68
+ // Token expired, refresh it
69
+ }
70
+ ```
71
+
72
+ ### Base Client
73
+
74
+ ```typescript
75
+ import { BaseClient } from '@safercity/sdk-core';
76
+
77
+ const client = new BaseClient({
78
+ baseUrl: 'https://api.example.com',
79
+ token: 'my-token',
80
+ timeout: 30000,
81
+ });
82
+
83
+ // Make requests
84
+ const response = await client.get('/users');
85
+ const created = await client.post('/users', { name: 'John' });
86
+ ```
87
+
88
+ ### Error Handling
89
+
90
+ ```typescript
91
+ import { SaferCityApiError } from '@safercity/sdk-core';
92
+
93
+ try {
94
+ await client.get('/not-found');
95
+ } catch (error) {
96
+ if (error instanceof SaferCityApiError) {
97
+ console.log(error.error); // "not_found"
98
+ console.log(error.message); // "Resource not found"
99
+ console.log(error.status); // 404
100
+ }
101
+ }
102
+ ```
103
+
104
+ ## Types
105
+
106
+ ### SaferCityConfig
107
+
108
+ ```typescript
109
+ interface SaferCityConfig {
110
+ baseUrl: string;
111
+ token?: string;
112
+ tenantId?: string;
113
+ fetch?: typeof fetch;
114
+ timeout?: number;
115
+ headers?: Record<string, string>;
116
+ }
117
+ ```
118
+
119
+ ### ServerSentEvent
120
+
121
+ ```typescript
122
+ interface ServerSentEvent {
123
+ id?: string;
124
+ event?: string;
125
+ data: string;
126
+ retry?: number;
127
+ }
128
+ ```
129
+
130
+ ## License
131
+
132
+ MIT
package/dist/index.cjs ADDED
@@ -0,0 +1,445 @@
1
+ 'use strict';
2
+
3
+ // src/types.ts
4
+ var SaferCityApiError = class _SaferCityApiError extends Error {
5
+ constructor(error, message, status, details) {
6
+ super(message);
7
+ this.error = error;
8
+ this.status = status;
9
+ this.details = details;
10
+ this.name = "SaferCityApiError";
11
+ }
12
+ static fromResponse(response, body) {
13
+ const errorBody = body;
14
+ return new _SaferCityApiError(
15
+ errorBody.error ?? "unknown_error",
16
+ errorBody.message ?? `Request failed with status ${response.status}`,
17
+ response.status,
18
+ errorBody.details
19
+ );
20
+ }
21
+ };
22
+
23
+ // src/auth.ts
24
+ var MemoryTokenStorage = class {
25
+ tokens = null;
26
+ get() {
27
+ return this.tokens;
28
+ }
29
+ set(tokens) {
30
+ this.tokens = tokens;
31
+ }
32
+ clear() {
33
+ this.tokens = null;
34
+ }
35
+ };
36
+ function isTokenExpired(expiresAt, bufferMs = 6e4) {
37
+ if (!expiresAt) return false;
38
+ return Date.now() + bufferMs >= expiresAt;
39
+ }
40
+ function createAuthHeader(token, type = "Bearer") {
41
+ return `${type} ${token}`;
42
+ }
43
+ function parseJwtPayload(token) {
44
+ try {
45
+ const parts = token.split(".");
46
+ if (parts.length !== 3) return null;
47
+ const payload = parts[1];
48
+ const decoded = atob(payload.replace(/-/g, "+").replace(/_/g, "/"));
49
+ return JSON.parse(decoded);
50
+ } catch {
51
+ return null;
52
+ }
53
+ }
54
+ function getJwtExpiration(token) {
55
+ const payload = parseJwtPayload(token);
56
+ if (!payload?.exp) return null;
57
+ return payload.exp * 1e3;
58
+ }
59
+
60
+ // src/streaming.ts
61
+ function parseSSE(buffer) {
62
+ const events = [];
63
+ const lines = buffer.split("\n");
64
+ let currentEvent = {};
65
+ let dataLines = [];
66
+ let remaining = "";
67
+ for (let i = 0; i < lines.length; i++) {
68
+ const line = lines[i];
69
+ if (i === lines.length - 1 && line !== "" && !buffer.endsWith("\n")) {
70
+ remaining = line;
71
+ break;
72
+ }
73
+ if (line === "") {
74
+ if (dataLines.length > 0 || currentEvent.event || currentEvent.id) {
75
+ events.push({
76
+ ...currentEvent,
77
+ data: dataLines.join("\n")
78
+ });
79
+ }
80
+ currentEvent = {};
81
+ dataLines = [];
82
+ continue;
83
+ }
84
+ const colonIndex = line.indexOf(":");
85
+ if (colonIndex === 0) {
86
+ continue;
87
+ }
88
+ let field;
89
+ let value;
90
+ if (colonIndex === -1) {
91
+ field = line;
92
+ value = "";
93
+ } else {
94
+ field = line.slice(0, colonIndex);
95
+ value = line.slice(colonIndex + 1).replace(/^ /, "");
96
+ }
97
+ switch (field) {
98
+ case "event":
99
+ currentEvent.event = value;
100
+ break;
101
+ case "data":
102
+ dataLines.push(value);
103
+ break;
104
+ case "id":
105
+ currentEvent.id = value;
106
+ break;
107
+ case "retry":
108
+ const retry = parseInt(value, 10);
109
+ if (!isNaN(retry)) {
110
+ currentEvent.retry = retry;
111
+ }
112
+ break;
113
+ }
114
+ }
115
+ return { parsed: events, remaining };
116
+ }
117
+ var WebStreamAdapter = class {
118
+ supportsNativeSSE() {
119
+ return typeof EventSource !== "undefined";
120
+ }
121
+ createEventSource(url, options) {
122
+ return {
123
+ [Symbol.asyncIterator]() {
124
+ let eventSource = null;
125
+ let resolveNext = null;
126
+ let rejectNext = null;
127
+ const eventQueue = [];
128
+ let done = false;
129
+ let error = null;
130
+ const cleanup = () => {
131
+ if (eventSource) {
132
+ eventSource.close();
133
+ eventSource = null;
134
+ }
135
+ done = true;
136
+ };
137
+ options?.signal?.addEventListener("abort", () => {
138
+ cleanup();
139
+ if (rejectNext) {
140
+ rejectNext(new Error("Aborted"));
141
+ }
142
+ });
143
+ eventSource = new EventSource(url);
144
+ eventSource.onopen = () => {
145
+ options?.onOpen?.();
146
+ };
147
+ eventSource.onmessage = (event) => {
148
+ const sseEvent = {
149
+ id: event.lastEventId || void 0,
150
+ data: event.data
151
+ };
152
+ if (resolveNext) {
153
+ resolveNext({ value: sseEvent, done: false });
154
+ resolveNext = null;
155
+ rejectNext = null;
156
+ } else {
157
+ eventQueue.push(sseEvent);
158
+ }
159
+ };
160
+ eventSource.onerror = (e) => {
161
+ const err = new Error("EventSource error");
162
+ error = err;
163
+ options?.onError?.(err);
164
+ cleanup();
165
+ if (rejectNext) {
166
+ rejectNext(err);
167
+ resolveNext = null;
168
+ rejectNext = null;
169
+ }
170
+ };
171
+ return {
172
+ async next() {
173
+ if (error) {
174
+ throw error;
175
+ }
176
+ if (eventQueue.length > 0) {
177
+ return { value: eventQueue.shift(), done: false };
178
+ }
179
+ if (done) {
180
+ return { value: void 0, done: true };
181
+ }
182
+ return new Promise((resolve, reject) => {
183
+ resolveNext = resolve;
184
+ rejectNext = reject;
185
+ });
186
+ },
187
+ async return() {
188
+ cleanup();
189
+ return { value: void 0, done: true };
190
+ },
191
+ async throw(e) {
192
+ cleanup();
193
+ throw e;
194
+ }
195
+ };
196
+ }
197
+ };
198
+ }
199
+ };
200
+ var FetchStreamAdapter = class {
201
+ constructor(fetchFn = fetch) {
202
+ this.fetchFn = fetchFn;
203
+ }
204
+ supportsNativeSSE() {
205
+ return false;
206
+ }
207
+ createEventSource(url, options) {
208
+ const fetchFn = this.fetchFn;
209
+ return {
210
+ [Symbol.asyncIterator]() {
211
+ let reader = null;
212
+ let buffer = "";
213
+ let done = false;
214
+ const eventQueue = [];
215
+ const cleanup = () => {
216
+ if (reader) {
217
+ reader.cancel().catch(() => {
218
+ });
219
+ reader = null;
220
+ }
221
+ done = true;
222
+ };
223
+ options?.signal?.addEventListener("abort", cleanup);
224
+ const fetchPromise = fetchFn(url, {
225
+ method: "GET",
226
+ headers: {
227
+ "Accept": "text/event-stream",
228
+ "Cache-Control": "no-cache",
229
+ ...options?.headers
230
+ },
231
+ signal: options?.signal
232
+ }).then((response) => {
233
+ if (!response.ok) {
234
+ throw new Error(`HTTP ${response.status}: ${response.statusText}`);
235
+ }
236
+ if (!response.body) {
237
+ throw new Error("Response body is not available");
238
+ }
239
+ options?.onOpen?.();
240
+ reader = response.body.getReader();
241
+ return reader;
242
+ }).catch((err) => {
243
+ options?.onError?.(err);
244
+ throw err;
245
+ });
246
+ const decoder = new TextDecoder();
247
+ return {
248
+ async next() {
249
+ if (eventQueue.length > 0) {
250
+ return { value: eventQueue.shift(), done: false };
251
+ }
252
+ if (done) {
253
+ return { value: void 0, done: true };
254
+ }
255
+ if (!reader) {
256
+ reader = await fetchPromise;
257
+ }
258
+ while (eventQueue.length === 0 && !done) {
259
+ const { value, done: readerDone } = await reader.read();
260
+ if (readerDone) {
261
+ done = true;
262
+ if (buffer.trim()) {
263
+ const { parsed: parsed2 } = parseSSE(buffer + "\n\n");
264
+ eventQueue.push(...parsed2);
265
+ }
266
+ break;
267
+ }
268
+ buffer += decoder.decode(value, { stream: true });
269
+ const { parsed, remaining } = parseSSE(buffer);
270
+ buffer = remaining;
271
+ eventQueue.push(...parsed);
272
+ }
273
+ if (eventQueue.length > 0) {
274
+ return { value: eventQueue.shift(), done: false };
275
+ }
276
+ return { value: void 0, done: true };
277
+ },
278
+ async return() {
279
+ cleanup();
280
+ return { value: void 0, done: true };
281
+ },
282
+ async throw(e) {
283
+ cleanup();
284
+ throw e;
285
+ }
286
+ };
287
+ }
288
+ };
289
+ }
290
+ };
291
+ function createStreamAdapter(fetchFn) {
292
+ if (typeof EventSource !== "undefined") {
293
+ return new WebStreamAdapter();
294
+ }
295
+ return new FetchStreamAdapter(fetchFn ?? fetch);
296
+ }
297
+
298
+ // src/client.ts
299
+ function createHeaders(config, options) {
300
+ const headers = {
301
+ "Content-Type": "application/json",
302
+ "Accept": "application/json",
303
+ ...config.headers,
304
+ ...options?.headers
305
+ };
306
+ if (config.token) {
307
+ headers["Authorization"] = createAuthHeader(config.token);
308
+ }
309
+ if (config.tenantId) {
310
+ headers["X-Tenant-ID"] = config.tenantId;
311
+ }
312
+ return headers;
313
+ }
314
+ function createTimeoutSignal(timeout, existingSignal) {
315
+ const controller = new AbortController();
316
+ const timeoutId = setTimeout(() => {
317
+ controller.abort(new Error(`Request timeout after ${timeout}ms`));
318
+ }, timeout);
319
+ if (existingSignal) {
320
+ existingSignal.addEventListener("abort", () => {
321
+ clearTimeout(timeoutId);
322
+ controller.abort(existingSignal.reason);
323
+ });
324
+ }
325
+ return controller.signal;
326
+ }
327
+ var BaseClient = class {
328
+ config;
329
+ fetchFn;
330
+ constructor(options) {
331
+ this.config = {
332
+ timeout: 3e4,
333
+ ...options,
334
+ baseUrl: options.baseUrl.replace(/\/$/, "")
335
+ // Remove trailing slash
336
+ };
337
+ this.fetchFn = options.fetch ?? fetch;
338
+ }
339
+ /**
340
+ * Update authentication token
341
+ */
342
+ setToken(token) {
343
+ this.config.token = token;
344
+ }
345
+ /**
346
+ * Update tenant ID
347
+ */
348
+ setTenantId(tenantId) {
349
+ this.config.tenantId = tenantId;
350
+ }
351
+ /**
352
+ * Get current configuration (read-only)
353
+ */
354
+ getConfig() {
355
+ return { ...this.config };
356
+ }
357
+ /**
358
+ * Build full URL from path
359
+ */
360
+ buildUrl(path, query) {
361
+ const url = new URL(path.startsWith("/") ? path : `/${path}`, this.config.baseUrl);
362
+ if (query) {
363
+ Object.entries(query).forEach(([key, value]) => {
364
+ if (value !== void 0) {
365
+ url.searchParams.set(key, String(value));
366
+ }
367
+ });
368
+ }
369
+ return url.toString();
370
+ }
371
+ /**
372
+ * Make HTTP request
373
+ */
374
+ async request(method, path, options) {
375
+ const url = this.buildUrl(path, options?.query);
376
+ const headers = createHeaders(this.config, options);
377
+ const timeout = options?.timeout ?? this.config.timeout;
378
+ const signal = createTimeoutSignal(timeout, options?.signal);
379
+ const response = await this.fetchFn(url, {
380
+ method,
381
+ headers,
382
+ body: options?.body ? JSON.stringify(options.body) : void 0,
383
+ signal
384
+ });
385
+ let data;
386
+ const contentType = response.headers.get("content-type");
387
+ if (contentType?.includes("application/json")) {
388
+ data = await response.json();
389
+ } else {
390
+ data = await response.text();
391
+ }
392
+ if (!response.ok) {
393
+ throw SaferCityApiError.fromResponse(response, data);
394
+ }
395
+ return {
396
+ data,
397
+ status: response.status,
398
+ headers: response.headers
399
+ };
400
+ }
401
+ /**
402
+ * GET request
403
+ */
404
+ async get(path, options) {
405
+ return this.request("GET", path, options);
406
+ }
407
+ /**
408
+ * POST request
409
+ */
410
+ async post(path, body, options) {
411
+ return this.request("POST", path, { ...options, body });
412
+ }
413
+ /**
414
+ * PUT request
415
+ */
416
+ async put(path, body, options) {
417
+ return this.request("PUT", path, { ...options, body });
418
+ }
419
+ /**
420
+ * PATCH request
421
+ */
422
+ async patch(path, body, options) {
423
+ return this.request("PATCH", path, { ...options, body });
424
+ }
425
+ /**
426
+ * DELETE request
427
+ */
428
+ async delete(path, options) {
429
+ return this.request("DELETE", path, options);
430
+ }
431
+ };
432
+
433
+ exports.BaseClient = BaseClient;
434
+ exports.FetchStreamAdapter = FetchStreamAdapter;
435
+ exports.MemoryTokenStorage = MemoryTokenStorage;
436
+ exports.SaferCityApiError = SaferCityApiError;
437
+ exports.WebStreamAdapter = WebStreamAdapter;
438
+ exports.createAuthHeader = createAuthHeader;
439
+ exports.createStreamAdapter = createStreamAdapter;
440
+ exports.getJwtExpiration = getJwtExpiration;
441
+ exports.isTokenExpired = isTokenExpired;
442
+ exports.parseJwtPayload = parseJwtPayload;
443
+ exports.parseSSE = parseSSE;
444
+ //# sourceMappingURL=index.cjs.map
445
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/types.ts","../src/auth.ts","../src/streaming.ts","../src/client.ts"],"names":["parsed"],"mappings":"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","file":"index.cjs","sourcesContent":["/**\n * Common types for SaferCity SDK\n */\n\nexport interface SaferCityConfig {\n /**\n * Base URL for the SaferCity API\n * @example \"https://api.safercity.com\"\n */\n baseUrl: string;\n\n /**\n * Authentication token (JWT)\n */\n token?: string;\n\n /**\n * Tenant ID for multi-tenant operations\n */\n tenantId?: string;\n\n /**\n * Custom fetch implementation (useful for React Native)\n */\n fetch?: typeof fetch;\n\n /**\n * Request timeout in milliseconds\n * @default 30000\n */\n timeout?: number;\n\n /**\n * Custom headers to include in all requests\n */\n headers?: Record<string, string>;\n}\n\nexport interface RequestOptions {\n /**\n * Additional headers for this request\n */\n headers?: Record<string, string>;\n\n /**\n * Request timeout override\n */\n timeout?: number;\n\n /**\n * Abort signal for cancellation\n */\n signal?: AbortSignal;\n}\n\nexport interface ApiResponse<T> {\n data: T;\n status: number;\n headers: Headers;\n}\n\nexport interface ApiError {\n error: string;\n message: string;\n status: number;\n details?: unknown;\n}\n\nexport class SaferCityApiError extends Error {\n constructor(\n public readonly error: string,\n message: string,\n public readonly status: number,\n public readonly details?: unknown\n ) {\n super(message);\n this.name = 'SaferCityApiError';\n }\n\n static fromResponse(response: Response, body: unknown): SaferCityApiError {\n const errorBody = body as Partial<ApiError>;\n return new SaferCityApiError(\n errorBody.error ?? 'unknown_error',\n errorBody.message ?? `Request failed with status ${response.status}`,\n response.status,\n errorBody.details\n );\n }\n}\n\n/**\n * Server-Sent Event structure\n */\nexport interface ServerSentEvent {\n id?: string;\n event?: string;\n data: string;\n retry?: number;\n}\n\n/**\n * Options for SSE connections\n */\nexport interface EventSourceOptions {\n headers?: Record<string, string>;\n signal?: AbortSignal;\n onOpen?: () => void;\n onError?: (error: Error) => void;\n}\n","/**\n * Authentication utilities for SaferCity SDK\n */\n\nexport interface AuthTokens {\n accessToken: string;\n refreshToken?: string;\n expiresAt?: number;\n tokenType: string;\n}\n\nexport interface TokenStorage {\n get(): AuthTokens | null;\n set(tokens: AuthTokens): void;\n clear(): void;\n}\n\n/**\n * In-memory token storage (default)\n */\nexport class MemoryTokenStorage implements TokenStorage {\n private tokens: AuthTokens | null = null;\n\n get(): AuthTokens | null {\n return this.tokens;\n }\n\n set(tokens: AuthTokens): void {\n this.tokens = tokens;\n }\n\n clear(): void {\n this.tokens = null;\n }\n}\n\n/**\n * Check if a token is expired (with buffer)\n */\nexport function isTokenExpired(expiresAt: number | undefined, bufferMs = 60000): boolean {\n if (!expiresAt) return false;\n return Date.now() + bufferMs >= expiresAt;\n}\n\n/**\n * Create Authorization header value\n */\nexport function createAuthHeader(token: string, type = 'Bearer'): string {\n return `${type} ${token}`;\n}\n\n/**\n * Parse JWT payload (without verification)\n * Only use for client-side display, not security decisions\n */\nexport function parseJwtPayload<T = Record<string, unknown>>(token: string): T | null {\n try {\n const parts = token.split('.');\n if (parts.length !== 3) return null;\n \n const payload = parts[1];\n const decoded = atob(payload.replace(/-/g, '+').replace(/_/g, '/'));\n return JSON.parse(decoded) as T;\n } catch {\n return null;\n }\n}\n\n/**\n * Extract expiration from JWT\n */\nexport function getJwtExpiration(token: string): number | null {\n const payload = parseJwtPayload<{ exp?: number }>(token);\n if (!payload?.exp) return null;\n return payload.exp * 1000; // Convert to milliseconds\n}\n\nexport interface SaferCityJwtPayload {\n sub?: string;\n tenantId?: string;\n environment?: string;\n scopes?: string[];\n iat?: number;\n exp?: number;\n}\n","/**\n * Cross-platform SSE/Streaming support for SaferCity SDK\n * \n * Provides adapters for:\n * - Web browsers (native EventSource)\n * - React Native (fetch-based streaming via expo-fetch or polyfill)\n * - Node.js (fetch-based streaming)\n */\n\nimport type { ServerSentEvent, EventSourceOptions } from './types';\n\n/**\n * Interface for stream adapters\n */\nexport interface StreamAdapter {\n /**\n * Create an async iterable for SSE events\n */\n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent>;\n \n /**\n * Check if native SSE is supported\n */\n supportsNativeSSE(): boolean;\n}\n\n/**\n * Parse SSE data from a text buffer\n */\nexport function parseSSE(buffer: string): { parsed: ServerSentEvent[]; remaining: string } {\n const events: ServerSentEvent[] = [];\n const lines = buffer.split('\\n');\n \n let currentEvent: Partial<ServerSentEvent> = {};\n let dataLines: string[] = [];\n let remaining = '';\n \n for (let i = 0; i < lines.length; i++) {\n const line = lines[i];\n \n // Check if this might be an incomplete line at the end\n if (i === lines.length - 1 && line !== '' && !buffer.endsWith('\\n')) {\n remaining = line;\n break;\n }\n \n if (line === '') {\n // Empty line = event boundary\n if (dataLines.length > 0 || currentEvent.event || currentEvent.id) {\n events.push({\n ...currentEvent,\n data: dataLines.join('\\n'),\n } as ServerSentEvent);\n }\n currentEvent = {};\n dataLines = [];\n continue;\n }\n \n const colonIndex = line.indexOf(':');\n \n // Comment line (starts with :)\n if (colonIndex === 0) {\n continue;\n }\n \n let field: string;\n let value: string;\n \n if (colonIndex === -1) {\n field = line;\n value = '';\n } else {\n field = line.slice(0, colonIndex);\n // Skip the optional space after colon\n value = line.slice(colonIndex + 1).replace(/^ /, '');\n }\n \n switch (field) {\n case 'event':\n currentEvent.event = value;\n break;\n case 'data':\n dataLines.push(value);\n break;\n case 'id':\n currentEvent.id = value;\n break;\n case 'retry':\n const retry = parseInt(value, 10);\n if (!isNaN(retry)) {\n currentEvent.retry = retry;\n }\n break;\n }\n }\n \n return { parsed: events, remaining };\n}\n\n/**\n * Web/Browser stream adapter using native EventSource\n */\nexport class WebStreamAdapter implements StreamAdapter {\n supportsNativeSSE(): boolean {\n return typeof EventSource !== 'undefined';\n }\n \n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent> {\n const adapter = this;\n \n return {\n [Symbol.asyncIterator]() {\n let eventSource: EventSource | null = null;\n let resolveNext: ((value: IteratorResult<ServerSentEvent>) => void) | null = null;\n let rejectNext: ((error: Error) => void) | null = null;\n const eventQueue: ServerSentEvent[] = [];\n let done = false;\n let error: Error | null = null;\n \n const cleanup = () => {\n if (eventSource) {\n eventSource.close();\n eventSource = null;\n }\n done = true;\n };\n \n // Handle abort signal\n options?.signal?.addEventListener('abort', () => {\n cleanup();\n if (rejectNext) {\n rejectNext(new Error('Aborted'));\n }\n });\n \n // Initialize EventSource\n eventSource = new EventSource(url);\n \n eventSource.onopen = () => {\n options?.onOpen?.();\n };\n \n eventSource.onmessage = (event) => {\n const sseEvent: ServerSentEvent = {\n id: event.lastEventId || undefined,\n data: event.data,\n };\n \n if (resolveNext) {\n resolveNext({ value: sseEvent, done: false });\n resolveNext = null;\n rejectNext = null;\n } else {\n eventQueue.push(sseEvent);\n }\n };\n \n eventSource.onerror = (e) => {\n const err = new Error('EventSource error');\n error = err;\n options?.onError?.(err);\n cleanup();\n \n if (rejectNext) {\n rejectNext(err);\n resolveNext = null;\n rejectNext = null;\n }\n };\n \n return {\n async next(): Promise<IteratorResult<ServerSentEvent>> {\n if (error) {\n throw error;\n }\n \n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n if (done) {\n return { value: undefined as unknown as ServerSentEvent, done: true };\n }\n \n return new Promise((resolve, reject) => {\n resolveNext = resolve;\n rejectNext = reject;\n });\n },\n \n async return(): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async throw(e: Error): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n throw e;\n },\n };\n },\n };\n }\n}\n\n/**\n * Fetch-based stream adapter for React Native and Node.js\n * Uses ReadableStream to parse SSE from fetch response\n */\nexport class FetchStreamAdapter implements StreamAdapter {\n constructor(private fetchFn: typeof fetch = fetch) {}\n \n supportsNativeSSE(): boolean {\n return false;\n }\n \n createEventSource(url: string, options?: EventSourceOptions): AsyncIterable<ServerSentEvent> {\n const fetchFn = this.fetchFn;\n \n return {\n [Symbol.asyncIterator]() {\n let reader: ReadableStreamDefaultReader<Uint8Array> | null = null;\n let buffer = '';\n let done = false;\n const eventQueue: ServerSentEvent[] = [];\n \n const cleanup = () => {\n if (reader) {\n reader.cancel().catch(() => {});\n reader = null;\n }\n done = true;\n };\n \n // Handle abort signal\n options?.signal?.addEventListener('abort', cleanup);\n \n // Start fetch\n const fetchPromise = fetchFn(url, {\n method: 'GET',\n headers: {\n 'Accept': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n ...options?.headers,\n },\n signal: options?.signal,\n }).then((response) => {\n if (!response.ok) {\n throw new Error(`HTTP ${response.status}: ${response.statusText}`);\n }\n \n if (!response.body) {\n throw new Error('Response body is not available');\n }\n \n options?.onOpen?.();\n reader = response.body.getReader();\n return reader;\n }).catch((err) => {\n options?.onError?.(err);\n throw err;\n });\n \n const decoder = new TextDecoder();\n \n return {\n async next(): Promise<IteratorResult<ServerSentEvent>> {\n // Return queued events first\n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n if (done) {\n return { value: undefined as unknown as ServerSentEvent, done: true };\n }\n \n // Ensure reader is initialized\n if (!reader) {\n reader = await fetchPromise;\n }\n \n // Read until we have at least one event\n while (eventQueue.length === 0 && !done) {\n const { value, done: readerDone } = await reader.read();\n \n if (readerDone) {\n done = true;\n // Parse any remaining buffer\n if (buffer.trim()) {\n const { parsed } = parseSSE(buffer + '\\n\\n');\n eventQueue.push(...parsed);\n }\n break;\n }\n \n buffer += decoder.decode(value, { stream: true });\n const { parsed, remaining } = parseSSE(buffer);\n buffer = remaining;\n eventQueue.push(...parsed);\n }\n \n if (eventQueue.length > 0) {\n return { value: eventQueue.shift()!, done: false };\n }\n \n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async return(): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n return { value: undefined as unknown as ServerSentEvent, done: true };\n },\n \n async throw(e: Error): Promise<IteratorResult<ServerSentEvent>> {\n cleanup();\n throw e;\n },\n };\n },\n };\n }\n}\n\n/**\n * Auto-detect and create the best stream adapter for the current environment\n */\nexport function createStreamAdapter(fetchFn?: typeof fetch): StreamAdapter {\n // Check for native EventSource (browser)\n if (typeof EventSource !== 'undefined') {\n return new WebStreamAdapter();\n }\n \n // Fall back to fetch-based adapter\n return new FetchStreamAdapter(fetchFn ?? fetch);\n}\n","/**\n * Base HTTP client for SaferCity SDK\n */\n\nimport type { SaferCityConfig, RequestOptions, ApiResponse } from './types';\nimport { SaferCityApiError } from './types';\nimport { createAuthHeader } from './auth';\n\nexport interface BaseClientOptions extends SaferCityConfig {}\n\n/**\n * Create base headers for requests\n */\nfunction createHeaders(\n config: SaferCityConfig,\n options?: RequestOptions\n): Record<string, string> {\n const headers: Record<string, string> = {\n 'Content-Type': 'application/json',\n 'Accept': 'application/json',\n ...config.headers,\n ...options?.headers,\n };\n\n if (config.token) {\n headers['Authorization'] = createAuthHeader(config.token);\n }\n\n if (config.tenantId) {\n headers['X-Tenant-ID'] = config.tenantId;\n }\n\n return headers;\n}\n\n/**\n * Create a timeout signal\n */\nfunction createTimeoutSignal(timeout: number, existingSignal?: AbortSignal): AbortSignal {\n const controller = new AbortController();\n \n const timeoutId = setTimeout(() => {\n controller.abort(new Error(`Request timeout after ${timeout}ms`));\n }, timeout);\n \n // If there's an existing signal, abort when it aborts\n if (existingSignal) {\n existingSignal.addEventListener('abort', () => {\n clearTimeout(timeoutId);\n controller.abort(existingSignal.reason);\n });\n }\n \n return controller.signal;\n}\n\n/**\n * Base HTTP client with common functionality\n */\nexport class BaseClient {\n protected config: Required<Pick<SaferCityConfig, 'baseUrl' | 'timeout'>> & SaferCityConfig;\n protected fetchFn: typeof fetch;\n\n constructor(options: BaseClientOptions) {\n this.config = {\n timeout: 30000,\n ...options,\n baseUrl: options.baseUrl.replace(/\\/$/, ''), // Remove trailing slash\n };\n this.fetchFn = options.fetch ?? fetch;\n }\n\n /**\n * Update authentication token\n */\n setToken(token: string | undefined): void {\n this.config.token = token;\n }\n\n /**\n * Update tenant ID\n */\n setTenantId(tenantId: string | undefined): void {\n this.config.tenantId = tenantId;\n }\n\n /**\n * Get current configuration (read-only)\n */\n getConfig(): Readonly<SaferCityConfig> {\n return { ...this.config };\n }\n\n /**\n * Build full URL from path\n */\n protected buildUrl(path: string, query?: Record<string, string | number | boolean | undefined>): string {\n const url = new URL(path.startsWith('/') ? path : `/${path}`, this.config.baseUrl);\n \n if (query) {\n Object.entries(query).forEach(([key, value]) => {\n if (value !== undefined) {\n url.searchParams.set(key, String(value));\n }\n });\n }\n \n return url.toString();\n }\n\n /**\n * Make HTTP request\n */\n protected async request<T>(\n method: string,\n path: string,\n options?: RequestOptions & {\n body?: unknown;\n query?: Record<string, string | number | boolean | undefined>;\n }\n ): Promise<ApiResponse<T>> {\n const url = this.buildUrl(path, options?.query);\n const headers = createHeaders(this.config, options);\n const timeout = options?.timeout ?? this.config.timeout;\n const signal = createTimeoutSignal(timeout, options?.signal);\n\n const response = await this.fetchFn(url, {\n method,\n headers,\n body: options?.body ? JSON.stringify(options.body) : undefined,\n signal,\n });\n\n let data: unknown;\n const contentType = response.headers.get('content-type');\n \n if (contentType?.includes('application/json')) {\n data = await response.json();\n } else {\n data = await response.text();\n }\n\n if (!response.ok) {\n throw SaferCityApiError.fromResponse(response, data);\n }\n\n return {\n data: data as T,\n status: response.status,\n headers: response.headers,\n };\n }\n\n /**\n * GET request\n */\n async get<T>(\n path: string,\n options?: RequestOptions & { query?: Record<string, string | number | boolean | undefined> }\n ): Promise<ApiResponse<T>> {\n return this.request<T>('GET', path, options);\n }\n\n /**\n * POST request\n */\n async post<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('POST', path, { ...options, body });\n }\n\n /**\n * PUT request\n */\n async put<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('PUT', path, { ...options, body });\n }\n\n /**\n * PATCH request\n */\n async patch<T>(\n path: string,\n body?: unknown,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('PATCH', path, { ...options, body });\n }\n\n /**\n * DELETE request\n */\n async delete<T>(\n path: string,\n options?: RequestOptions\n ): Promise<ApiResponse<T>> {\n return this.request<T>('DELETE', path, options);\n }\n}\n"]}