Skip to main content

Reducing and reviving

version: v0.8.6+
What you'll learn
  • Reducing a model's instance
  • Reviving a model's instance
  • Configuring custom behaviors

Purpose

Reducing and reviving instances allow you to convert models' instances to a serializable object. This is useful when you cannot pass a complex object between two JavaScript programs, such as in Web Workers or when using Nuxt SSR features.

Using CLI

You can generate reducer and reviver using @foscia/cli.

npx foscia make reducer
npx foscia make reviver

Reducing and reviving

Foscia provides built-in tools to reduce and revive models' instances with two functions: makeModelsReducer and makeModelsReviver.

Those tools support instance's state, values and relations (circular or not) reducing and reviving out of the box.

import { makeModelsReducer, makeModelsReviver } from '@foscia/core';
import Post from './models/post';
import Comment from './models/comment';

const { reduce } = makeModelsReducer();
const { revive } = makeModelsReviver([Post, Comment]);

const myPost = new Post();

const json = JSON.stringify(reduce(myPost));
const myRevivedPost = revive(JSON.parse(json));
info

Notice that:

  • makeModelsReviver must receive an array of revivable models.
  • Reducing models will not "serialize" object values, such as Date. You can use a tool like devalue to leverage this.

Custom behaviors

You can easily define custom reducing and reviving behaviors by defining $reduce and $revive instance methods. Those custom behaviors can support default state reducing/reviving or not, and can be strongly typed.

Keeping instance state

To provide a custom reducing/reviving behavior while keeping instance state automatic reducing/reviving, you can use the provided data factory function passed to $reduce. It will reduce internal data used by Foscia to correctly revive instance state.

During $revive, data will contain your reduced data provided by $reduce.

import { attr, makeModel, ModelReduceTools } from '@foscia/core';

export default class Post extends makeModel('posts', {
title: attr(),
}) {
public foo = 'bar';

public $reduce({ data }: ModelReduceTools) {
return {
foo: this.foo,
// Foscia reducing should have priority over your custom data.
// It will reduce data with `$` prefixed keys, so avoid using those.
...data(this),
};
}

public $revive(data: any) {
this.foo = data.foo;
// Thanks to `data` use in `$reduce`, the post state is already revived
// and available inside `$revive`.
console.log(`Revived: ${this.title}`);
}
}

Destroying instance state

You can omit instance state reducing by not calling data function inside your $reduce method. This can be useful when you need something very specific, but be aware that no instance state will be restored automatically (values, relations, $exists state, $original snapshot or other instance properties).

import { attr, makeModel } from '@foscia/core';

export default class Post extends makeModel('posts', {
title: attr(),
}) {
public foo = 'bar';

public $reduce() {
return {
title: this.title,
foo: this.foo,
};
}

public $revive(data: any) {
this.title = data.title;
this.foo = data.foo;
}
}

Typechecking custom behaviors

If you want to ensure you do not miss any implemented methods or typing, you can use Foscia provided interface ModelCanReduceRevive and types to strict type your implementations of custom $reduce and $revive methods.

import { makeModel, ModelCanReduceRevive, ModelReduceTools, ModelReviveTools, ReducedModelInstanceCustomData } from '@foscia/core';

// You must declare a custom type which represent custom data format.
type PostReducedData = ReducedModelInstanceCustomData & {
// Your data typings...
foo: string;
};

export default class Post
extends makeModel('posts')
implements ModelCanReduceRevive<PostReducedData> {
public foo = 'bar';

public $reduce(tools: ModelReduceTools) {
return {
foo: this.foo,
...tools.data(this),
};
}

public $revive(data: PostReducedData, tools: ModelReviveTools) {
this.foo = data.foo;
}
}

Defining common custom behaviors

If you want to share a common behavior between some or all of your models, you can use model composition to define common $reduce and $revive methods.

With a composable

import { makeComposable, ModelReduceTools } from '@foscia/core';

export default makeComposable({
foo: 'bar',
$reduce({ data }: ModelReduceTools) {
return {
foo: this.foo,
...data.data(this),
};
},
$revive(data: any) {
this.foo = data.foo;
},
});

With a model factory

import { makeModelFactory, ModelReduceTools } from '@foscia/core';

export default makeModelFactory({}, {
foo: 'bar',
$reduce({ data }: ModelReduceTools) {
return {
foo: this.foo,
...data.data(this),
};
},
$revive(data: any) {
this.foo = data.foo;
},
});