0.0.9 • Published 7 months ago

@medamajs/compose v0.0.9

Weekly downloads
-
License
MIT
Repository
-
Last release
7 months ago

@medamajs/compose

@medamajs/compose extends medama's capabilities by enabling composition of multiple state layers into a cohesive unit. It maintains all the original guarantees while allowing for sophisticated state hierarchies.

Installation

npm i @medamajs/compose

Basic Usage

The main function composeMedama allows you to combine multiple medama states:

const { readState, subscribeToState, setState, pupil } = composeMedama(
  {
    layer1: createMedama<Layer1State>(),
    layer2: createMedama<Layer2State>(),
  },

  {
    layer1: { /* initial state */ },
    layer2: { /* initial state */ }
  }
);

State Layers

Each layer in a composite state maintains its independence while being part of a larger state structure. Layers can be accessed and modified individually or as part of the whole:

const selector = (state: { layer1: Layer1State; layer2: Layer2State }) => ({
  ...state.layer1,
  ...state.layer2
});

subscribeToState(selector, (value) => {
  // Reacts to changes in either layer
  console.log(value);
});

Dynamic Layer Management

Layers can be added or removed dynamically:

const { addLayers, deleteLayers } = composeMedama(/* ... */);

// Add new layers
const extendedState = addLayers({
  layer3: createMedama<Layer3State>()
});

// Remove layers
const reducedState = deleteLayers('layer2');

/**
 * Can also pass array of layer names
 *
 * const reducedState = deleteLayers(['layer1', 'layer2']);
*/

Nested Composition

States can be composed hierarchically to create complex state structures:

const nestedState = composeMedama({
  outer: composeMedama({
    inner1: createMedama<InnerState1>(),
    inner2: createMedama<InnerState2>()
  }),

  sibling: createMedama<SiblingState>()
});

Performance Optimization

The composition maintains medama's performance characteristics:

  • Efficient memoization of selector results
  • Targeted subscription updates
  • Garbage collection of unused selectors
  • Smart dependency tracking through the layer hierarchy

Note that state objects passed to selectors and setters maintain the same safety guarantees as the base medama implementation, preventing unauthorized state access outside of designated operations.

Powerful Subscription Behavior

Unlike the original medama, composite states allow state changes during subscription callbacks, both when the subscription is initially created and when it's triggered by state updates. This opens up powerful capabilities like:

  • Self-adjusting states
  • Automatic state synchronization
  • Reactive state chains
  • State-based finite state machines

Here's an example of a self-adjusting counter:

const { subscribeToState, setState } = composeMedama({
  counter: createMedama<{ value: number }>(),
}, {
  counter: { value: 0 }
});

subscribeToState(
  (state) => state.counter.value,

  (value) => {
    // Safe to call setState here
    if (value < 5) {
      setState({
        counter: { value: value + 1 };
      });
    }
  }
);

This feature enables complex state automation while maintaining predictable state updates and preserving all composition benefits.

Parallel Compositions

A unique characteristic of @medamajs/compose is that different compositions of the same layers can coexist independently, regardless of their configuration or nesting depth. When a state update occurs in one composite state, it automatically triggers subscriptions in other composite states that share the affected layers:

// Create two different compositions sharing some layers
const stateA = createMedama<SharedLayerState>();
const stateB = createMedama<SharedLayerState>();

const composition1 = composeMedama({
  shared1: stateA,
  local1: createMedama<LocalState>()
});

const composition2 = composeMedama({
  nested: composeMedama({
    shared1: stateA,  // Same layer as in composition1
    shared2: stateB
  })
});

// Updates in composition1 trigger subscriptions in composition2
composition1.setState({
  shared1: { value: 42 }
});

// Subscriptions in composition2 react to shared layer changes
composition2.subscribeToState(
  (state) => state.nested.shared1.value,
  (value) => console.log(value) // Logs: 42
);

This powerful feature enables decentralized state management where different parts of your application can maintain their own state compositions while staying synchronized through shared layers.

This approach also allows you to organize selectors based on different logical requirements throughout your application. Each part of your application can compose exactly the states it needs to observe:

// Authentication related composition
const authComposition = composeMedama({
  user: userState,
  session: sessionState
});

// Feature specific composition combining auth and feature state
const featureComposition = composeMedama({
  user: userState,
  featureA: featureState,
  settings: settingsState
});

// Different selectors for different needs
authComposition.subscribeToState(
  (state) => ({
    isLoggedIn: state.user.loggedIn,
    sessionValid: state.session.valid
  }),

  // ...
);

featureComposition.subscribeToState(
  (state) => ({
    canAccess: state.user.permissions.includes('featureA'),
    featureEnabled: state.featureA.enabled && state.settings.featureAEnabled
  }),

  // ...
);

Checking Composite State Layers

The isComposite utility function allows you to check if a state layer within a selector is itself a composite state. This is useful when working with nested compositions and needing to handle layers differently based on their composition status:

import { isComposite } from '@medamajs/compose';

const complexState = composeMedama({
  simple: createMedama<SimpleState>(),

  nested: composeMedama({
    inner1: createMedama<InnerState>(),
    inner2: createMedama<InnerState>()
  })
});

complexState.subscribeToState(
  (state) => {
    // isComposite can only be used within selectors
    const isNestedComposite = isComposite(state.nested); // true
    const isSimpleComposite = isComposite(state.simple); // false

    return {
      // Use the composition information to handle state differently
    };
  },

  // ...
);

Note that isComposite can only be used within selector functions. Attempting to use it outside of a selector context will result in an error, as the state objects are only accessible during selector execution.