@channel-state/core
Version:
Core-library for channel-state, providing framework-agnostic, zero-dependency state management with cross-context synchronization and persistence.
1 lines • 18.6 kB
Source Map (JSON)
{"version":3,"sources":["../src/ChannelState.ts"],"names":[],"mappings":"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","file":"index.cjs","sourcesContent":["/*\n * Copyright 2025 ronny1020\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\n/**\n * Represents the types of messages that can be sent between stores.\n * - 'STATE_REQUEST': A request for the current state of the store.\n * - 'STATE_UPDATE': An update to the state of the store.\n */\nexport type StoreBroadcastMessageType = 'STATE_REQUEST' | 'STATE_UPDATE'\n\n/**\n * Represents a message sent between stores.\n * @template T The type of the payload.\n * @remarks This interface is used for both sending and receiving messages between stores.\n **/\nexport interface StoreBroadcastMessage<T> {\n type: StoreBroadcastMessageType\n senderId: string\n payload: T\n}\n\n/**\n * Represents the status of the ChannelStore.\n * - 'initializing': The store is initializing.\n * - 'ready': The store is ready to be used.\n * - 'destroyed': The store has been destroyed.\n */\nexport type StoreStatus = 'initializing' | 'ready' | 'destroyed'\n\n/**\n * Callback function type for store status changes.\n */\nexport type StoreStatusCallback = (status: StoreStatus) => void\n\n/**\n * Callback function type for store changes.\n */\ntype StoreChangeCallback<T> = (value: T) => void\n\n/**\n * Options for configuring a ChannelStore instance.\n * @template T The type of the state managed by the store.\n */\nexport interface ChannelStoreOptions<T> {\n /**\n * The name of the channel. This is used for both BroadcastChannel and IndexedDB.\n * @remarks Required.\n */\n name: string\n /**\n * Whether the store should persist its state to IndexedDB.\n * @remarks Defaults to `false`.\n */\n persist?: boolean\n /**\n * The initial state of the store.\n * @remarks Required.\n */\n initial: T\n}\n\n/**\n * A class that manages and shares state across different browser tabs or windows\n * using BroadcastChannel and IndexedDB for persistence.\n * @template T The type of the state managed by the store.\n *\n * @property {StoreStatus} status The current status of the store, indicating its readiness and lifecycle phase.\n */\nexport class ChannelStore<T> {\n private _db: IDBDatabase | null = null\n private _subscribers = new Set<StoreChangeCallback<T>>()\n private _statusSubscribers = new Set<StoreStatusCallback>()\n private _value: T\n private readonly _name: string\n private readonly _persist: boolean\n private readonly _initial: T\n private readonly _channel: BroadcastChannel\n private readonly _dbKey = 'state' // Fixed key for storing the single state object\n private readonly _prefixedName: string\n private _instanceId: string\n private _initialStateRequestTimeout: ReturnType<typeof setTimeout> | null =\n null\n\n /**\n * The current status of the store.\n */\n status: StoreStatus = 'initializing'\n\n /**\n * Creates an instance of ChannelStore.\n * @param options The options for configuring the store.\n */\n constructor(options: ChannelStoreOptions<T>) {\n this._name = options.name\n this._persist = options.persist ?? false\n this._initial = options.initial\n this._prefixedName = `channel-state__${this._name}`\n this._instanceId = crypto.randomUUID()\n\n this._value = structuredClone(this._initial)\n\n this._channel = new BroadcastChannel(this._prefixedName)\n this._channel.addEventListener('message', this._handleBroadcast)\n\n if (this._persist) {\n this._initDB()\n } else {\n this._requestInitialStateFromOtherTabs()\n }\n }\n\n private _initDB() {\n if (this.status === 'destroyed') {\n return\n }\n const request = indexedDB.open(this._prefixedName, 1)\n\n request.onupgradeneeded = () => {\n const db = request.result\n if (!db.objectStoreNames.contains(this._prefixedName)) {\n db.createObjectStore(this._prefixedName)\n }\n }\n\n request.onsuccess = () => {\n this._db = request.result\n this._loadCacheFromDB()\n }\n\n request.onerror = () => {\n console.error('IndexedDB init failed:', request.error)\n this.status = 'ready' // Fallback to initial values cache\n this._notifyStatusSubscribers()\n }\n }\n\n private _loadCacheFromDB() {\n if (this.status === 'destroyed') {\n return\n }\n if (!this._db) return\n\n const tx = this._db.transaction(this._prefixedName, 'readonly')\n const store = tx.objectStore(this._prefixedName)\n\n const req = store.get(this._dbKey) as IDBRequest<T>\n req.onsuccess = () => {\n const val = req.result\n if (val === undefined) {\n // If no stored value, use initial value and set status to ready\n this.status = 'ready'\n this._notifySubscribers()\n this._notifyStatusSubscribers()\n } else {\n this._value = val\n this.status = 'ready'\n this._notifySubscribers()\n this._notifyStatusSubscribers()\n }\n }\n req.onerror = () => {\n // If IndexedDB read fails, request from other tabs\n this._requestInitialStateFromOtherTabs()\n }\n }\n\n private _requestInitialStateFromOtherTabs() {\n if (this.status === 'destroyed') {\n return\n }\n this._channel.postMessage({\n type: 'STATE_REQUEST',\n senderId: this._instanceId,\n })\n\n this._initialStateRequestTimeout = setTimeout(() => {\n if (this.status !== 'ready') {\n this.status = 'ready'\n this._notifySubscribers()\n this._notifyStatusSubscribers()\n }\n this._initialStateRequestTimeout = null\n }, 500) // Wait for 500ms for a response\n }\n\n /**\n * Handles messages received from the BroadcastChannel, updating the cache and notifying subscribers.\n * @param messageEvent The MessageEvent containing the broadcasted data.\n * @private\n */\n private _handleBroadcast = (\n messageEvent: MessageEvent<StoreBroadcastMessage<T>>,\n ) => {\n if (this.status === 'destroyed') {\n return\n }\n\n const message = messageEvent.data\n\n if (message.senderId === this._instanceId) {\n return // Ignore messages from self\n }\n\n if (message.type === 'STATE_REQUEST') {\n this._channel.postMessage({\n type: 'STATE_UPDATE',\n payload: this._value,\n senderId: this._instanceId,\n })\n\n return\n }\n\n if (this._initialStateRequestTimeout) {\n clearTimeout(this._initialStateRequestTimeout)\n this._initialStateRequestTimeout = null\n }\n this._value = message.payload\n this.status = 'ready'\n this._notifySubscribers()\n this._notifyStatusSubscribers()\n }\n\n /**\n * Notifies all registered subscribers about a change in the store's state.\n * @private\n */\n private _notifySubscribers() {\n this._subscribers.forEach((subscriber) => {\n subscriber(this._value)\n })\n }\n\n private _notifyStatusSubscribers() {\n this._statusSubscribers.forEach((subscriber) => {\n subscriber(this.status)\n })\n }\n\n /**\n * Triggers a change notification by posting the current cache to the BroadcastChannel\n * and notifying local subscribers.\n * @private\n */\n private _triggerChange() {\n if (this.status === 'destroyed') {\n return\n }\n this._channel.postMessage({\n type: 'STATE_UPDATE',\n payload: this._value,\n senderId: this._instanceId,\n })\n this._notifySubscribers()\n }\n\n /**\n * Synchronously retrieves the current state from the cache.\n * @returns The current state of the store.\n */\n get(): T {\n return this._value\n }\n\n /**\n * Sets the new value for the store's state, updates the value, and optionally persists it to IndexedDB asynchronously.\n * @param value The new state value to set.\n * @returns A Promise that resolves when the state has been set and persisted (if applicable).\n */\n set(value: T): void {\n if (this.status === 'destroyed') {\n return\n }\n this._value = value\n\n if (!this._persist || this._db === null) {\n this._triggerChange()\n return\n }\n\n void new Promise<void>((resolve, reject) => {\n const db = this._db\n\n if (!db) {\n reject(new Error('Database not initialized'))\n return\n }\n\n const tx = db.transaction(this._prefixedName, 'readwrite')\n const store = tx.objectStore(this._prefixedName)\n const req = store.put(value, this._dbKey)\n\n req.onsuccess = () => {\n this._triggerChange()\n resolve()\n }\n req.onerror = () => {\n reject(new Error(req.error?.message ?? 'unknown error'))\n }\n })\n }\n\n /**\n * Subscribes a callback function to state changes.\n * @param callback The function to call when the state changes.\n * @returns A function that can be called to unsubscribe the callback.\n */\n subscribe(callback: StoreChangeCallback<T>): () => void {\n if (this.status === 'destroyed') {\n throw new Error('ChannelStore is destroyed')\n }\n this._subscribers.add(callback)\n\n return () => {\n this._subscribers.delete(callback)\n }\n }\n\n /**\n * Subscribes a callback function to status changes.\n * @param callback The function to call when the status changes.\n * @returns A function that can be called to unsubscribe the callback.\n */\n subscribeStatus(callback: StoreStatusCallback): () => void {\n if (this.status === 'destroyed') {\n throw new Error('ChannelStore is destroyed')\n }\n this._statusSubscribers.add(callback)\n\n return () => {\n this._statusSubscribers.delete(callback)\n }\n }\n\n /**\n * Cleans up resources used by the ChannelStore, including closing the BroadcastChannel\n * and IndexedDB connection, and clearing subscribers.\n */\n destroy() {\n if (this.status === 'destroyed') {\n return\n }\n this.status = 'destroyed'\n this._notifyStatusSubscribers()\n this._channel.close()\n this._subscribers.clear()\n this._statusSubscribers.clear()\n this._db?.close()\n if (this._initialStateRequestTimeout) {\n clearTimeout(this._initialStateRequestTimeout)\n this._initialStateRequestTimeout = null\n }\n }\n\n /**\n * Resets the store's state to its initial value.\n * @returns A Promise that resolves when the state has been reset.\n */\n reset(): Promise<void> {\n if (this.status === 'destroyed') {\n return Promise.resolve()\n }\n this._value = structuredClone(this._initial)\n\n if (!this._db) {\n this._triggerChange()\n return Promise.resolve()\n }\n\n return new Promise((resolve, reject) => {\n const db = this._db\n\n if (!db) {\n reject(new Error('IndexedDB is not available'))\n return\n }\n\n const tx = db.transaction(this._prefixedName, 'readwrite')\n const store = tx.objectStore(this._prefixedName)\n const req = store.put(this._initial, this._dbKey)\n\n req.onsuccess = () => {\n this._triggerChange()\n resolve()\n }\n req.onerror = () => {\n reject(new Error(req.error?.message ?? 'unknown error'))\n }\n })\n }\n}\n"]}