lowdata 0.1.0 → 0.2.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 +37 -4
- package/dist/{client-CqO3Y1J5.d.cts → client-BmsR0Pf6.d.cts} +8 -2
- package/dist/{client-BvdWSYIM.d.ts → client-DSKoYUj9.d.ts} +8 -2
- package/dist/forms.cjs +29 -11
- package/dist/forms.cjs.map +1 -1
- package/dist/forms.d.cts +4 -4
- package/dist/forms.d.ts +4 -4
- package/dist/forms.js +29 -11
- package/dist/forms.js.map +1 -1
- package/dist/index.cjs +29 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +4 -4
- package/dist/index.d.ts +4 -4
- package/dist/index.js +29 -11
- package/dist/index.js.map +1 -1
- package/dist/media.cjs +1 -1
- package/dist/media.cjs.map +1 -1
- package/dist/media.d.cts +2 -2
- package/dist/media.d.ts +2 -2
- package/dist/media.js +1 -1
- package/dist/media.js.map +1 -1
- package/dist/network.cjs +29 -11
- package/dist/network.cjs.map +1 -1
- package/dist/network.d.cts +7 -6
- package/dist/network.d.ts +7 -6
- package/dist/network.js +29 -11
- package/dist/network.js.map +1 -1
- package/dist/{progressiveImage-BhY_K8Dj.d.ts → progressiveImage-BOdic4dJ.d.ts} +1 -1
- package/dist/{progressiveImage-BWNdewbi.d.cts → progressiveImage-CDpz_rv3.d.cts} +1 -1
- package/dist/react.cjs +29 -11
- package/dist/react.cjs.map +1 -1
- package/dist/react.d.cts +4 -4
- package/dist/react.d.ts +4 -4
- package/dist/react.js +29 -11
- package/dist/react.js.map +1 -1
- package/dist/{retry-C3zL9T5Z.d.cts → retry-BnuESos0.d.cts} +1 -1
- package/dist/{retry-D6DfKGOi.d.ts → retry-CnvZhHan.d.ts} +1 -1
- package/dist/{types-Bn1BAcch.d.ts → types-1ha2HIfv.d.ts} +1 -1
- package/dist/{types-CZDYS-fB.d.cts → types-Cbm1nNDO.d.cts} +1 -1
- package/dist/{types--FRrBa-i.d.cts → types-D6fjl8Wy.d.cts} +22 -1
- package/dist/{types--FRrBa-i.d.ts → types-D6fjl8Wy.d.ts} +22 -1
- package/package.json +22 -2
package/dist/media.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/events.ts","../src/core/connection.ts","../src/media/qualityPolicy.ts","../src/media/compressImage.ts","../src/media/progressiveImage.ts"],"names":[],"mappings":"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","file":"media.cjs","sourcesContent":["/** Tiny typed pub-sub. No Node `EventEmitter` dependency, works in any JS runtime. */\nexport class Emitter<T> {\n private listeners = new Set<(value: T) => void>();\n\n subscribe(listener: (value: T) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n emit(value: T): void {\n for (const listener of this.listeners) {\n listener(value);\n }\n }\n\n clear(): void {\n this.listeners.clear();\n }\n\n get size(): number {\n return this.listeners.size;\n }\n}\n","import { Emitter } from './events.js';\nimport type {\n ConnectionInfo,\n ConnectionListener,\n ConnectionQuality,\n Unsubscribe,\n} from './types.js';\n\n/**\n * The Network Information API (`navigator.connection`) is Chromium-only and not part of the\n * standard DOM lib types, hence this local shape instead of a global augmentation.\n */\ninterface NetworkInformationLike {\n effectiveType?: '4g' | '3g' | '2g' | 'slow-2g';\n downlink?: number;\n rtt?: number;\n saveData?: boolean;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ntype NavigatorWithConnection = Navigator & { connection?: NetworkInformationLike };\n\nexport interface ConnectionMonitorOptions {\n /**\n * Opt-in latency probe URL used to detect 'slow' connections on browsers without the Network\n * Information API (Safari, Firefox). Never probed automatically on a timer — only once on\n * startup and once per reconnect — because every probe costs a little data.\n */\n pingUrl?: string;\n /** Round-trip time above which the ping probe classifies the connection as 'slow'. Default 600ms. */\n slowRttThresholdMs?: number;\n /** downlink (Mbps) below which the Network Information API classifies as 'slow'. Default 0.5. */\n slowDownlinkMbps?: number;\n /** Timeout for the ping probe itself, in ms. Default 5000. */\n pingTimeoutMs?: number;\n}\n\nconst DEFAULT_SLOW_RTT_MS = 600;\nconst DEFAULT_SLOW_DOWNLINK_MBPS = 0.5;\nconst DEFAULT_PING_TIMEOUT_MS = 5000;\n\nfunction hasNavigator(): boolean {\n return typeof navigator !== 'undefined';\n}\n\n/**\n * Tracks connection quality (online / slow / offline) using the best signal available:\n * `navigator.onLine` + online/offline events as the universal baseline, `navigator.connection`\n * where present, and an optional opt-in ping probe as a cross-browser fallback for 'slow'.\n */\nexport class ConnectionMonitor {\n private emitter = new Emitter<ConnectionInfo>();\n private current: ConnectionInfo;\n private probedRttMs: number | undefined;\n private readonly options: Required<\n Pick<ConnectionMonitorOptions, 'slowRttThresholdMs' | 'slowDownlinkMbps' | 'pingTimeoutMs'>\n > &\n Pick<ConnectionMonitorOptions, 'pingUrl'>;\n private disposed = false;\n private onlineHandler?: () => void;\n private offlineHandler?: () => void;\n private connectionChangeHandler?: () => void;\n\n constructor(options: ConnectionMonitorOptions = {}) {\n this.options = {\n pingUrl: options.pingUrl,\n slowRttThresholdMs: options.slowRttThresholdMs ?? DEFAULT_SLOW_RTT_MS,\n slowDownlinkMbps: options.slowDownlinkMbps ?? DEFAULT_SLOW_DOWNLINK_MBPS,\n pingTimeoutMs: options.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS,\n };\n this.current = this.computeInfo();\n\n if (hasNavigator() && typeof window !== 'undefined') {\n this.onlineHandler = () => {\n this.refresh();\n void this.probeNow();\n };\n this.offlineHandler = () => this.refresh();\n window.addEventListener('online', this.onlineHandler);\n window.addEventListener('offline', this.offlineHandler);\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn?.addEventListener) {\n this.connectionChangeHandler = () => this.refresh();\n conn.addEventListener('change', this.connectionChangeHandler);\n }\n }\n\n if (this.options.pingUrl) {\n void this.probeNow();\n }\n }\n\n getStatus(): ConnectionInfo {\n return this.current;\n }\n\n subscribe(listener: ConnectionListener): Unsubscribe {\n return this.emitter.subscribe(listener);\n }\n\n /** Manually re-run the opt-in ping probe (no-op if no `pingUrl` was configured). */\n async probeNow(): Promise<ConnectionInfo> {\n if (!this.options.pingUrl || !hasNavigator() || navigator.onLine === false) {\n return this.current;\n }\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.options.pingTimeoutMs);\n try {\n await fetch(this.options.pingUrl, {\n method: 'HEAD',\n cache: 'no-store',\n signal: controller.signal,\n });\n this.probedRttMs = Date.now() - start;\n } catch {\n // A failed probe tells us nothing reliable about latency; leave prior measurement as-is.\n } finally {\n clearTimeout(timer);\n }\n return this.refresh();\n }\n\n destroy(): void {\n if (this.disposed) return;\n this.disposed = true;\n if (typeof window !== 'undefined') {\n if (this.onlineHandler) window.removeEventListener('online', this.onlineHandler);\n if (this.offlineHandler) window.removeEventListener('offline', this.offlineHandler);\n }\n if (hasNavigator() && this.connectionChangeHandler) {\n const conn = (navigator as NavigatorWithConnection).connection;\n conn?.removeEventListener?.('change', this.connectionChangeHandler);\n }\n this.emitter.clear();\n }\n\n private refresh(): ConnectionInfo {\n this.current = this.computeInfo();\n this.emitter.emit(this.current);\n return this.current;\n }\n\n private computeInfo(): ConnectionInfo {\n if (!hasNavigator()) {\n // Server/SSR context: assume the best case rather than block anything.\n return { quality: 'online', online: true };\n }\n\n const online = navigator.onLine !== false;\n if (!online) {\n return { quality: 'offline', online: false };\n }\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n const info: ConnectionInfo = {\n quality: 'online',\n online: true,\n effectiveType: conn.effectiveType,\n downlinkMbps: conn.downlink,\n rttMs: conn.rtt,\n saveData: conn.saveData,\n };\n const isSlow =\n conn.saveData === true ||\n conn.effectiveType === '2g' ||\n conn.effectiveType === 'slow-2g' ||\n (typeof conn.downlink === 'number' && conn.downlink < this.options.slowDownlinkMbps) ||\n (typeof conn.rtt === 'number' && conn.rtt > this.options.slowRttThresholdMs);\n return { ...info, quality: isSlow ? 'slow' : 'online' };\n }\n\n // No Network Information API: fall back to the opt-in ping probe measurement, if any.\n const quality: ConnectionQuality =\n typeof this.probedRttMs === 'number' && this.probedRttMs > this.options.slowRttThresholdMs\n ? 'slow'\n : 'online';\n return {\n quality,\n online: true,\n rttMs: this.probedRttMs,\n };\n }\n}\n\nlet sharedMonitor: ConnectionMonitor | undefined;\n\nfunction getSharedMonitor(): ConnectionMonitor {\n if (!sharedMonitor) sharedMonitor = new ConnectionMonitor();\n return sharedMonitor;\n}\n\n/** Convenience one-shot read of connection quality, without owning a `LowdataClient`. */\nexport function getConnectionQuality(): ConnectionInfo {\n return getSharedMonitor().getStatus();\n}\n\n/** Convenience subscription to connection changes, without owning a `LowdataClient`. */\nexport function onConnectionChange(listener: ConnectionListener): Unsubscribe {\n return getSharedMonitor().subscribe(listener);\n}\n","import type { ConnectionQuality } from '../core/types.js';\nimport type { ImageCompressionOptions } from './types.js';\n\nexport type CompressionPreset = Required<Pick<ImageCompressionOptions, 'maxWidth' | 'quality'>> &\n Pick<ImageCompressionOptions, 'targetSizeKB'>;\n\n/**\n * Aggressive-but-usable presets by connection quality. 'offline' is deliberately the smallest —\n * an image compressed while offline is headed straight into the persistent queue, where every\n * extra KB counts against storage quota and, eventually, the user's data bundle.\n */\nconst PRESETS: Record<ConnectionQuality, CompressionPreset> = {\n online: { maxWidth: 1600, quality: 0.72 },\n slow: { maxWidth: 1000, quality: 0.5, targetSizeKB: 150 },\n offline: { maxWidth: 800, quality: 0.4, targetSizeKB: 80 },\n};\n\nexport function presetForQuality(quality: ConnectionQuality): CompressionPreset {\n return PRESETS[quality];\n}\n","import { getConnectionQuality } from '../core/connection.js';\nimport { presetForQuality } from './qualityPolicy.js';\nimport type { CompressedImageResult, ImageCompressionOptions } from './types.js';\n\nconst DEFAULT_MAX_WIDTH = 1280;\nconst DEFAULT_QUALITY = 0.6;\nconst DEFAULT_MIME: NonNullable<ImageCompressionOptions['mimeType']> = 'image/jpeg';\nconst MIN_QUALITY = 0.35;\nconst QUALITY_STEP = 0.1;\nconst MAX_QUALITY_ITERATIONS = 5;\n\ntype ImageSource = ImageBitmap | HTMLImageElement;\n\nasync function loadImageSource(file: File | Blob): Promise<ImageSource> {\n if (typeof createImageBitmap === 'function') {\n return createImageBitmap(file);\n }\n // Fallback for environments without createImageBitmap (older Safari).\n return new Promise<HTMLImageElement>((resolve, reject) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve(img);\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('lowdata: failed to load image for compression'));\n };\n img.src = url;\n });\n}\n\nfunction computeTargetDimensions(\n sourceWidth: number,\n sourceHeight: number,\n maxWidth: number,\n maxHeight?: number,\n): { width: number; height: number } {\n let width = sourceWidth;\n let height = sourceHeight;\n\n if (width > maxWidth) {\n height = Math.round((height * maxWidth) / width);\n width = maxWidth;\n }\n if (maxHeight && height > maxHeight) {\n width = Math.round((width * maxHeight) / height);\n height = maxHeight;\n }\n return { width, height };\n}\n\nfunction drawToCanvas(source: ImageSource, width: number, height: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('lowdata: canvas 2D context is not available in this environment');\n ctx.drawImage(source, 0, 0, width, height);\n return canvas;\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement, mimeType: string, quality: number): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob);\n else reject(new Error('lowdata: canvas failed to produce an image blob'));\n },\n mimeType,\n quality,\n );\n });\n}\n\n/**\n * Client-side image compression via the Canvas API: downscales to a max dimension and re-encodes\n * at a target quality, optionally iterating quality further down to hit a byte budget. No\n * dependency — avoids pulling in a native/wasm image library, keeping the `lowdata/media` subpath\n * tiny. Known v1 limitation: EXIF orientation can be lost on re-encode in some browsers.\n */\nexport async function compressImage(\n file: File | Blob,\n options: ImageCompressionOptions = {},\n): Promise<CompressedImageResult> {\n const preset = options.connectionAware\n ? presetForQuality(getConnectionQuality().quality)\n : undefined;\n const maxWidth = options.maxWidth ?? preset?.maxWidth ?? DEFAULT_MAX_WIDTH;\n const maxHeight = options.maxHeight;\n const mimeType = options.mimeType ?? DEFAULT_MIME;\n const targetSizeKB = options.targetSizeKB ?? preset?.targetSizeKB;\n let quality = options.quality ?? preset?.quality ?? DEFAULT_QUALITY;\n\n const source = await loadImageSource(file);\n const { width, height } = computeTargetDimensions(\n source.width,\n source.height,\n maxWidth,\n maxHeight,\n );\n const canvas = drawToCanvas(source, width, height);\n if ('close' in source && typeof source.close === 'function') source.close();\n\n let blob = await canvasToBlob(canvas, mimeType, quality);\n\n if (targetSizeKB) {\n const targetBytes = targetSizeKB * 1024;\n let iterations = 0;\n while (\n blob.size > targetBytes &&\n quality > MIN_QUALITY &&\n iterations < MAX_QUALITY_ITERATIONS\n ) {\n quality = Math.max(MIN_QUALITY, quality - QUALITY_STEP);\n blob = await canvasToBlob(canvas, mimeType, quality);\n iterations++;\n }\n }\n\n return { blob, width, height, sizeBytes: blob.size, quality };\n}\n","import { Emitter } from '../core/events.js';\nimport type { Unsubscribe } from '../core/types.js';\n\nexport interface ProgressiveImageState {\n src: string;\n isLoaded: boolean;\n}\n\nexport interface ProgressiveImageLoaderOptions {\n src: string;\n placeholder: string;\n}\n\nexport interface ProgressiveImageLoader {\n subscribe(callback: (state: ProgressiveImageState) => void): Unsubscribe;\n getState(): ProgressiveImageState;\n destroy(): void;\n}\n\n/**\n * Blur-up / placeholder helper: reports the (typically tiny, inlined) placeholder immediately,\n * preloads the full-size image in the background, and reports the swap once it's ready.\n * Framework-agnostic — `lowdata/react`'s `useProgressiveImage` is a thin hook over this.\n */\nexport function createProgressiveImageLoader(\n options: ProgressiveImageLoaderOptions,\n): ProgressiveImageLoader {\n const emitter = new Emitter<ProgressiveImageState>();\n let state: ProgressiveImageState = { src: options.placeholder, isLoaded: false };\n let disposed = false;\n\n const img = typeof Image !== 'undefined' ? new Image() : undefined;\n if (img) {\n img.onload = () => {\n if (disposed) return;\n state = { src: options.src, isLoaded: true };\n emitter.emit(state);\n };\n img.src = options.src;\n } else {\n // No Image constructor available (SSR) — nothing to preload; report the target src directly.\n state = { src: options.src, isLoaded: true };\n }\n\n return {\n subscribe: (callback) => emitter.subscribe(callback),\n getState: () => state,\n destroy: () => {\n disposed = true;\n if (img) img.onload = null;\n emitter.clear();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/events.ts","../src/core/connection.ts","../src/media/qualityPolicy.ts","../src/media/compressImage.ts","../src/media/progressiveImage.ts"],"names":[],"mappings":"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","file":"media.cjs","sourcesContent":["/** Tiny typed pub-sub. No Node `EventEmitter` dependency, works in any JS runtime. */\nexport class Emitter<T> {\n private listeners = new Set<(value: T) => void>();\n\n subscribe(listener: (value: T) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n emit(value: T): void {\n for (const listener of this.listeners) {\n listener(value);\n }\n }\n\n clear(): void {\n this.listeners.clear();\n }\n\n get size(): number {\n return this.listeners.size;\n }\n}\n","import { Emitter } from './events.js';\nimport type {\n ConnectionInfo,\n ConnectionListener,\n ConnectionQuality,\n Unsubscribe,\n} from './types.js';\n\n/**\n * The Network Information API (`navigator.connection`) is Chromium-only and not part of the\n * standard DOM lib types, hence this local shape instead of a global augmentation.\n */\ninterface NetworkInformationLike {\n effectiveType?: '4g' | '3g' | '2g' | 'slow-2g';\n downlink?: number;\n rtt?: number;\n saveData?: boolean;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ntype NavigatorWithConnection = Navigator & { connection?: NetworkInformationLike };\n\nexport interface ConnectionMonitorOptions {\n /**\n * Opt-in latency probe URL used to detect 'slow' connections on browsers without the Network\n * Information API (Safari, Firefox). Never probed automatically on a timer — only once on\n * startup and once per reconnect — because every probe costs a little data.\n */\n pingUrl?: string;\n /** Round-trip time above which the ping probe classifies the connection as 'slow'. Default 600ms. */\n slowRttThresholdMs?: number;\n /** downlink (Mbps) below which the Network Information API classifies as 'slow'. Default 0.5. */\n slowDownlinkMbps?: number;\n /** Timeout for the ping probe itself, in ms. Default 5000. */\n pingTimeoutMs?: number;\n}\n\nconst DEFAULT_SLOW_RTT_MS = 600;\nconst DEFAULT_SLOW_DOWNLINK_MBPS = 0.5;\nconst DEFAULT_PING_TIMEOUT_MS = 5000;\n\nfunction hasNavigator(): boolean {\n return typeof navigator !== 'undefined';\n}\n\n/**\n * Tracks connection quality (online / slow / offline) using the best signal available:\n * `navigator.onLine` + online/offline events as the universal baseline, `navigator.connection`\n * where present, and an optional opt-in ping probe as a cross-browser fallback for 'slow'.\n */\nexport class ConnectionMonitor {\n private emitter = new Emitter<ConnectionInfo>();\n private current: ConnectionInfo;\n private probedRttMs: number | undefined;\n private readonly options: Required<\n Pick<ConnectionMonitorOptions, 'slowRttThresholdMs' | 'slowDownlinkMbps' | 'pingTimeoutMs'>\n > &\n Pick<ConnectionMonitorOptions, 'pingUrl'>;\n private disposed = false;\n private onlineHandler?: () => void;\n private offlineHandler?: () => void;\n private connectionChangeHandler?: () => void;\n\n constructor(options: ConnectionMonitorOptions = {}) {\n this.options = {\n pingUrl: options.pingUrl,\n slowRttThresholdMs: options.slowRttThresholdMs ?? DEFAULT_SLOW_RTT_MS,\n slowDownlinkMbps: options.slowDownlinkMbps ?? DEFAULT_SLOW_DOWNLINK_MBPS,\n pingTimeoutMs: options.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS,\n };\n this.current = this.computeInfo();\n\n if (hasNavigator() && typeof window !== 'undefined') {\n this.onlineHandler = () => {\n this.refresh();\n void this.probeNow();\n };\n this.offlineHandler = () => this.refresh();\n window.addEventListener('online', this.onlineHandler);\n window.addEventListener('offline', this.offlineHandler);\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn?.addEventListener) {\n this.connectionChangeHandler = () => this.refresh();\n conn.addEventListener('change', this.connectionChangeHandler);\n }\n }\n\n if (this.options.pingUrl) {\n void this.probeNow();\n }\n }\n\n getStatus(): ConnectionInfo {\n return this.current;\n }\n\n subscribe(listener: ConnectionListener): Unsubscribe {\n return this.emitter.subscribe(listener);\n }\n\n /** Manually re-run the opt-in ping probe (no-op if no `pingUrl` was configured). */\n async probeNow(): Promise<ConnectionInfo> {\n if (!this.options.pingUrl || !hasNavigator() || navigator.onLine === false) {\n return this.current;\n }\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.options.pingTimeoutMs);\n try {\n await fetch(this.options.pingUrl, {\n method: 'HEAD',\n cache: 'no-store',\n signal: controller.signal,\n });\n this.probedRttMs = Date.now() - start;\n } catch {\n // A failed probe tells us nothing reliable about latency; leave prior measurement as-is.\n } finally {\n clearTimeout(timer);\n }\n return this.refresh();\n }\n\n destroy(): void {\n if (this.disposed) return;\n this.disposed = true;\n if (typeof window !== 'undefined') {\n if (this.onlineHandler) window.removeEventListener('online', this.onlineHandler);\n if (this.offlineHandler) window.removeEventListener('offline', this.offlineHandler);\n }\n if (hasNavigator() && this.connectionChangeHandler) {\n const conn = (navigator as NavigatorWithConnection).connection;\n conn?.removeEventListener?.('change', this.connectionChangeHandler);\n }\n this.emitter.clear();\n }\n\n private refresh(): ConnectionInfo {\n this.current = this.computeInfo();\n this.emitter.emit(this.current);\n return this.current;\n }\n\n private computeInfo(): ConnectionInfo {\n if (!hasNavigator()) {\n // Server/SSR context: assume the best case rather than block anything.\n return { quality: 'online', online: true };\n }\n\n const online = navigator.onLine !== false;\n if (!online) {\n return { quality: 'offline', online: false };\n }\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n const info: ConnectionInfo = {\n quality: 'online',\n online: true,\n effectiveType: conn.effectiveType,\n downlinkMbps: conn.downlink,\n rttMs: conn.rtt,\n saveData: conn.saveData,\n };\n const isSlow =\n conn.saveData === true ||\n conn.effectiveType === '2g' ||\n conn.effectiveType === 'slow-2g' ||\n (typeof conn.downlink === 'number' && conn.downlink < this.options.slowDownlinkMbps) ||\n (typeof conn.rtt === 'number' && conn.rtt > this.options.slowRttThresholdMs);\n return { ...info, quality: isSlow ? 'slow' : 'online' };\n }\n\n // No Network Information API: fall back to the opt-in ping probe measurement, if any.\n const quality: ConnectionQuality =\n typeof this.probedRttMs === 'number' && this.probedRttMs > this.options.slowRttThresholdMs\n ? 'slow'\n : 'online';\n return {\n quality,\n online: true,\n rttMs: this.probedRttMs,\n };\n }\n}\n\nlet sharedMonitor: ConnectionMonitor | undefined;\n\nfunction getSharedMonitor(): ConnectionMonitor {\n if (!sharedMonitor) sharedMonitor = new ConnectionMonitor();\n return sharedMonitor;\n}\n\n/** Convenience one-shot read of connection quality, without owning a `LowdataClient`. */\nexport function getConnectionQuality(): ConnectionInfo {\n return getSharedMonitor().getStatus();\n}\n\n/** Convenience subscription to connection changes, without owning a `LowdataClient`. */\nexport function onConnectionChange(listener: ConnectionListener): Unsubscribe {\n return getSharedMonitor().subscribe(listener);\n}\n","import type { ConnectionQuality } from '../core/types.js';\nimport type { ImageCompressionOptions } from './types.js';\n\nexport type CompressionPreset = Required<Pick<ImageCompressionOptions, 'maxWidth' | 'quality'>> &\n Pick<ImageCompressionOptions, 'targetSizeKB'>;\n\n/**\n * Aggressive-but-usable presets by connection quality. 'offline' is deliberately the smallest —\n * an image compressed while offline is headed straight into the persistent queue, where every\n * extra KB counts against storage quota and, eventually, the user's data bundle.\n */\nconst PRESETS: Record<ConnectionQuality, CompressionPreset> = {\n online: { maxWidth: 1600, quality: 0.72 },\n slow: { maxWidth: 1000, quality: 0.5, targetSizeKB: 150 },\n offline: { maxWidth: 800, quality: 0.4, targetSizeKB: 80 },\n};\n\nexport function presetForQuality(quality: ConnectionQuality): CompressionPreset {\n return PRESETS[quality];\n}\n","import { getConnectionQuality } from '../core/connection.js';\nimport { presetForQuality } from './qualityPolicy.js';\nimport type { CompressedImageResult, ImageCompressionOptions } from './types.js';\n\nconst DEFAULT_MAX_WIDTH = 1280;\nconst DEFAULT_QUALITY = 0.6;\nconst DEFAULT_MIME: NonNullable<ImageCompressionOptions['mimeType']> = 'image/jpeg';\nconst MIN_QUALITY = 0.35;\nconst QUALITY_STEP = 0.1;\nconst MAX_QUALITY_ITERATIONS = 5;\n\ntype ImageSource = ImageBitmap | HTMLImageElement;\n\nasync function loadImageSource(file: File | Blob): Promise<ImageSource> {\n if (typeof createImageBitmap === 'function') {\n // `imageOrientation: 'from-image'` is the spec-defined way to force EXIF-orientation-correct\n // decoding regardless of a given browser's default — without it, a photo taken sideways on a\n // phone can come out of `drawImage()` still sideways, since canvas pixel data (unlike an\n // `<img>` element's own on-screen rendering) doesn't reliably apply EXIF orientation itself.\n return createImageBitmap(file, { imageOrientation: 'from-image' });\n }\n // Fallback for environments without createImageBitmap (older Safari).\n return new Promise<HTMLImageElement>((resolve, reject) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve(img);\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('lowdata: failed to load image for compression'));\n };\n img.src = url;\n });\n}\n\nfunction computeTargetDimensions(\n sourceWidth: number,\n sourceHeight: number,\n maxWidth: number,\n maxHeight?: number,\n): { width: number; height: number } {\n let width = sourceWidth;\n let height = sourceHeight;\n\n if (width > maxWidth) {\n height = Math.round((height * maxWidth) / width);\n width = maxWidth;\n }\n if (maxHeight && height > maxHeight) {\n width = Math.round((width * maxHeight) / height);\n height = maxHeight;\n }\n return { width, height };\n}\n\nfunction drawToCanvas(source: ImageSource, width: number, height: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('lowdata: canvas 2D context is not available in this environment');\n ctx.drawImage(source, 0, 0, width, height);\n return canvas;\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement, mimeType: string, quality: number): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob);\n else reject(new Error('lowdata: canvas failed to produce an image blob'));\n },\n mimeType,\n quality,\n );\n });\n}\n\n/**\n * Client-side image compression via the Canvas API: downscales to a max dimension and re-encodes\n * at a target quality, optionally iterating quality further down to hit a byte budget. No\n * dependency — avoids pulling in a native/wasm image library, keeping the `lowdata/media` subpath\n * tiny. Known v1 limitation: EXIF orientation can be lost on re-encode in some browsers.\n */\nexport async function compressImage(\n file: File | Blob,\n options: ImageCompressionOptions = {},\n): Promise<CompressedImageResult> {\n const preset = options.connectionAware\n ? presetForQuality(getConnectionQuality().quality)\n : undefined;\n const maxWidth = options.maxWidth ?? preset?.maxWidth ?? DEFAULT_MAX_WIDTH;\n const maxHeight = options.maxHeight;\n const mimeType = options.mimeType ?? DEFAULT_MIME;\n const targetSizeKB = options.targetSizeKB ?? preset?.targetSizeKB;\n let quality = options.quality ?? preset?.quality ?? DEFAULT_QUALITY;\n\n const source = await loadImageSource(file);\n const { width, height } = computeTargetDimensions(\n source.width,\n source.height,\n maxWidth,\n maxHeight,\n );\n const canvas = drawToCanvas(source, width, height);\n if ('close' in source && typeof source.close === 'function') source.close();\n\n let blob = await canvasToBlob(canvas, mimeType, quality);\n\n if (targetSizeKB) {\n const targetBytes = targetSizeKB * 1024;\n let iterations = 0;\n while (\n blob.size > targetBytes &&\n quality > MIN_QUALITY &&\n iterations < MAX_QUALITY_ITERATIONS\n ) {\n quality = Math.max(MIN_QUALITY, quality - QUALITY_STEP);\n blob = await canvasToBlob(canvas, mimeType, quality);\n iterations++;\n }\n }\n\n return { blob, width, height, sizeBytes: blob.size, quality };\n}\n","import { Emitter } from '../core/events.js';\nimport type { Unsubscribe } from '../core/types.js';\n\nexport interface ProgressiveImageState {\n src: string;\n isLoaded: boolean;\n}\n\nexport interface ProgressiveImageLoaderOptions {\n src: string;\n placeholder: string;\n}\n\nexport interface ProgressiveImageLoader {\n subscribe(callback: (state: ProgressiveImageState) => void): Unsubscribe;\n getState(): ProgressiveImageState;\n destroy(): void;\n}\n\n/**\n * Blur-up / placeholder helper: reports the (typically tiny, inlined) placeholder immediately,\n * preloads the full-size image in the background, and reports the swap once it's ready.\n * Framework-agnostic — `lowdata/react`'s `useProgressiveImage` is a thin hook over this.\n */\nexport function createProgressiveImageLoader(\n options: ProgressiveImageLoaderOptions,\n): ProgressiveImageLoader {\n const emitter = new Emitter<ProgressiveImageState>();\n let state: ProgressiveImageState = { src: options.placeholder, isLoaded: false };\n let disposed = false;\n\n const img = typeof Image !== 'undefined' ? new Image() : undefined;\n if (img) {\n img.onload = () => {\n if (disposed) return;\n state = { src: options.src, isLoaded: true };\n emitter.emit(state);\n };\n img.src = options.src;\n } else {\n // No Image constructor available (SSR) — nothing to preload; report the target src directly.\n state = { src: options.src, isLoaded: true };\n }\n\n return {\n subscribe: (callback) => emitter.subscribe(callback),\n getState: () => state,\n destroy: () => {\n disposed = true;\n if (img) img.onload = null;\n emitter.clear();\n },\n };\n}\n"]}
|
package/dist/media.d.cts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as ConnectionQuality } from './types
|
|
2
|
-
export { P as ProgressiveImageLoader, a as ProgressiveImageLoaderOptions, b as ProgressiveImageState, c as createProgressiveImageLoader } from './progressiveImage-
|
|
1
|
+
import { b as ConnectionQuality } from './types-D6fjl8Wy.cjs';
|
|
2
|
+
export { P as ProgressiveImageLoader, a as ProgressiveImageLoaderOptions, b as ProgressiveImageState, c as createProgressiveImageLoader } from './progressiveImage-CDpz_rv3.cjs';
|
|
3
3
|
|
|
4
4
|
interface ImageCompressionOptions {
|
|
5
5
|
/** Longest edge in pixels; the image is downscaled proportionally to fit. Default 1280. */
|
package/dist/media.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { b as ConnectionQuality } from './types
|
|
2
|
-
export { P as ProgressiveImageLoader, a as ProgressiveImageLoaderOptions, b as ProgressiveImageState, c as createProgressiveImageLoader } from './progressiveImage-
|
|
1
|
+
import { b as ConnectionQuality } from './types-D6fjl8Wy.js';
|
|
2
|
+
export { P as ProgressiveImageLoader, a as ProgressiveImageLoaderOptions, b as ProgressiveImageState, c as createProgressiveImageLoader } from './progressiveImage-BOdic4dJ.js';
|
|
3
3
|
|
|
4
4
|
interface ImageCompressionOptions {
|
|
5
5
|
/** Longest edge in pixels; the image is downscaled proportionally to fit. Default 1280. */
|
package/dist/media.js
CHANGED
|
@@ -162,7 +162,7 @@ var QUALITY_STEP = 0.1;
|
|
|
162
162
|
var MAX_QUALITY_ITERATIONS = 5;
|
|
163
163
|
async function loadImageSource(file) {
|
|
164
164
|
if (typeof createImageBitmap === "function") {
|
|
165
|
-
return createImageBitmap(file);
|
|
165
|
+
return createImageBitmap(file, { imageOrientation: "from-image" });
|
|
166
166
|
}
|
|
167
167
|
return new Promise((resolve, reject) => {
|
|
168
168
|
const url = URL.createObjectURL(file);
|
package/dist/media.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/core/events.ts","../src/core/connection.ts","../src/media/qualityPolicy.ts","../src/media/compressImage.ts","../src/media/progressiveImage.ts"],"names":[],"mappings":"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","file":"media.js","sourcesContent":["/** Tiny typed pub-sub. No Node `EventEmitter` dependency, works in any JS runtime. */\nexport class Emitter<T> {\n private listeners = new Set<(value: T) => void>();\n\n subscribe(listener: (value: T) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n emit(value: T): void {\n for (const listener of this.listeners) {\n listener(value);\n }\n }\n\n clear(): void {\n this.listeners.clear();\n }\n\n get size(): number {\n return this.listeners.size;\n }\n}\n","import { Emitter } from './events.js';\nimport type {\n ConnectionInfo,\n ConnectionListener,\n ConnectionQuality,\n Unsubscribe,\n} from './types.js';\n\n/**\n * The Network Information API (`navigator.connection`) is Chromium-only and not part of the\n * standard DOM lib types, hence this local shape instead of a global augmentation.\n */\ninterface NetworkInformationLike {\n effectiveType?: '4g' | '3g' | '2g' | 'slow-2g';\n downlink?: number;\n rtt?: number;\n saveData?: boolean;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ntype NavigatorWithConnection = Navigator & { connection?: NetworkInformationLike };\n\nexport interface ConnectionMonitorOptions {\n /**\n * Opt-in latency probe URL used to detect 'slow' connections on browsers without the Network\n * Information API (Safari, Firefox). Never probed automatically on a timer — only once on\n * startup and once per reconnect — because every probe costs a little data.\n */\n pingUrl?: string;\n /** Round-trip time above which the ping probe classifies the connection as 'slow'. Default 600ms. */\n slowRttThresholdMs?: number;\n /** downlink (Mbps) below which the Network Information API classifies as 'slow'. Default 0.5. */\n slowDownlinkMbps?: number;\n /** Timeout for the ping probe itself, in ms. Default 5000. */\n pingTimeoutMs?: number;\n}\n\nconst DEFAULT_SLOW_RTT_MS = 600;\nconst DEFAULT_SLOW_DOWNLINK_MBPS = 0.5;\nconst DEFAULT_PING_TIMEOUT_MS = 5000;\n\nfunction hasNavigator(): boolean {\n return typeof navigator !== 'undefined';\n}\n\n/**\n * Tracks connection quality (online / slow / offline) using the best signal available:\n * `navigator.onLine` + online/offline events as the universal baseline, `navigator.connection`\n * where present, and an optional opt-in ping probe as a cross-browser fallback for 'slow'.\n */\nexport class ConnectionMonitor {\n private emitter = new Emitter<ConnectionInfo>();\n private current: ConnectionInfo;\n private probedRttMs: number | undefined;\n private readonly options: Required<\n Pick<ConnectionMonitorOptions, 'slowRttThresholdMs' | 'slowDownlinkMbps' | 'pingTimeoutMs'>\n > &\n Pick<ConnectionMonitorOptions, 'pingUrl'>;\n private disposed = false;\n private onlineHandler?: () => void;\n private offlineHandler?: () => void;\n private connectionChangeHandler?: () => void;\n\n constructor(options: ConnectionMonitorOptions = {}) {\n this.options = {\n pingUrl: options.pingUrl,\n slowRttThresholdMs: options.slowRttThresholdMs ?? DEFAULT_SLOW_RTT_MS,\n slowDownlinkMbps: options.slowDownlinkMbps ?? DEFAULT_SLOW_DOWNLINK_MBPS,\n pingTimeoutMs: options.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS,\n };\n this.current = this.computeInfo();\n\n if (hasNavigator() && typeof window !== 'undefined') {\n this.onlineHandler = () => {\n this.refresh();\n void this.probeNow();\n };\n this.offlineHandler = () => this.refresh();\n window.addEventListener('online', this.onlineHandler);\n window.addEventListener('offline', this.offlineHandler);\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn?.addEventListener) {\n this.connectionChangeHandler = () => this.refresh();\n conn.addEventListener('change', this.connectionChangeHandler);\n }\n }\n\n if (this.options.pingUrl) {\n void this.probeNow();\n }\n }\n\n getStatus(): ConnectionInfo {\n return this.current;\n }\n\n subscribe(listener: ConnectionListener): Unsubscribe {\n return this.emitter.subscribe(listener);\n }\n\n /** Manually re-run the opt-in ping probe (no-op if no `pingUrl` was configured). */\n async probeNow(): Promise<ConnectionInfo> {\n if (!this.options.pingUrl || !hasNavigator() || navigator.onLine === false) {\n return this.current;\n }\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.options.pingTimeoutMs);\n try {\n await fetch(this.options.pingUrl, {\n method: 'HEAD',\n cache: 'no-store',\n signal: controller.signal,\n });\n this.probedRttMs = Date.now() - start;\n } catch {\n // A failed probe tells us nothing reliable about latency; leave prior measurement as-is.\n } finally {\n clearTimeout(timer);\n }\n return this.refresh();\n }\n\n destroy(): void {\n if (this.disposed) return;\n this.disposed = true;\n if (typeof window !== 'undefined') {\n if (this.onlineHandler) window.removeEventListener('online', this.onlineHandler);\n if (this.offlineHandler) window.removeEventListener('offline', this.offlineHandler);\n }\n if (hasNavigator() && this.connectionChangeHandler) {\n const conn = (navigator as NavigatorWithConnection).connection;\n conn?.removeEventListener?.('change', this.connectionChangeHandler);\n }\n this.emitter.clear();\n }\n\n private refresh(): ConnectionInfo {\n this.current = this.computeInfo();\n this.emitter.emit(this.current);\n return this.current;\n }\n\n private computeInfo(): ConnectionInfo {\n if (!hasNavigator()) {\n // Server/SSR context: assume the best case rather than block anything.\n return { quality: 'online', online: true };\n }\n\n const online = navigator.onLine !== false;\n if (!online) {\n return { quality: 'offline', online: false };\n }\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n const info: ConnectionInfo = {\n quality: 'online',\n online: true,\n effectiveType: conn.effectiveType,\n downlinkMbps: conn.downlink,\n rttMs: conn.rtt,\n saveData: conn.saveData,\n };\n const isSlow =\n conn.saveData === true ||\n conn.effectiveType === '2g' ||\n conn.effectiveType === 'slow-2g' ||\n (typeof conn.downlink === 'number' && conn.downlink < this.options.slowDownlinkMbps) ||\n (typeof conn.rtt === 'number' && conn.rtt > this.options.slowRttThresholdMs);\n return { ...info, quality: isSlow ? 'slow' : 'online' };\n }\n\n // No Network Information API: fall back to the opt-in ping probe measurement, if any.\n const quality: ConnectionQuality =\n typeof this.probedRttMs === 'number' && this.probedRttMs > this.options.slowRttThresholdMs\n ? 'slow'\n : 'online';\n return {\n quality,\n online: true,\n rttMs: this.probedRttMs,\n };\n }\n}\n\nlet sharedMonitor: ConnectionMonitor | undefined;\n\nfunction getSharedMonitor(): ConnectionMonitor {\n if (!sharedMonitor) sharedMonitor = new ConnectionMonitor();\n return sharedMonitor;\n}\n\n/** Convenience one-shot read of connection quality, without owning a `LowdataClient`. */\nexport function getConnectionQuality(): ConnectionInfo {\n return getSharedMonitor().getStatus();\n}\n\n/** Convenience subscription to connection changes, without owning a `LowdataClient`. */\nexport function onConnectionChange(listener: ConnectionListener): Unsubscribe {\n return getSharedMonitor().subscribe(listener);\n}\n","import type { ConnectionQuality } from '../core/types.js';\nimport type { ImageCompressionOptions } from './types.js';\n\nexport type CompressionPreset = Required<Pick<ImageCompressionOptions, 'maxWidth' | 'quality'>> &\n Pick<ImageCompressionOptions, 'targetSizeKB'>;\n\n/**\n * Aggressive-but-usable presets by connection quality. 'offline' is deliberately the smallest —\n * an image compressed while offline is headed straight into the persistent queue, where every\n * extra KB counts against storage quota and, eventually, the user's data bundle.\n */\nconst PRESETS: Record<ConnectionQuality, CompressionPreset> = {\n online: { maxWidth: 1600, quality: 0.72 },\n slow: { maxWidth: 1000, quality: 0.5, targetSizeKB: 150 },\n offline: { maxWidth: 800, quality: 0.4, targetSizeKB: 80 },\n};\n\nexport function presetForQuality(quality: ConnectionQuality): CompressionPreset {\n return PRESETS[quality];\n}\n","import { getConnectionQuality } from '../core/connection.js';\nimport { presetForQuality } from './qualityPolicy.js';\nimport type { CompressedImageResult, ImageCompressionOptions } from './types.js';\n\nconst DEFAULT_MAX_WIDTH = 1280;\nconst DEFAULT_QUALITY = 0.6;\nconst DEFAULT_MIME: NonNullable<ImageCompressionOptions['mimeType']> = 'image/jpeg';\nconst MIN_QUALITY = 0.35;\nconst QUALITY_STEP = 0.1;\nconst MAX_QUALITY_ITERATIONS = 5;\n\ntype ImageSource = ImageBitmap | HTMLImageElement;\n\nasync function loadImageSource(file: File | Blob): Promise<ImageSource> {\n if (typeof createImageBitmap === 'function') {\n return createImageBitmap(file);\n }\n // Fallback for environments without createImageBitmap (older Safari).\n return new Promise<HTMLImageElement>((resolve, reject) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve(img);\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('lowdata: failed to load image for compression'));\n };\n img.src = url;\n });\n}\n\nfunction computeTargetDimensions(\n sourceWidth: number,\n sourceHeight: number,\n maxWidth: number,\n maxHeight?: number,\n): { width: number; height: number } {\n let width = sourceWidth;\n let height = sourceHeight;\n\n if (width > maxWidth) {\n height = Math.round((height * maxWidth) / width);\n width = maxWidth;\n }\n if (maxHeight && height > maxHeight) {\n width = Math.round((width * maxHeight) / height);\n height = maxHeight;\n }\n return { width, height };\n}\n\nfunction drawToCanvas(source: ImageSource, width: number, height: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('lowdata: canvas 2D context is not available in this environment');\n ctx.drawImage(source, 0, 0, width, height);\n return canvas;\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement, mimeType: string, quality: number): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob);\n else reject(new Error('lowdata: canvas failed to produce an image blob'));\n },\n mimeType,\n quality,\n );\n });\n}\n\n/**\n * Client-side image compression via the Canvas API: downscales to a max dimension and re-encodes\n * at a target quality, optionally iterating quality further down to hit a byte budget. No\n * dependency — avoids pulling in a native/wasm image library, keeping the `lowdata/media` subpath\n * tiny. Known v1 limitation: EXIF orientation can be lost on re-encode in some browsers.\n */\nexport async function compressImage(\n file: File | Blob,\n options: ImageCompressionOptions = {},\n): Promise<CompressedImageResult> {\n const preset = options.connectionAware\n ? presetForQuality(getConnectionQuality().quality)\n : undefined;\n const maxWidth = options.maxWidth ?? preset?.maxWidth ?? DEFAULT_MAX_WIDTH;\n const maxHeight = options.maxHeight;\n const mimeType = options.mimeType ?? DEFAULT_MIME;\n const targetSizeKB = options.targetSizeKB ?? preset?.targetSizeKB;\n let quality = options.quality ?? preset?.quality ?? DEFAULT_QUALITY;\n\n const source = await loadImageSource(file);\n const { width, height } = computeTargetDimensions(\n source.width,\n source.height,\n maxWidth,\n maxHeight,\n );\n const canvas = drawToCanvas(source, width, height);\n if ('close' in source && typeof source.close === 'function') source.close();\n\n let blob = await canvasToBlob(canvas, mimeType, quality);\n\n if (targetSizeKB) {\n const targetBytes = targetSizeKB * 1024;\n let iterations = 0;\n while (\n blob.size > targetBytes &&\n quality > MIN_QUALITY &&\n iterations < MAX_QUALITY_ITERATIONS\n ) {\n quality = Math.max(MIN_QUALITY, quality - QUALITY_STEP);\n blob = await canvasToBlob(canvas, mimeType, quality);\n iterations++;\n }\n }\n\n return { blob, width, height, sizeBytes: blob.size, quality };\n}\n","import { Emitter } from '../core/events.js';\nimport type { Unsubscribe } from '../core/types.js';\n\nexport interface ProgressiveImageState {\n src: string;\n isLoaded: boolean;\n}\n\nexport interface ProgressiveImageLoaderOptions {\n src: string;\n placeholder: string;\n}\n\nexport interface ProgressiveImageLoader {\n subscribe(callback: (state: ProgressiveImageState) => void): Unsubscribe;\n getState(): ProgressiveImageState;\n destroy(): void;\n}\n\n/**\n * Blur-up / placeholder helper: reports the (typically tiny, inlined) placeholder immediately,\n * preloads the full-size image in the background, and reports the swap once it's ready.\n * Framework-agnostic — `lowdata/react`'s `useProgressiveImage` is a thin hook over this.\n */\nexport function createProgressiveImageLoader(\n options: ProgressiveImageLoaderOptions,\n): ProgressiveImageLoader {\n const emitter = new Emitter<ProgressiveImageState>();\n let state: ProgressiveImageState = { src: options.placeholder, isLoaded: false };\n let disposed = false;\n\n const img = typeof Image !== 'undefined' ? new Image() : undefined;\n if (img) {\n img.onload = () => {\n if (disposed) return;\n state = { src: options.src, isLoaded: true };\n emitter.emit(state);\n };\n img.src = options.src;\n } else {\n // No Image constructor available (SSR) — nothing to preload; report the target src directly.\n state = { src: options.src, isLoaded: true };\n }\n\n return {\n subscribe: (callback) => emitter.subscribe(callback),\n getState: () => state,\n destroy: () => {\n disposed = true;\n if (img) img.onload = null;\n emitter.clear();\n },\n };\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/core/events.ts","../src/core/connection.ts","../src/media/qualityPolicy.ts","../src/media/compressImage.ts","../src/media/progressiveImage.ts"],"names":[],"mappings":"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","file":"media.js","sourcesContent":["/** Tiny typed pub-sub. No Node `EventEmitter` dependency, works in any JS runtime. */\nexport class Emitter<T> {\n private listeners = new Set<(value: T) => void>();\n\n subscribe(listener: (value: T) => void): () => void {\n this.listeners.add(listener);\n return () => {\n this.listeners.delete(listener);\n };\n }\n\n emit(value: T): void {\n for (const listener of this.listeners) {\n listener(value);\n }\n }\n\n clear(): void {\n this.listeners.clear();\n }\n\n get size(): number {\n return this.listeners.size;\n }\n}\n","import { Emitter } from './events.js';\nimport type {\n ConnectionInfo,\n ConnectionListener,\n ConnectionQuality,\n Unsubscribe,\n} from './types.js';\n\n/**\n * The Network Information API (`navigator.connection`) is Chromium-only and not part of the\n * standard DOM lib types, hence this local shape instead of a global augmentation.\n */\ninterface NetworkInformationLike {\n effectiveType?: '4g' | '3g' | '2g' | 'slow-2g';\n downlink?: number;\n rtt?: number;\n saveData?: boolean;\n addEventListener?: (type: 'change', listener: () => void) => void;\n removeEventListener?: (type: 'change', listener: () => void) => void;\n}\n\ntype NavigatorWithConnection = Navigator & { connection?: NetworkInformationLike };\n\nexport interface ConnectionMonitorOptions {\n /**\n * Opt-in latency probe URL used to detect 'slow' connections on browsers without the Network\n * Information API (Safari, Firefox). Never probed automatically on a timer — only once on\n * startup and once per reconnect — because every probe costs a little data.\n */\n pingUrl?: string;\n /** Round-trip time above which the ping probe classifies the connection as 'slow'. Default 600ms. */\n slowRttThresholdMs?: number;\n /** downlink (Mbps) below which the Network Information API classifies as 'slow'. Default 0.5. */\n slowDownlinkMbps?: number;\n /** Timeout for the ping probe itself, in ms. Default 5000. */\n pingTimeoutMs?: number;\n}\n\nconst DEFAULT_SLOW_RTT_MS = 600;\nconst DEFAULT_SLOW_DOWNLINK_MBPS = 0.5;\nconst DEFAULT_PING_TIMEOUT_MS = 5000;\n\nfunction hasNavigator(): boolean {\n return typeof navigator !== 'undefined';\n}\n\n/**\n * Tracks connection quality (online / slow / offline) using the best signal available:\n * `navigator.onLine` + online/offline events as the universal baseline, `navigator.connection`\n * where present, and an optional opt-in ping probe as a cross-browser fallback for 'slow'.\n */\nexport class ConnectionMonitor {\n private emitter = new Emitter<ConnectionInfo>();\n private current: ConnectionInfo;\n private probedRttMs: number | undefined;\n private readonly options: Required<\n Pick<ConnectionMonitorOptions, 'slowRttThresholdMs' | 'slowDownlinkMbps' | 'pingTimeoutMs'>\n > &\n Pick<ConnectionMonitorOptions, 'pingUrl'>;\n private disposed = false;\n private onlineHandler?: () => void;\n private offlineHandler?: () => void;\n private connectionChangeHandler?: () => void;\n\n constructor(options: ConnectionMonitorOptions = {}) {\n this.options = {\n pingUrl: options.pingUrl,\n slowRttThresholdMs: options.slowRttThresholdMs ?? DEFAULT_SLOW_RTT_MS,\n slowDownlinkMbps: options.slowDownlinkMbps ?? DEFAULT_SLOW_DOWNLINK_MBPS,\n pingTimeoutMs: options.pingTimeoutMs ?? DEFAULT_PING_TIMEOUT_MS,\n };\n this.current = this.computeInfo();\n\n if (hasNavigator() && typeof window !== 'undefined') {\n this.onlineHandler = () => {\n this.refresh();\n void this.probeNow();\n };\n this.offlineHandler = () => this.refresh();\n window.addEventListener('online', this.onlineHandler);\n window.addEventListener('offline', this.offlineHandler);\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn?.addEventListener) {\n this.connectionChangeHandler = () => this.refresh();\n conn.addEventListener('change', this.connectionChangeHandler);\n }\n }\n\n if (this.options.pingUrl) {\n void this.probeNow();\n }\n }\n\n getStatus(): ConnectionInfo {\n return this.current;\n }\n\n subscribe(listener: ConnectionListener): Unsubscribe {\n return this.emitter.subscribe(listener);\n }\n\n /** Manually re-run the opt-in ping probe (no-op if no `pingUrl` was configured). */\n async probeNow(): Promise<ConnectionInfo> {\n if (!this.options.pingUrl || !hasNavigator() || navigator.onLine === false) {\n return this.current;\n }\n const start = Date.now();\n const controller = new AbortController();\n const timer = setTimeout(() => controller.abort(), this.options.pingTimeoutMs);\n try {\n await fetch(this.options.pingUrl, {\n method: 'HEAD',\n cache: 'no-store',\n signal: controller.signal,\n });\n this.probedRttMs = Date.now() - start;\n } catch {\n // A failed probe tells us nothing reliable about latency; leave prior measurement as-is.\n } finally {\n clearTimeout(timer);\n }\n return this.refresh();\n }\n\n destroy(): void {\n if (this.disposed) return;\n this.disposed = true;\n if (typeof window !== 'undefined') {\n if (this.onlineHandler) window.removeEventListener('online', this.onlineHandler);\n if (this.offlineHandler) window.removeEventListener('offline', this.offlineHandler);\n }\n if (hasNavigator() && this.connectionChangeHandler) {\n const conn = (navigator as NavigatorWithConnection).connection;\n conn?.removeEventListener?.('change', this.connectionChangeHandler);\n }\n this.emitter.clear();\n }\n\n private refresh(): ConnectionInfo {\n this.current = this.computeInfo();\n this.emitter.emit(this.current);\n return this.current;\n }\n\n private computeInfo(): ConnectionInfo {\n if (!hasNavigator()) {\n // Server/SSR context: assume the best case rather than block anything.\n return { quality: 'online', online: true };\n }\n\n const online = navigator.onLine !== false;\n if (!online) {\n return { quality: 'offline', online: false };\n }\n\n const conn = (navigator as NavigatorWithConnection).connection;\n if (conn) {\n const info: ConnectionInfo = {\n quality: 'online',\n online: true,\n effectiveType: conn.effectiveType,\n downlinkMbps: conn.downlink,\n rttMs: conn.rtt,\n saveData: conn.saveData,\n };\n const isSlow =\n conn.saveData === true ||\n conn.effectiveType === '2g' ||\n conn.effectiveType === 'slow-2g' ||\n (typeof conn.downlink === 'number' && conn.downlink < this.options.slowDownlinkMbps) ||\n (typeof conn.rtt === 'number' && conn.rtt > this.options.slowRttThresholdMs);\n return { ...info, quality: isSlow ? 'slow' : 'online' };\n }\n\n // No Network Information API: fall back to the opt-in ping probe measurement, if any.\n const quality: ConnectionQuality =\n typeof this.probedRttMs === 'number' && this.probedRttMs > this.options.slowRttThresholdMs\n ? 'slow'\n : 'online';\n return {\n quality,\n online: true,\n rttMs: this.probedRttMs,\n };\n }\n}\n\nlet sharedMonitor: ConnectionMonitor | undefined;\n\nfunction getSharedMonitor(): ConnectionMonitor {\n if (!sharedMonitor) sharedMonitor = new ConnectionMonitor();\n return sharedMonitor;\n}\n\n/** Convenience one-shot read of connection quality, without owning a `LowdataClient`. */\nexport function getConnectionQuality(): ConnectionInfo {\n return getSharedMonitor().getStatus();\n}\n\n/** Convenience subscription to connection changes, without owning a `LowdataClient`. */\nexport function onConnectionChange(listener: ConnectionListener): Unsubscribe {\n return getSharedMonitor().subscribe(listener);\n}\n","import type { ConnectionQuality } from '../core/types.js';\nimport type { ImageCompressionOptions } from './types.js';\n\nexport type CompressionPreset = Required<Pick<ImageCompressionOptions, 'maxWidth' | 'quality'>> &\n Pick<ImageCompressionOptions, 'targetSizeKB'>;\n\n/**\n * Aggressive-but-usable presets by connection quality. 'offline' is deliberately the smallest —\n * an image compressed while offline is headed straight into the persistent queue, where every\n * extra KB counts against storage quota and, eventually, the user's data bundle.\n */\nconst PRESETS: Record<ConnectionQuality, CompressionPreset> = {\n online: { maxWidth: 1600, quality: 0.72 },\n slow: { maxWidth: 1000, quality: 0.5, targetSizeKB: 150 },\n offline: { maxWidth: 800, quality: 0.4, targetSizeKB: 80 },\n};\n\nexport function presetForQuality(quality: ConnectionQuality): CompressionPreset {\n return PRESETS[quality];\n}\n","import { getConnectionQuality } from '../core/connection.js';\nimport { presetForQuality } from './qualityPolicy.js';\nimport type { CompressedImageResult, ImageCompressionOptions } from './types.js';\n\nconst DEFAULT_MAX_WIDTH = 1280;\nconst DEFAULT_QUALITY = 0.6;\nconst DEFAULT_MIME: NonNullable<ImageCompressionOptions['mimeType']> = 'image/jpeg';\nconst MIN_QUALITY = 0.35;\nconst QUALITY_STEP = 0.1;\nconst MAX_QUALITY_ITERATIONS = 5;\n\ntype ImageSource = ImageBitmap | HTMLImageElement;\n\nasync function loadImageSource(file: File | Blob): Promise<ImageSource> {\n if (typeof createImageBitmap === 'function') {\n // `imageOrientation: 'from-image'` is the spec-defined way to force EXIF-orientation-correct\n // decoding regardless of a given browser's default — without it, a photo taken sideways on a\n // phone can come out of `drawImage()` still sideways, since canvas pixel data (unlike an\n // `<img>` element's own on-screen rendering) doesn't reliably apply EXIF orientation itself.\n return createImageBitmap(file, { imageOrientation: 'from-image' });\n }\n // Fallback for environments without createImageBitmap (older Safari).\n return new Promise<HTMLImageElement>((resolve, reject) => {\n const url = URL.createObjectURL(file);\n const img = new Image();\n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve(img);\n };\n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('lowdata: failed to load image for compression'));\n };\n img.src = url;\n });\n}\n\nfunction computeTargetDimensions(\n sourceWidth: number,\n sourceHeight: number,\n maxWidth: number,\n maxHeight?: number,\n): { width: number; height: number } {\n let width = sourceWidth;\n let height = sourceHeight;\n\n if (width > maxWidth) {\n height = Math.round((height * maxWidth) / width);\n width = maxWidth;\n }\n if (maxHeight && height > maxHeight) {\n width = Math.round((width * maxHeight) / height);\n height = maxHeight;\n }\n return { width, height };\n}\n\nfunction drawToCanvas(source: ImageSource, width: number, height: number): HTMLCanvasElement {\n const canvas = document.createElement('canvas');\n canvas.width = width;\n canvas.height = height;\n const ctx = canvas.getContext('2d');\n if (!ctx) throw new Error('lowdata: canvas 2D context is not available in this environment');\n ctx.drawImage(source, 0, 0, width, height);\n return canvas;\n}\n\nfunction canvasToBlob(canvas: HTMLCanvasElement, mimeType: string, quality: number): Promise<Blob> {\n return new Promise((resolve, reject) => {\n canvas.toBlob(\n (blob) => {\n if (blob) resolve(blob);\n else reject(new Error('lowdata: canvas failed to produce an image blob'));\n },\n mimeType,\n quality,\n );\n });\n}\n\n/**\n * Client-side image compression via the Canvas API: downscales to a max dimension and re-encodes\n * at a target quality, optionally iterating quality further down to hit a byte budget. No\n * dependency — avoids pulling in a native/wasm image library, keeping the `lowdata/media` subpath\n * tiny. Known v1 limitation: EXIF orientation can be lost on re-encode in some browsers.\n */\nexport async function compressImage(\n file: File | Blob,\n options: ImageCompressionOptions = {},\n): Promise<CompressedImageResult> {\n const preset = options.connectionAware\n ? presetForQuality(getConnectionQuality().quality)\n : undefined;\n const maxWidth = options.maxWidth ?? preset?.maxWidth ?? DEFAULT_MAX_WIDTH;\n const maxHeight = options.maxHeight;\n const mimeType = options.mimeType ?? DEFAULT_MIME;\n const targetSizeKB = options.targetSizeKB ?? preset?.targetSizeKB;\n let quality = options.quality ?? preset?.quality ?? DEFAULT_QUALITY;\n\n const source = await loadImageSource(file);\n const { width, height } = computeTargetDimensions(\n source.width,\n source.height,\n maxWidth,\n maxHeight,\n );\n const canvas = drawToCanvas(source, width, height);\n if ('close' in source && typeof source.close === 'function') source.close();\n\n let blob = await canvasToBlob(canvas, mimeType, quality);\n\n if (targetSizeKB) {\n const targetBytes = targetSizeKB * 1024;\n let iterations = 0;\n while (\n blob.size > targetBytes &&\n quality > MIN_QUALITY &&\n iterations < MAX_QUALITY_ITERATIONS\n ) {\n quality = Math.max(MIN_QUALITY, quality - QUALITY_STEP);\n blob = await canvasToBlob(canvas, mimeType, quality);\n iterations++;\n }\n }\n\n return { blob, width, height, sizeBytes: blob.size, quality };\n}\n","import { Emitter } from '../core/events.js';\nimport type { Unsubscribe } from '../core/types.js';\n\nexport interface ProgressiveImageState {\n src: string;\n isLoaded: boolean;\n}\n\nexport interface ProgressiveImageLoaderOptions {\n src: string;\n placeholder: string;\n}\n\nexport interface ProgressiveImageLoader {\n subscribe(callback: (state: ProgressiveImageState) => void): Unsubscribe;\n getState(): ProgressiveImageState;\n destroy(): void;\n}\n\n/**\n * Blur-up / placeholder helper: reports the (typically tiny, inlined) placeholder immediately,\n * preloads the full-size image in the background, and reports the swap once it's ready.\n * Framework-agnostic — `lowdata/react`'s `useProgressiveImage` is a thin hook over this.\n */\nexport function createProgressiveImageLoader(\n options: ProgressiveImageLoaderOptions,\n): ProgressiveImageLoader {\n const emitter = new Emitter<ProgressiveImageState>();\n let state: ProgressiveImageState = { src: options.placeholder, isLoaded: false };\n let disposed = false;\n\n const img = typeof Image !== 'undefined' ? new Image() : undefined;\n if (img) {\n img.onload = () => {\n if (disposed) return;\n state = { src: options.src, isLoaded: true };\n emitter.emit(state);\n };\n img.src = options.src;\n } else {\n // No Image constructor available (SSR) — nothing to preload; report the target src directly.\n state = { src: options.src, isLoaded: true };\n }\n\n return {\n subscribe: (callback) => emitter.subscribe(callback),\n getState: () => state,\n destroy: () => {\n disposed = true;\n if (img) img.onload = null;\n emitter.clear();\n },\n };\n}\n"]}
|
package/dist/network.cjs
CHANGED
|
@@ -272,16 +272,23 @@ function getSharedDb() {
|
|
|
272
272
|
}
|
|
273
273
|
return sharedDbPromise;
|
|
274
274
|
}
|
|
275
|
-
function createDbFallbackAccessor(getDb) {
|
|
275
|
+
function createDbFallbackAccessor(getDb, onError) {
|
|
276
276
|
let dbAvailable = true;
|
|
277
277
|
return {
|
|
278
278
|
async run(fn, fallback) {
|
|
279
279
|
if (!dbAvailable) return fallback();
|
|
280
|
+
let db;
|
|
280
281
|
try {
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
} catch {
|
|
282
|
+
db = await getDb();
|
|
283
|
+
} catch (error) {
|
|
284
284
|
dbAvailable = false;
|
|
285
|
+
onError == null ? void 0 : onError(error, { scope: "db-open" });
|
|
286
|
+
return fallback();
|
|
287
|
+
}
|
|
288
|
+
try {
|
|
289
|
+
return await fn(db);
|
|
290
|
+
} catch (error) {
|
|
291
|
+
onError == null ? void 0 : onError(error, { scope: "db-operation" });
|
|
285
292
|
return fallback();
|
|
286
293
|
}
|
|
287
294
|
},
|
|
@@ -307,9 +314,9 @@ var LowdataRequestError = class extends Error {
|
|
|
307
314
|
var STORE = "queue";
|
|
308
315
|
var PRIORITY_ORDER = { high: 0, normal: 1, low: 2 };
|
|
309
316
|
var RequestQueue = class {
|
|
310
|
-
constructor(getDb) {
|
|
317
|
+
constructor(getDb, onError) {
|
|
311
318
|
this.memory = /* @__PURE__ */ new Map();
|
|
312
|
-
this.accessor = createDbFallbackAccessor(getDb);
|
|
319
|
+
this.accessor = createDbFallbackAccessor(getDb, onError);
|
|
313
320
|
}
|
|
314
321
|
isPersistent() {
|
|
315
322
|
return this.accessor.isPersistent();
|
|
@@ -624,12 +631,16 @@ var SyncManager = class {
|
|
|
624
631
|
* or safety poll simply tries again.
|
|
625
632
|
*/
|
|
626
633
|
async drain() {
|
|
627
|
-
var _a, _b, _c, _d;
|
|
634
|
+
var _a, _b, _c, _d, _e, _f;
|
|
628
635
|
if (this.draining || this.disposed) return;
|
|
629
636
|
if (this.opts.connection.getStatus().quality === "offline") return;
|
|
630
637
|
this.draining = true;
|
|
631
638
|
try {
|
|
632
|
-
const db = await this.opts.getDb().catch(() =>
|
|
639
|
+
const db = await this.opts.getDb().catch((error) => {
|
|
640
|
+
var _a2, _b2;
|
|
641
|
+
(_b2 = (_a2 = this.opts).onError) == null ? void 0 : _b2.call(_a2, error, { scope: "db-open" });
|
|
642
|
+
return void 0;
|
|
643
|
+
});
|
|
633
644
|
const lock = await acquireSyncLock(db, SYNC_LOCK_NAME, this.ownerId);
|
|
634
645
|
if (!lock) return;
|
|
635
646
|
let succeeded = 0;
|
|
@@ -652,7 +663,8 @@ var SyncManager = class {
|
|
|
652
663
|
await lock.release().catch(() => {
|
|
653
664
|
});
|
|
654
665
|
}
|
|
655
|
-
} catch {
|
|
666
|
+
} catch (error) {
|
|
667
|
+
(_f = (_e = this.opts).onError) == null ? void 0 : _f.call(_e, error, { scope: "sync" });
|
|
656
668
|
} finally {
|
|
657
669
|
this.draining = false;
|
|
658
670
|
}
|
|
@@ -738,6 +750,9 @@ var MUTATING_METHODS = /* @__PURE__ */ new Set(["POST", "PUT", "PATCH", "DELETE"
|
|
|
738
750
|
function defaultShouldQueueOffline({ method }) {
|
|
739
751
|
return MUTATING_METHODS.has(method);
|
|
740
752
|
}
|
|
753
|
+
var defaultOnError = (error, { scope }) => {
|
|
754
|
+
console.warn(`lowdata: internal error (${scope})`, error);
|
|
755
|
+
};
|
|
741
756
|
function normalizeHeaders(headers) {
|
|
742
757
|
if (!headers) return void 0;
|
|
743
758
|
if (headers instanceof Headers) {
|
|
@@ -766,15 +781,18 @@ var LowdataClient = class {
|
|
|
766
781
|
this.config = config;
|
|
767
782
|
this.syncEmitter = new Emitter();
|
|
768
783
|
this.destroyed = false;
|
|
784
|
+
var _a;
|
|
785
|
+
const onError = (_a = config.onError) != null ? _a : defaultOnError;
|
|
769
786
|
this.monitor = new ConnectionMonitor(config.connection);
|
|
770
|
-
this.requestQueue = new RequestQueue(() => getSharedDb());
|
|
787
|
+
this.requestQueue = new RequestQueue(() => getSharedDb(), onError);
|
|
771
788
|
this.syncManager = new SyncManager({
|
|
772
789
|
queue: this.requestQueue,
|
|
773
790
|
connection: this.monitor,
|
|
774
791
|
getDb: () => getSharedDb(),
|
|
775
792
|
retryConfig: config.retry,
|
|
776
793
|
syncConcurrency: config.syncConcurrency,
|
|
777
|
-
onEvent: (event) => this.syncEmitter.emit(event)
|
|
794
|
+
onEvent: (event) => this.syncEmitter.emit(event),
|
|
795
|
+
onError
|
|
778
796
|
});
|
|
779
797
|
this.connection = {
|
|
780
798
|
getStatus: () => this.monitor.getStatus(),
|