Skip to content
Open
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
e4ccfc3
Add `createStorage` function, to create stand-alone effects for storage
yumauri Apr 17, 2024
06bdfb9
Add /*#__PURE__*/ annotations for dead code elimination
yumauri Apr 17, 2024
63d2435
Storage handles effects always fails as Fail<Err> type
yumauri Apr 18, 2024
9219ecd
Merge branch 'refs/heads/main' into separate-adapter-effects
yumauri Jul 6, 2024
714ecda
Fix merge with new eslint rules
yumauri Jul 6, 2024
d0de266
Merge branch 'refs/heads/main' into separate-adapter-effects
yumauri Jul 6, 2024
0d82163
Finalize code
igorkamyshev Aug 6, 2024
a6bb1f6
Edit size-limits
igorkamyshev Aug 6, 2024
e45b2bd
Add basic docs
igorkamyshev Aug 6, 2024
9bbcbd8
Add fx suffix
igorkamyshev Aug 6, 2024
9e4b685
Renaming finish
igorkamyshev Aug 6, 2024
7ddff94
Renaming finish
igorkamyshev Aug 6, 2024
8e18ec1
Edit size-limits
igorkamyshev Aug 6, 2024
6346d2d
Edit size-limits
igorkamyshev Aug 6, 2024
8a8d28d
Merge branch 'refs/heads/main' into separate-adapter-effects
yumauri Aug 17, 2024
b2b0630
Merge branch 'main' into separate-adapter-effects
yumauri Sep 8, 2024
c352143
Merge branch 'main' into separate-adapter-effects
yumauri Nov 23, 2024
b5b9243
Merge branch 'main' into separate-adapter-effects
yumauri Feb 9, 2025
2fc45ad
Update branch to be up to date with main
yumauri Feb 9, 2025
5fe3991
Merge branch 'main' into separate-adapter-effects
yumauri Jun 7, 2025
64ae76d
Try to fix remove effect by adding optional adapter method
yumauri Jun 7, 2025
736dbfb
[WIP]
yumauri Jun 22, 2025
1f069d6
Merge branch 'main' into separate-adapter-effects
yumauri Jun 28, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 22 additions & 22 deletions .size-limit.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ module.exports = [
{
name: 'root persist, es module',
path: 'build/index.js',
limit: '985 B',
limit: '989 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: 'root persist, cjs module',
path: 'build/index.cjs',
limit: '3250 B',
limit: '3869 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -21,15 +21,15 @@ module.exports = [
{
name: 'core persist, es module',
path: 'build/core/index.js',
limit: '980 B',
limit: '984 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: 'core persist, cjs module',
path: 'build/core/index.cjs',
limit: '1166 B',
limit: '1643 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -39,15 +39,15 @@ module.exports = [
{
name: 'tools, es module',
path: 'build/tools/index.js',
limit: '289 B',
limit: '292 B',
import: '{ async, either, farcached }',
ignore: ['effector'],
gzip: true,
},
{
name: 'tools, cjs module',
path: 'build/tools/index.cjs',
limit: '482 B',
limit: '485 B',
// import: '{ async, either, farcached }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -65,7 +65,7 @@ module.exports = [
{
name: 'nil adapter, cjs module',
path: 'build/nil/index.cjs',
limit: '139 B',
limit: '140 B',
// import: '{ nil }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -75,7 +75,7 @@ module.exports = [
{
name: 'log adapter, es module',
path: 'build/log/index.js',
limit: '139 B',
limit: '140 B',
import: '{ log }',
ignore: ['effector'],
gzip: true,
Expand All @@ -93,7 +93,7 @@ module.exports = [
{
name: 'storage adapter, es module',
path: 'build/storage/index.js',
limit: '399 B',
limit: '401 B',
import: '{ storage }',
ignore: ['effector'],
gzip: true,
Expand All @@ -111,15 +111,15 @@ module.exports = [
{
name: '`localStorage` persist, es module',
path: 'build/local/index.js',
limit: '1453 B',
limit: '1464 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: '`localStorage` persist, cjs module',
path: 'build/local/index.cjs',
limit: '1732 B',
limit: '2238 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -130,15 +130,15 @@ module.exports = [
{
name: 'core adapter, es module',
path: 'build/index.js',
limit: '1459 B',
limit: '1444 B',
import: '{ persist, local }',
ignore: ['effector'],
gzip: true,
},
{
name: 'core adapter factory, es module',
path: ['build/index.js', 'build/local/index.js'],
limit: '1462 B',
limit: '1446 B',
import: {
'build/index.js': '{ persist }',
'build/local/index.js': '{ local }',
Expand All @@ -151,15 +151,15 @@ module.exports = [
{
name: '`sessionStorage` persist, es module',
path: 'build/session/index.js',
limit: '1451 B',
limit: '1463 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: '`sessionStorage` persist, cjs module',
path: 'build/session/index.cjs',
limit: '1729 B',
limit: '2234 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -169,15 +169,15 @@ module.exports = [
{
name: 'query string persist, es module',
path: 'build/query/index.js',
limit: '1509 B',
limit: '1514 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: 'query string persist, cjs module',
path: 'build/query/index.cjs',
limit: '1802 B',
limit: '2295 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -187,15 +187,15 @@ module.exports = [
{
name: 'memory adapter, es module',
path: 'build/memory/index.js',
limit: '1073 B',
limit: '1078 B',
import: '{ persist }',
ignore: ['effector'],
gzip: true,
},
{
name: 'memory adapter, cjs module',
path: 'build/memory/index.cjs',
limit: '1304 B',
limit: '1816 B',
// import: '{ persist }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand All @@ -205,7 +205,7 @@ module.exports = [
{
name: 'generic async storage adapter, es module',
path: 'build/async-storage/index.js',
limit: '162 B',
limit: '165 B',
import: '{ asyncStorage }',
ignore: ['effector'],
gzip: true,
Expand All @@ -223,15 +223,15 @@ module.exports = [
{
name: 'broadcast channel adapter, es module',
path: 'build/broadcast/index.js',
limit: '1317 B',
limit: '371 B',
import: '{ broadcast }',
ignore: ['effector'],
gzip: true,
},
{
name: 'broadcast channel adapter, cjs module',
path: 'build/broadcast/index.cjs',
limit: '1568 B',
limit: '2074 B',
// import: '{ broadcast }', // tree-shaking is not working with cjs
ignore: ['effector'],
gzip: true,
Expand Down
57 changes: 54 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,7 @@ In order to synchronize _something_, you need to specify effector units. Dependi
- `keyPrefix` ([_string_]): Prefix, used in adapter, to be concatenated to `key`. By default = `''`.
- `operation` (_`'set'`_ | _`'get'`_ | _`'validate'`_): Type of operation, read (get), write (set) or validation against contract (validate).
- `error` ([_Error_]): Error instance
- `value`? (_any_): In case of _'set'_ operation — value from `store`. In case of _'get'_ operation could contain raw value from storage or could be empty.
- `value`? (_any_): In case of _'set'_ operation — value from `store`. In case of _'get'_ and _'validate'_ operations could contain raw value from storage or could be empty.
- `finally`? ([_Event_] | [_Effect_] | [_Store_]): Unit, which will be triggered either in case of success or error.<br>
Payload structure:
- `key` ([_string_]): Same `key` as above.
Expand Down Expand Up @@ -274,6 +274,57 @@ persist({

- Custom `persist` function, with predefined adapter options.

## `createStorage` factory

In rare cases you might want to get a granular control over a storage and manually set or get values from it. You can use `createStorage` factory for that.

```javascript
import { sample, createEvent } from 'effector'
import { createStorage } from 'effector-storage/local'

const persist = createStorage('my-storage')

const userWantToSave = createEvent()
const userWantToLoad = createEvent()

// ---8<---

sample({
clock: userWantToSave,
fn: () => 'some data'
target: persist.setFx,
})

sample({
source: userWantToLoad,
target: persist.getFx,
})
```

### Options

- `key`? ([_string_]): Key for local/session storage, to store value in. If omitted — `store` name is used. **Note!** If `key` is not specified, `store` _must_ have a `name`! You can use `'effector/babel-plugin'` to have those names automatically.
- `keyPrefix`? ([_string_]): Prefix, used in adapter, to be concatenated to `key`. By default = `''`.
- `context`? ([_Event_] | [_Effect_] | [_Store_]): Unit, which can set a special context for adapter.
- `contract`? ([_Contract_]): Rule to statically validate data from storage.

### Returns

An object with fields:

- `getFx` (_Effect_): to get value from storage.
- `setFx` (_Effect_): to set value to storage.
- `removeFx` (_Effect_): to remove value from storage.

All fields of returned object are _Effects_ units, so you can use them in `sample` as any other _Effects_. For example, you can add logging on failed storage operations:

```javascript
sample({
clock: getFx.fail,
target: sendLogToSentry,
})
```

## Advanced usage

`effector-storage` consists of a _core_ module and _adapter_ modules.
Expand All @@ -300,8 +351,8 @@ interface StorageAdapter {
key: string,
update: (raw?: any) => void
): {
get(raw?: any, ctx?: any): State | Promise<State | undefined> | undefined
set(value: State, ctx?: any): void
get(raw?: any, ctx?: any): State | undefined | Promise<State | undefined>
set(value: State, ctx?: any): void | Promise<void>
}
keyArea?: any
noop?: boolean
Expand Down
5 changes: 1 addition & 4 deletions eslint.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@ export default tseslint.config(
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/unified-signatures': 'off',
'@typescript-eslint/consistent-type-definitions': 'off',
'@typescript-eslint/no-invalid-void-type': [
'error',
{ allowAsThisParameter: true },
],
'@typescript-eslint/no-invalid-void-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': [
'error',
{ allowArgumentsExplicitlyTypedAsAny: true },
Expand Down
1 change: 1 addition & 0 deletions rollup.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ const src = (name) => ({
},
format: {
comments: false,
preserve_annotations: true,
},
}),

Expand Down
37 changes: 34 additions & 3 deletions src/broadcast/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,12 @@ import type {
ConfigPersist as BaseConfigPersist,
ConfigStore as BaseConfigStore,
ConfigSourceTarget as BaseConfigSourceTarget,
ConfigCreateStorage as BaseConfigCreateStorage,
StorageAdapter,
StorageHandles,
} from '../types'
import type { BroadcastConfig } from './adapter'
import { persist as base } from '../core'
import { persist as base, createStorage as baseCreateStorage } from '../core'
import { nil } from '../nil'
import { adapter } from './adapter'

Expand Down Expand Up @@ -37,6 +39,19 @@ export interface Persist {
<State, Err = Error>(config: ConfigStore<State, Err>): Subscription
}

export interface ConfigCreateStorage<State>
extends BaseConfigCreateStorage<State> {}

export interface CreateStorage {
<State, Err = Error>(
key: string,
config?: BroadcastConfig & BaseConfigCreateStorage<State>
): StorageHandles<State, Err>
<State, Err = Error>(
config: BroadcastConfig & BaseConfigCreateStorage<State> & { key: string }
): StorageHandles<State, Err>
}

/**
* Function, checking if `BroadcastChannel` exists and accessible
*/
Expand All @@ -45,7 +60,7 @@ function supports() {
}

/**
* Creates BroadcastChannel string adapter
* Creates BroadcastChannel adapter
*/
broadcast.factory = true as const
export function broadcast(config?: BroadcastConfig): StorageAdapter {
Expand All @@ -72,4 +87,20 @@ export function createPersist(defaults?: ConfigPersist): Persist {
/**
* Default partially applied `persist`
*/
export const persist = createPersist()
export const persist = /*#__PURE__*/ createPersist()

/**
* Creates custom partially applied `createStorage`
* with predefined BroadcastChannel adapter
*/
export function createStorageFactory(
defaults?: ConfigCreateStorage<any>
): CreateStorage {
return (...configs: any[]) =>
baseCreateStorage({ adapter: broadcast }, defaults, ...configs)
}

/**
* Default partially applied `createStorage`
*/
export const createStorage = /*#__PURE__*/ createStorageFactory()
9 changes: 6 additions & 3 deletions src/core/area.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,18 @@
import type { Store } from 'effector'
import type { StoreWritable } from 'effector'
import { createStore } from 'effector'

/**
* Keys areas / namespaces cache
*/
const areas = new Map<any, Map<string, Store<any>>>()
const areas = new Map<any, Map<string, StoreWritable<any>>>()

/**
* Get store, responsible for the key in key area / namespace
*/
export function getAreaStorage<State>(keyArea: any, key: string): Store<State> {
export function getAreaStorage<State>(
keyArea: any,
key: string
): StoreWritable<State> {
let area = areas.get(keyArea)
if (area === undefined) {
area = new Map()
Expand Down
Loading