@quikturn/logos 0.4.0 → 0.5.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/README.md CHANGED
@@ -1,61 +1,57 @@
1
1
  # @quikturn/logos
2
2
 
3
- > TypeScript SDK for the Quikturn Logos API -- fetch company logos with type safety.
3
+ TypeScript SDK for the [Quikturn Logos API](https://getquikturn.io) -- fetch any company's logo by domain name.
4
4
 
5
- ## Packages
5
+ **[Get your API key](https://getquikturn.io)** -- free tier available, no credit card required.
6
6
 
7
- | Package | Description | Install |
8
- |---------|-------------|---------|
7
+ | Package | Description | |
8
+ |---------|-------------|-|
9
9
  | [`@quikturn/logos`](https://www.npmjs.com/package/@quikturn/logos) | Core SDK -- URL builder, browser client, server client, web component | `pnpm add @quikturn/logos` |
10
- | [`@quikturn/logos-react`](https://www.npmjs.com/package/@quikturn/logos-react) | React components -- `<QuikturnLogo>`, `<QuikturnLogoCarousel>`, `<QuikturnLogoGrid>` | `pnpm add @quikturn/logos-react` |
10
+ | [`@quikturn/logos-react`](https://www.npmjs.com/package/@quikturn/logos-react) | React components -- logo, carousel, grid | `pnpm add @quikturn/logos-react` |
11
11
 
12
- ## Features
13
-
14
- - **Zero-dependency URL builder** -- universal, works in any JavaScript runtime
15
- - **Browser client** -- blob URL management, retry/backoff, scrape polling, event emission
16
- - **Server client** -- Buffer output, ReadableStream streaming, concurrent batch operations
17
- - **`<quikturn-logo>` web component** -- zero-effort attribution element with shadow DOM
18
- - **React components** -- see [`@quikturn/logos-react`](./packages/react/) for `<QuikturnLogo>`, `<QuikturnLogoCarousel>`, and `<QuikturnLogoGrid>`
19
- - **Full TypeScript support** -- strict types, discriminated union error codes, generic response shapes
20
- - **Tree-shakeable** -- ESM and CJS dual builds; import only what you need
12
+ ---
21
13
 
22
- ## Installation
14
+ ## Install
23
15
 
24
16
  ```bash
25
- # pnpm (recommended)
26
17
  pnpm add @quikturn/logos
18
+ ```
27
19
 
28
- # npm
29
- npm install @quikturn/logos
20
+ > Requires Node.js >= 18. Works with pnpm, npm, or yarn.
21
+ >
22
+ > Need an API key? **[Sign up at getquikturn.io](https://getquikturn.io)** -- takes 30 seconds.
30
23
 
31
- # yarn
32
- yarn add @quikturn/logos
33
- ```
24
+ ## Pick Your Entry Point
25
+
26
+ | Entry Point | Import | Use Case |
27
+ |-------------|--------|----------|
28
+ | **Universal** | `@quikturn/logos` | URL builder, types, constants -- zero dependencies, any runtime |
29
+ | **Browser** | `@quikturn/logos/client` | Blob URLs, retry/backoff, scrape polling, events |
30
+ | **Server** | `@quikturn/logos/server` | Buffer output, streaming, batch operations |
31
+ | **Element** | `@quikturn/logos/element` | `<quikturn-logo>` web component with built-in attribution |
32
+ | **React** | `@quikturn/logos-react` | `<QuikturnLogo>`, `<QuikturnLogoCarousel>`, `<QuikturnLogoGrid>` |
34
33
 
35
- **Requirements:** Node.js >= 18
34
+ ---
36
35
 
37
36
  ## Quick Start
38
37
 
39
- ### URL Builder (Universal)
38
+ ### URL Builder
40
39
 
41
- The URL builder runs everywhere -- browsers, Node.js, edge runtimes -- with zero dependencies and no network calls.
40
+ The simplest way to get a logo URL. Runs everywhere -- browsers, Node.js, edge runtimes -- with no network calls.
42
41
 
43
42
  ```ts
44
43
  import { logoUrl } from "@quikturn/logos";
45
44
 
46
- // Simple usage
47
45
  const url = logoUrl("github.com");
48
46
  // => "https://logos.getquikturn.io/github.com"
49
47
 
50
- // With options
51
- const customUrl = logoUrl("stripe.com", {
48
+ const url = logoUrl("stripe.com", {
52
49
  token: "qt_abc123",
53
50
  size: 256,
54
51
  format: "webp",
55
52
  greyscale: true,
56
53
  theme: "dark",
57
54
  });
58
- // => "https://logos.getquikturn.io/stripe.com?token=qt_abc123&size=256&greyscale=1&theme=dark&format=webp"
59
55
  ```
60
56
 
61
57
  ### Browser Client
@@ -65,19 +61,13 @@ import { QuikturnLogos } from "@quikturn/logos/client";
65
61
 
66
62
  const client = new QuikturnLogos({ token: "qt_your_publishable_key" });
67
63
 
68
- // Fetch a logo as a blob URL (ready for <img src>)
69
- const { url, blob, contentType, metadata } = await client.get("github.com", {
64
+ const { url, blob, contentType } = await client.get("github.com", {
70
65
  size: 256,
71
66
  format: "webp",
72
67
  });
73
68
 
74
69
  document.querySelector("img")!.src = url;
75
70
 
76
- // Listen for rate limit warnings
77
- client.on("rateLimitWarning", (remaining, limit) => {
78
- console.warn(`Rate limit: ${remaining}/${limit} requests remaining`);
79
- });
80
-
81
71
  // Clean up blob URLs when done
82
72
  client.destroy();
83
73
  ```
@@ -89,22 +79,15 @@ import { QuikturnLogos } from "@quikturn/logos/server";
89
79
 
90
80
  const client = new QuikturnLogos({ secretKey: "sk_your_secret_key" });
91
81
 
92
- // Fetch a logo as a Buffer
93
- const { buffer, contentType, metadata } = await client.get("github.com", {
94
- size: 512,
95
- format: "png",
96
- });
82
+ // Single logo
83
+ const { buffer, contentType } = await client.get("github.com");
97
84
 
98
- // Batch fetch multiple logos
85
+ // Batch fetch
99
86
  for await (const result of client.getMany(["github.com", "stripe.com", "vercel.com"])) {
100
- if (result.success) {
101
- console.log(`${result.domain}: ${result.buffer!.byteLength} bytes`);
102
- } else {
103
- console.error(`${result.domain}: ${result.error!.message}`);
104
- }
87
+ if (result.success) console.log(`${result.domain}: ${result.buffer!.byteLength} bytes`);
105
88
  }
106
89
 
107
- // Stream a logo to a file
90
+ // Stream to file
108
91
  import { createWriteStream } from "node:fs";
109
92
  import { Readable } from "node:stream";
110
93
 
@@ -114,36 +97,19 @@ Readable.fromWeb(stream).pipe(createWriteStream("logo.png"));
114
97
 
115
98
  ### Web Component
116
99
 
117
- The `<quikturn-logo>` custom element renders a logo with built-in attribution. It uses shadow DOM to protect the attribution badge and requires no framework.
100
+ No framework needed. Import the element entry and use it in plain HTML:
118
101
 
119
102
  ```html
120
103
  <script type="module">
121
104
  import "@quikturn/logos/element";
122
105
  </script>
123
106
 
124
- <quikturn-logo
125
- domain="github.com"
126
- token="qt_abc123"
127
- size="64"
128
- format="webp"
129
- theme="dark"
130
- ></quikturn-logo>
107
+ <quikturn-logo domain="github.com" token="qt_abc123" size="64"></quikturn-logo>
131
108
  ```
132
109
 
133
- | Attribute | Type | Description |
134
- |-----------|------|-------------|
135
- | `domain` | `string` | Domain to fetch logo for (required for rendering) |
136
- | `token` | `string` | Publishable API key |
137
- | `size` | `string` | Image width in pixels |
138
- | `format` | `string` | `"png"`, `"jpeg"`, `"webp"`, or `"avif"` |
139
- | `greyscale` | presence | When present, applies greyscale transformation |
140
- | `theme` | `string` | `"light"` or `"dark"` |
141
-
142
- The element automatically registers as `quikturn-logo` on import and fires an attribution beacon on first render. Attribution styling uses `!important` rules inside the shadow DOM to prevent accidental removal.
110
+ Renders the logo with a "Powered by Quikturn" attribution badge protected by shadow DOM.
143
111
 
144
- ### React Components
145
-
146
- For React applications, install the companion package:
112
+ ### React
147
113
 
148
114
  ```bash
149
115
  pnpm add @quikturn/logos-react
@@ -152,296 +118,228 @@ pnpm add @quikturn/logos-react
152
118
  ```tsx
153
119
  import { QuikturnProvider, QuikturnLogo, QuikturnLogoCarousel } from "@quikturn/logos-react";
154
120
 
155
- <QuikturnProvider token="qt_your_key">
156
- <QuikturnLogo domain="github.com" size={64} />
157
- <QuikturnLogoCarousel
158
- domains={["github.com", "stripe.com", "vercel.com"]}
159
- speed={120}
160
- fadeOut
161
- pauseOnHover
162
- />
163
- </QuikturnProvider>
121
+ function App() {
122
+ return (
123
+ <QuikturnProvider token="qt_your_key">
124
+ <QuikturnLogo domain="github.com" size={64} />
125
+
126
+ <QuikturnLogoCarousel
127
+ domains={["github.com", "stripe.com", "vercel.com", "figma.com"]}
128
+ speed={120}
129
+ fadeOut
130
+ pauseOnHover
131
+ />
132
+ </QuikturnProvider>
133
+ );
134
+ }
164
135
  ```
165
136
 
166
- See the full API reference in [`@quikturn/logos-react` README](./packages/react/README.md).
137
+ Full React API reference: [`@quikturn/logos-react` docs](./packages/react/README.md)
167
138
 
168
- ## API Reference
169
-
170
- ### Universal (`@quikturn/logos`)
171
-
172
- The universal entry point exports the URL builder, types, constants, and error classes. No network calls are made from this module.
173
-
174
- #### `logoUrl(domain, options?)`
175
-
176
- Constructs a fully-qualified Logos API URL. Pure function with no side effects.
177
-
178
- | Parameter | Type | Description |
179
- |-----------|------|-------------|
180
- | `domain` | `string` | Domain to fetch a logo for (e.g. `"github.com"`) |
181
- | `options` | `LogoRequestOptions` | Optional request parameters |
182
-
183
- **Returns:** `string` -- fully-qualified URL
184
-
185
- **Throws:** `DomainValidationError` if the domain fails RFC 1035/1123 validation
139
+ ---
186
140
 
187
- ##### `LogoRequestOptions`
141
+ ## Authentication
188
142
 
189
- | Property | Type | Default | Description |
190
- |----------|------|---------|-------------|
191
- | `token` | `string` | -- | Publishable key (`qt_`/`pk_`) appended as a query parameter |
192
- | `size` | `number` | `128` | Output width in pixels |
193
- | `width` | `number` | `128` | Alias for `size` |
194
- | `greyscale` | `boolean` | `false` | When `true`, applies saturation: 0 transformation |
195
- | `theme` | `"light" \| "dark"` | -- | Optimize logo for light or dark backgrounds |
196
- | `format` | `SupportedOutputFormat \| FormatShorthand` | `"image/png"` | Output image format |
197
- | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | Override the API base URL |
143
+ | Key Type | Prefix | Environment | Auth Method |
144
+ |----------|--------|-------------|-------------|
145
+ | **Publishable** | `qt_` / `pk_` | Browser | Query parameter (`?token=...`) |
146
+ | **Secret** | `sk_` | Server only | `Authorization: Bearer` header |
198
147
 
199
- #### Types
148
+ Publishable keys are safe to expose in client-side code (max 800px). Secret keys must never reach the browser (max 1200px). Manage your keys in the [Quikturn dashboard](https://getquikturn.io/dashboard).
200
149
 
201
150
  ```ts
202
- import type {
203
- // Request
204
- ThemeOption, // "light" | "dark"
205
- SupportedOutputFormat, // "image/png" | "image/jpeg" | "image/webp" | "image/avif"
206
- FormatShorthand, // "png" | "jpeg" | "webp" | "avif"
207
- LogoRequestOptions,
208
-
209
- // Response
210
- LogoMetadata,
211
- BrowserLogoResponse,
212
- ServerLogoResponse,
213
-
214
- // Scrape polling
215
- ScrapeProgressEvent,
151
+ // Browser
152
+ import { QuikturnLogos } from "@quikturn/logos/client";
153
+ const client = new QuikturnLogos({ token: "qt_your_publishable_key" });
216
154
 
217
- // Error codes
218
- LogoErrorCode,
219
- } from "@quikturn/logos";
155
+ // Server
156
+ import { QuikturnLogos } from "@quikturn/logos/server";
157
+ const client = new QuikturnLogos({ secretKey: "sk_your_secret_key" });
220
158
  ```
221
159
 
222
- #### Constants
223
-
224
- | Constant | Value | Description |
225
- |----------|-------|-------------|
226
- | `BASE_URL` | `"https://logos.getquikturn.io"` | Root API endpoint |
227
- | `DEFAULT_WIDTH` | `128` | Default logo width (px) |
228
- | `DEFAULT_FORMAT` | `"image/png"` | Default output MIME type |
229
- | `SUPPORTED_FORMATS` | `Set<SupportedOutputFormat>` | All supported MIME types |
230
- | `FORMAT_ALIASES` | `Record<FormatShorthand, SupportedOutputFormat>` | Shorthand-to-MIME mapping |
231
-
232
160
  ---
233
161
 
234
- ### Browser Client (`@quikturn/logos/client`)
235
-
236
- #### `new QuikturnLogos(options)`
237
-
238
- | Option | Type | Default | Description |
239
- |--------|------|---------|-------------|
240
- | `token` | `string` | **required** | Publishable key (`qt_`/`pk_` prefix). Server keys (`sk_`) are rejected. |
241
- | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | Override the API base URL |
242
- | `maxRetries` | `number` | `2` | Max retry attempts for rate-limited/server-error responses |
243
-
244
- #### `client.get(domain, options?)`
245
-
246
- Fetches a logo and returns a `BrowserLogoResponse`.
162
+ ## API Reference
247
163
 
248
- | Option | Type | Default | Description |
249
- |--------|------|---------|-------------|
250
- | `size` | `number` | `128` | Output width in pixels |
251
- | `width` | `number` | `128` | Alias for `size` |
252
- | `greyscale` | `boolean` | `false` | Greyscale transformation |
253
- | `theme` | `"light" \| "dark"` | -- | Gamma curve adjustment |
254
- | `format` | `SupportedOutputFormat \| FormatShorthand` | `"image/png"` | Output format |
255
- | `scrapeTimeout` | `number` | -- | Max time (ms) to wait for scrape completion |
256
- | `onScrapeProgress` | `(event: ScrapeProgressEvent) => void` | -- | Callback for scrape progress |
257
- | `signal` | `AbortSignal` | -- | Cancel the request |
164
+ ### `logoUrl(domain, options?)`
258
165
 
259
- **Returns:** `Promise<BrowserLogoResponse>`
166
+ Pure URL builder. No network calls, no side effects.
260
167
 
261
168
  ```ts
262
- interface BrowserLogoResponse {
263
- url: string; // blob: object URL for <img src>
264
- blob: Blob; // Raw image Blob
265
- contentType: string; // e.g. "image/webp"
266
- metadata: LogoMetadata;
267
- }
169
+ import { logoUrl } from "@quikturn/logos";
268
170
  ```
269
171
 
270
- #### `client.getUrl(domain, options?)`
271
-
272
- Returns a plain URL string without making a network request. Useful for `<img>` tags, CSS `background-image`, or preloading hints.
273
-
274
- **Returns:** `string`
275
-
276
- #### `client.on(event, handler)` / `client.off(event, handler)`
277
-
278
- Register or remove event listeners.
279
-
280
- | Event | Handler Signature | Description |
281
- |-------|-------------------|-------------|
282
- | `"rateLimitWarning"` | `(remaining: number, limit: number) => void` | Fires when rate limit is approaching |
283
- | `"quotaWarning"` | `(remaining: number, limit: number) => void` | Fires when monthly quota is approaching |
172
+ **Options:**
284
173
 
285
- #### `client.destroy()`
174
+ | Property | Type | Default | Description |
175
+ |----------|------|---------|-------------|
176
+ | `token` | `string` | -- | API key appended as query parameter |
177
+ | `size` | `number` | `128` | Output width in pixels |
178
+ | `width` | `number` | `128` | Alias for `size` |
179
+ | `greyscale` | `boolean` | `false` | Desaturation filter |
180
+ | `theme` | `"light" \| "dark"` | -- | Background-optimized rendering |
181
+ | `format` | `string` | `"image/png"` | `"png"`, `"jpeg"`, `"webp"`, `"avif"` (or full MIME type) |
182
+ | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | API base URL override |
286
183
 
287
- Revokes all tracked `blob:` object URLs to free memory and removes all event listeners. Call this when the client is no longer needed to prevent memory leaks in long-lived browser sessions.
184
+ **Returns:** `string` | **Throws:** `DomainValidationError`
288
185
 
289
186
  ---
290
187
 
291
- ### Server Client (`@quikturn/logos/server`)
188
+ ### Browser Client
189
+
190
+ ```ts
191
+ import { QuikturnLogos } from "@quikturn/logos/client";
192
+ ```
292
193
 
293
- #### `new QuikturnLogos(options)`
194
+ #### Constructor
294
195
 
295
196
  | Option | Type | Default | Description |
296
197
  |--------|------|---------|-------------|
297
- | `secretKey` | `string` | **required** | Secret key (`sk_` prefix). Publishable keys (`qt_`/`pk_`) are rejected. |
298
- | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | Override the API base URL |
299
- | `maxRetries` | `number` | `2` | Max retry attempts for rate-limited/server-error responses |
198
+ | `token` | `string` | **required** | Publishable key (`qt_`/`pk_` prefix) |
199
+ | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | API base URL override |
200
+ | `maxRetries` | `number` | `2` | Max retries for 429/5xx responses |
300
201
 
301
- #### `client.get(domain, options?)`
202
+ #### Methods
302
203
 
303
- Fetches a logo and returns a `ServerLogoResponse`.
204
+ **`client.get(domain, options?)`** -- Fetches a logo and returns a blob URL.
304
205
 
305
- Accepts the same options as the browser client's `get()` method.
206
+ | Option | Type | Default |
207
+ |--------|------|---------|
208
+ | `size` | `number` | `128` |
209
+ | `format` | `string` | `"image/png"` |
210
+ | `greyscale` | `boolean` | `false` |
211
+ | `theme` | `"light" \| "dark"` | -- |
212
+ | `scrapeTimeout` | `number` | -- |
213
+ | `onScrapeProgress` | `(event) => void` | -- |
214
+ | `signal` | `AbortSignal` | -- |
306
215
 
307
- **Returns:** `Promise<ServerLogoResponse>`
216
+ Returns `Promise<{ url: string, blob: Blob, contentType: string, metadata: LogoMetadata }>`.
308
217
 
309
- ```ts
310
- interface ServerLogoResponse {
311
- buffer: Buffer; // Raw image bytes
312
- contentType: string; // e.g. "image/png"
313
- metadata: LogoMetadata;
314
- }
315
- ```
218
+ **`client.getUrl(domain, options?)`** -- Returns a plain URL string without a network request.
316
219
 
317
- #### `client.getMany(domains, options?)`
220
+ **`client.on(event, handler)`** / **`client.off(event, handler)`** -- Listen for `"rateLimitWarning"` or `"quotaWarning"` events.
318
221
 
319
- Fetches logos for multiple domains with concurrency control. Yields results in the same order as the input array.
222
+ **`client.destroy()`** -- Revokes all tracked blob URLs and removes event listeners. Call this to prevent memory leaks.
320
223
 
321
- | Option | Type | Default | Description |
322
- |--------|------|---------|-------------|
323
- | `concurrency` | `number` | `5` | Maximum parallel fetches |
324
- | `size` | `number` | `128` | Output width in pixels |
325
- | `greyscale` | `boolean` | `false` | Greyscale transformation |
326
- | `theme` | `"light" \| "dark"` | -- | Gamma curve adjustment |
327
- | `format` | `SupportedOutputFormat \| FormatShorthand` | `"image/png"` | Output format |
328
- | `signal` | `AbortSignal` | -- | Cancel remaining batch items |
329
- | `continueOnError` | `boolean` | `true` | Capture errors per-domain instead of aborting the batch |
224
+ ---
330
225
 
331
- **Returns:** `AsyncGenerator<BatchResult>`
226
+ ### Server Client
332
227
 
333
228
  ```ts
334
- interface BatchResult {
335
- domain: string;
336
- success: boolean;
337
- buffer?: Buffer;
338
- contentType?: string;
339
- metadata?: LogoMetadata;
340
- error?: LogoError;
341
- }
229
+ import { QuikturnLogos } from "@quikturn/logos/server";
342
230
  ```
343
231
 
344
- #### `client.getStream(domain, options?)`
232
+ #### Constructor
345
233
 
346
- Returns the raw response body as a `ReadableStream`. Useful for piping to a file or HTTP response without buffering the entire image in memory.
234
+ | Option | Type | Default | Description |
235
+ |--------|------|---------|-------------|
236
+ | `secretKey` | `string` | **required** | Secret key (`sk_` prefix) |
237
+ | `baseUrl` | `string` | `"https://logos.getquikturn.io"` | API base URL override |
238
+ | `maxRetries` | `number` | `2` | Max retries for 429/5xx responses |
347
239
 
348
- Accepts the same options as `get()`.
240
+ #### Methods
349
241
 
350
- **Returns:** `Promise<ReadableStream>`
242
+ **`client.get(domain, options?)`** -- Returns `Promise<{ buffer: Buffer, contentType: string, metadata: LogoMetadata }>`.
351
243
 
352
- #### `client.getUrl(domain, options?)`
244
+ **`client.getMany(domains, options?)`** -- Batch fetch with concurrency control. Returns `AsyncGenerator<BatchResult>`.
353
245
 
354
- Returns a plain URL string without making a network request. Does **not** include the secret key -- use the `Authorization: Bearer` header when fetching.
246
+ | Option | Type | Default |
247
+ |--------|------|---------|
248
+ | `concurrency` | `number` | `5` |
249
+ | `continueOnError` | `boolean` | `true` |
250
+ | `signal` | `AbortSignal` | -- |
355
251
 
356
- **Returns:** `string`
252
+ **`client.getStream(domain, options?)`** -- Returns `Promise<ReadableStream>` for zero-copy streaming.
357
253
 
358
- #### `client.on(event, handler)` / `client.off(event, handler)`
254
+ **`client.getUrl(domain, options?)`** -- Returns a URL string (secret key NOT included -- use `Authorization` header).
359
255
 
360
- Same event interface as the browser client. Supports `"rateLimitWarning"` and `"quotaWarning"` events.
256
+ **`client.on(event, handler)`** / **`client.off(event, handler)`** -- Same events as the browser client.
361
257
 
362
258
  ---
363
259
 
364
- ### Error Classes
365
-
366
- All SDK errors extend `LogoError`, which extends the native `Error` class with a machine-readable `code` and an optional HTTP `status`.
260
+ ### Web Component
367
261
 
368
262
  ```ts
369
- import {
370
- LogoError,
371
- DomainValidationError,
372
- RateLimitError,
373
- QuotaExceededError,
374
- AuthenticationError,
375
- ForbiddenError,
376
- NotFoundError,
377
- ScrapeTimeoutError,
378
- BadRequestError,
379
- } from "@quikturn/logos";
263
+ import "@quikturn/logos/element";
380
264
  ```
381
265
 
382
- | Error Class | Code | HTTP Status | Extra Properties |
383
- |-------------|------|-------------|------------------|
384
- | `LogoError` | varies | varies | `code: LogoErrorCode`, `status?: number` |
385
- | `DomainValidationError` | `DOMAIN_VALIDATION_ERROR` | -- | `domain: string` |
386
- | `AuthenticationError` | `AUTHENTICATION_ERROR` | 401 | -- |
387
- | `ForbiddenError` | `FORBIDDEN_ERROR` | 403 | `reason: string` |
388
- | `NotFoundError` | `NOT_FOUND_ERROR` | 404 | `domain: string` |
389
- | `BadRequestError` | `BAD_REQUEST_ERROR` | 400 | -- |
390
- | `RateLimitError` | `RATE_LIMIT_ERROR` | 429 | `retryAfter: number`, `remaining: number`, `resetAt: Date` |
391
- | `QuotaExceededError` | `QUOTA_EXCEEDED_ERROR` | 429 | `retryAfter: number`, `limit: number`, `used: number` |
392
- | `ScrapeTimeoutError` | `SCRAPE_TIMEOUT_ERROR` | -- | `jobId: string`, `elapsed: number` |
266
+ | Attribute | Type | Description |
267
+ |-----------|------|-------------|
268
+ | `domain` | `string` | Domain to fetch logo for (required) |
269
+ | `token` | `string` | Publishable API key |
270
+ | `size` | `string` | Image width in pixels |
271
+ | `format` | `string` | `"png"`, `"jpeg"`, `"webp"`, or `"avif"` |
272
+ | `greyscale` | (presence) | Greyscale filter when attribute is present |
273
+ | `theme` | `string` | `"light"` or `"dark"` |
274
+
275
+ Auto-registers as `<quikturn-logo>` on import. Shadow DOM protects the attribution badge with `!important` CSS rules.
276
+
277
+ ---
278
+
279
+ ### Error Handling
393
280
 
394
- All error codes are typed via the `LogoErrorCode` discriminated union for exhaustive switch handling:
281
+ All errors extend `LogoError` with a typed `code` property for exhaustive `switch` handling:
395
282
 
396
283
  ```ts
397
284
  import { LogoError } from "@quikturn/logos";
398
- import type { LogoErrorCode } from "@quikturn/logos";
399
285
 
400
286
  try {
401
287
  const { url } = await client.get("example.com");
402
288
  } catch (err) {
403
289
  if (err instanceof LogoError) {
404
290
  switch (err.code) {
405
- case "RATE_LIMIT_ERROR":
406
- // err is narrowed, handle backoff
407
- break;
408
- case "NOT_FOUND_ERROR":
409
- // show placeholder
410
- break;
411
- // ... handle other cases
291
+ case "RATE_LIMIT_ERROR": /* backoff */ break;
292
+ case "NOT_FOUND_ERROR": /* fallback */ break;
293
+ case "AUTHENTICATION_ERROR": /* check key */ break;
294
+ // ...
412
295
  }
413
296
  }
414
297
  }
415
298
  ```
416
299
 
417
- ## Authentication
418
-
419
- The Quikturn Logos API uses token-based authentication with two key types:
300
+ | Error | Code | Status | Extra Properties |
301
+ |-------|------|--------|------------------|
302
+ | `DomainValidationError` | `DOMAIN_VALIDATION_ERROR` | -- | `domain` |
303
+ | `AuthenticationError` | `AUTHENTICATION_ERROR` | 401 | -- |
304
+ | `ForbiddenError` | `FORBIDDEN_ERROR` | 403 | `reason` |
305
+ | `NotFoundError` | `NOT_FOUND_ERROR` | 404 | `domain` |
306
+ | `BadRequestError` | `BAD_REQUEST_ERROR` | 400 | -- |
307
+ | `RateLimitError` | `RATE_LIMIT_ERROR` | 429 | `retryAfter`, `remaining`, `resetAt` |
308
+ | `QuotaExceededError` | `QUOTA_EXCEEDED_ERROR` | 429 | `retryAfter`, `limit`, `used` |
309
+ | `ScrapeTimeoutError` | `SCRAPE_TIMEOUT_ERROR` | -- | `jobId`, `elapsed` |
420
310
 
421
- | Key Type | Prefix | Environment | Auth Method |
422
- |----------|--------|-------------|-------------|
423
- | **Publishable** | `qt_` or `pk_` | Browser | Query parameter (`?token=...`) |
424
- | **Secret** | `sk_` | Server only | `Authorization: Bearer` header |
311
+ ---
425
312
 
426
- - **Publishable keys** are safe to expose in client-side code. They are passed as query parameters and support a max image width of 800px.
427
- - **Secret keys** must never be exposed to the browser. They are sent via the `Authorization` header and support a max image width of 1200px.
313
+ ### Types & Constants
428
314
 
429
315
  ```ts
430
- // Browser -- publishable key
431
- import { QuikturnLogos } from "@quikturn/logos/client";
432
- const client = new QuikturnLogos({ token: "qt_your_publishable_key" });
433
-
434
- // Server -- secret key
435
- import { QuikturnLogos } from "@quikturn/logos/server";
436
- const client = new QuikturnLogos({ secretKey: "sk_your_secret_key" });
316
+ import type {
317
+ ThemeOption, // "light" | "dark"
318
+ SupportedOutputFormat, // "image/png" | "image/jpeg" | "image/webp" | "image/avif"
319
+ FormatShorthand, // "png" | "jpeg" | "webp" | "avif"
320
+ LogoRequestOptions,
321
+ LogoMetadata,
322
+ BrowserLogoResponse,
323
+ ServerLogoResponse,
324
+ ScrapeProgressEvent,
325
+ LogoErrorCode, // discriminated union of all error codes
326
+ } from "@quikturn/logos";
437
327
  ```
438
328
 
329
+ | Constant | Value |
330
+ |----------|-------|
331
+ | `BASE_URL` | `"https://logos.getquikturn.io"` |
332
+ | `DEFAULT_WIDTH` | `128` |
333
+ | `DEFAULT_FORMAT` | `"image/png"` |
334
+ | `SUPPORTED_FORMATS` | `Set` of 4 MIME types |
335
+ | `FORMAT_ALIASES` | `{ png, jpeg, webp, avif }` -> MIME mapping |
336
+
337
+ ---
338
+
439
339
  ## Configuration
440
340
 
441
341
  ### Custom Base URL
442
342
 
443
- Override the API endpoint for testing, proxied environments, or self-hosted deployments:
444
-
445
343
  ```ts
446
344
  const client = new QuikturnLogos({
447
345
  token: "qt_your_key",
@@ -449,48 +347,33 @@ const client = new QuikturnLogos({
449
347
  });
450
348
  ```
451
349
 
452
- ### Format Options
350
+ ### Formats
453
351
 
454
- Four output formats are supported:
455
-
456
- | Format | MIME Type | Shorthand |
352
+ | Format | Shorthand | MIME Type |
457
353
  |--------|-----------|-----------|
458
- | PNG | `image/png` | `"png"` |
459
- | JPEG | `image/jpeg` | `"jpeg"` |
460
- | WebP | `image/webp` | `"webp"` |
461
- | AVIF | `image/avif` | `"avif"` |
354
+ | PNG | `"png"` | `image/png` |
355
+ | JPEG | `"jpeg"` | `image/jpeg` |
356
+ | WebP | `"webp"` | `image/webp` |
357
+ | AVIF | `"avif"` | `image/avif` |
462
358
 
463
- Both the full MIME type and the shorthand alias are accepted:
359
+ Both forms are accepted: `format: "webp"` and `format: "image/webp"` are equivalent.
464
360
 
465
- ```ts
466
- // These are equivalent
467
- client.get("github.com", { format: "image/webp" });
468
- client.get("github.com", { format: "webp" });
469
- ```
361
+ ### Themes
470
362
 
471
- ### Theme Options
472
-
473
- | Theme | Use Case |
474
- |-------|----------|
475
- | `"light"` | Optimized for light backgrounds |
476
- | `"dark"` | Optimized for dark backgrounds |
363
+ Use `"light"` for light backgrounds and `"dark"` for dark backgrounds. The API adjusts the logo's color profile to maximize contrast.
477
364
 
478
365
  ## Rate Limits & Quotas
479
366
 
480
- Rate limits and monthly quotas are enforced by the API server and vary by plan. The SDK automatically reads rate-limit headers to provide warnings via the event system and retries with backoff when limits are hit. See [Quikturn pricing](https://getquikturn.io/pricing) for details on your plan's limits.
481
-
482
- ## Related Packages
367
+ Rate limits and monthly quotas are enforced server-side and vary by plan. The SDK automatically retries with exponential backoff when limits are hit and emits `"rateLimitWarning"` / `"quotaWarning"` events so you can react in your UI. See [pricing & plan details](https://getquikturn.io/pricing).
483
368
 
484
- ### [`@quikturn/logos-react`](https://www.npmjs.com/package/@quikturn/logos-react)
485
-
486
- Ready-made React components for displaying Quikturn logos. Includes an infinite scrolling carousel, responsive grid, single logo image, context provider for token propagation, and a `useLogoUrl()` hook. Zero CSS dependencies -- inline styles only.
487
-
488
- ```bash
489
- pnpm add @quikturn/logos-react @quikturn/logos
490
- ```
369
+ ## Resources
491
370
 
492
- See the full documentation at [`packages/react/README.md`](./packages/react/README.md).
371
+ - **[Quikturn website](https://getquikturn.io)** -- sign up, manage keys, explore the API
372
+ - **[Dashboard](https://getquikturn.io/dashboard)** -- usage analytics, key management, plan upgrades
373
+ - **[Pricing](https://getquikturn.io/pricing)** -- free tier, pro, and enterprise plans
374
+ - **[React components](./packages/react/README.md)** -- `@quikturn/logos-react` docs
375
+ - **[GitHub](https://github.com/Quikturn-PowerPoint-Add-In/Logo-SDK)** -- source, issues, contributions
493
376
 
494
377
  ## License
495
378
 
496
- MIT
379
+ MIT -- built by [Quikturn](https://getquikturn.io)
@@ -366,8 +366,8 @@ async function browserFetch(url, options) {
366
366
  throw new AuthenticationError("Authentication failed");
367
367
  }
368
368
  if (response.status === 403) {
369
- const body2 = await response.text();
370
- const reason = body2.slice(0, 256) || "unknown";
369
+ const body = await response.text();
370
+ const reason = body.slice(0, 256) || "unknown";
371
371
  throw new ForbiddenError("Access forbidden", reason);
372
372
  }
373
373
  if (response.status === 404) {
@@ -407,8 +407,8 @@ async function browserFetch(url, options) {
407
407
  );
408
408
  }
409
409
  if (response.status === 400) {
410
- const body2 = await response.text();
411
- throw new BadRequestError(body2.slice(0, 256) || "Bad request");
410
+ const body = await response.text();
411
+ throw new BadRequestError(body.slice(0, 256) || "Bad request");
412
412
  }
413
413
  if (response.status === 500) {
414
414
  if (!serverErrorRetried) {
@@ -416,9 +416,9 @@ async function browserFetch(url, options) {
416
416
  await delay(SERVER_ERROR_RETRY_DELAY_MS);
417
417
  continue;
418
418
  }
419
- const body2 = await response.text();
419
+ const body = await response.text();
420
420
  throw new LogoError(
421
- body2.slice(0, 256) || "Internal server error",
421
+ body.slice(0, 256) || "Internal server error",
422
422
  "SERVER_ERROR",
423
423
  500
424
424
  );
@@ -364,8 +364,8 @@ async function browserFetch(url, options) {
364
364
  throw new AuthenticationError("Authentication failed");
365
365
  }
366
366
  if (response.status === 403) {
367
- const body2 = await response.text();
368
- const reason = body2.slice(0, 256) || "unknown";
367
+ const body = await response.text();
368
+ const reason = body.slice(0, 256) || "unknown";
369
369
  throw new ForbiddenError("Access forbidden", reason);
370
370
  }
371
371
  if (response.status === 404) {
@@ -405,8 +405,8 @@ async function browserFetch(url, options) {
405
405
  );
406
406
  }
407
407
  if (response.status === 400) {
408
- const body2 = await response.text();
409
- throw new BadRequestError(body2.slice(0, 256) || "Bad request");
408
+ const body = await response.text();
409
+ throw new BadRequestError(body.slice(0, 256) || "Bad request");
410
410
  }
411
411
  if (response.status === 500) {
412
412
  if (!serverErrorRetried) {
@@ -414,9 +414,9 @@ async function browserFetch(url, options) {
414
414
  await delay(SERVER_ERROR_RETRY_DELAY_MS);
415
415
  continue;
416
416
  }
417
- const body2 = await response.text();
417
+ const body = await response.text();
418
418
  throw new LogoError(
419
- body2.slice(0, 256) || "Internal server error",
419
+ body.slice(0, 256) || "Internal server error",
420
420
  "SERVER_ERROR",
421
421
  500
422
422
  );
@@ -369,8 +369,8 @@ async function serverFetch(url, options) {
369
369
  throw new AuthenticationError("Authentication failed");
370
370
  }
371
371
  if (response.status === 403) {
372
- const body2 = await response.text();
373
- const reason = body2.slice(0, 256) || "unknown";
372
+ const body = await response.text();
373
+ const reason = body.slice(0, 256) || "unknown";
374
374
  throw new ForbiddenError("Access forbidden", reason);
375
375
  }
376
376
  if (response.status === 404) {
@@ -410,8 +410,8 @@ async function serverFetch(url, options) {
410
410
  );
411
411
  }
412
412
  if (response.status === 400) {
413
- const body2 = await response.text();
414
- throw new BadRequestError(body2.slice(0, 256) || "Bad request");
413
+ const body = await response.text();
414
+ throw new BadRequestError(body.slice(0, 256) || "Bad request");
415
415
  }
416
416
  if (response.status === 500) {
417
417
  if (!serverErrorRetried) {
@@ -419,9 +419,9 @@ async function serverFetch(url, options) {
419
419
  await delay(SERVER_ERROR_RETRY_DELAY_MS);
420
420
  continue;
421
421
  }
422
- const body2 = await response.text();
422
+ const body = await response.text();
423
423
  throw new LogoError(
424
- body2.slice(0, 256) || "Internal server error",
424
+ body.slice(0, 256) || "Internal server error",
425
425
  "SERVER_ERROR",
426
426
  500
427
427
  );
@@ -367,8 +367,8 @@ async function serverFetch(url, options) {
367
367
  throw new AuthenticationError("Authentication failed");
368
368
  }
369
369
  if (response.status === 403) {
370
- const body2 = await response.text();
371
- const reason = body2.slice(0, 256) || "unknown";
370
+ const body = await response.text();
371
+ const reason = body.slice(0, 256) || "unknown";
372
372
  throw new ForbiddenError("Access forbidden", reason);
373
373
  }
374
374
  if (response.status === 404) {
@@ -408,8 +408,8 @@ async function serverFetch(url, options) {
408
408
  );
409
409
  }
410
410
  if (response.status === 400) {
411
- const body2 = await response.text();
412
- throw new BadRequestError(body2.slice(0, 256) || "Bad request");
411
+ const body = await response.text();
412
+ throw new BadRequestError(body.slice(0, 256) || "Bad request");
413
413
  }
414
414
  if (response.status === 500) {
415
415
  if (!serverErrorRetried) {
@@ -417,9 +417,9 @@ async function serverFetch(url, options) {
417
417
  await delay(SERVER_ERROR_RETRY_DELAY_MS);
418
418
  continue;
419
419
  }
420
- const body2 = await response.text();
420
+ const body = await response.text();
421
421
  throw new LogoError(
422
- body2.slice(0, 256) || "Internal server error",
422
+ body.slice(0, 256) || "Internal server error",
423
423
  "SERVER_ERROR",
424
424
  500
425
425
  );
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@quikturn/logos",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "Quikturn Logo SDK — URL builder, browser client, and server client for the Quikturn Logos API",
5
5
  "license": "MIT",
6
6
  "author": "Quikturn",
@@ -30,7 +30,6 @@
30
30
  },
31
31
  "files": [
32
32
  "dist",
33
- "package.json",
34
33
  "LICENSE",
35
34
  "README.md"
36
35
  ],
@@ -81,20 +80,29 @@
81
80
  "test:watch": "vitest",
82
81
  "test:coverage": "vitest run --coverage",
83
82
  "typecheck": "tsc --noEmit",
84
- "lint": "tsc --noEmit",
85
- "check": "pnpm lint && pnpm typecheck",
83
+ "lint": "eslint .",
84
+ "lint:fix": "eslint . --fix",
85
+ "check": "pnpm typecheck && pnpm lint",
86
86
  "prepublishOnly": "pnpm run build"
87
87
  },
88
88
  "devDependencies": {
89
+ "@eslint/js": "^9.39.2",
89
90
  "@semantic-release/changelog": "^6.0.3",
90
91
  "@semantic-release/git": "^10.0.1",
91
92
  "@types/jsdom": "^27.0.0",
92
93
  "@types/node": "^25.2.2",
93
94
  "@vitest/coverage-v8": "^4.0.18",
95
+ "angular-eslint": "^21.2.0",
96
+ "eslint": "^9.39.2",
97
+ "eslint-plugin-react-hooks": "^7.0.1",
98
+ "eslint-plugin-svelte": "^3.15.0",
99
+ "eslint-plugin-vue": "^10.7.0",
100
+ "globals": "^17.3.0",
94
101
  "jsdom": "^28.0.0",
95
102
  "semantic-release": "^25.0.3",
96
103
  "tsup": "^8.5.1",
97
104
  "typescript": "^5.9.3",
105
+ "typescript-eslint": "^8.55.0",
98
106
  "vitest": "^4.0.18"
99
107
  }
100
108
  }