Skip to content

Commit

Permalink
Release 0.29.0
Browse files Browse the repository at this point in the history
Release V0.29.0
  • Loading branch information
resolve-bot committed Mar 30, 2021
2 parents c94b906 + 980bfc2 commit b8ace32
Show file tree
Hide file tree
Showing 55 changed files with 529 additions and 237 deletions.
15 changes: 15 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## 0.28.0

### Breaking Changes

#### Cloud config

- `deploymentId` and `encryptedDeploymentId` removed from **uploadAdapter** options

### Added

#### resolve runtime

- `clientIp` in request object


## 0.27.0

### Breaking Changes
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@ title: reSolve Documentation
- [Advanced Techniques](advanced-techniques.md)

- [Splitting Code Into Chunks](advanced-techniques.md#splitting-code-into-chunks)
- [Server-Side Rendering](advanced-techniques.md#server-side-rendering)
- [Adapters](advanced-techniques.md#adapters)
- [Custom Read Models](advanced-techniques.md#custom-read-models)
- [Modules](advanced-techniques.md#modules)

- [Authentication and Authorization](authentication-and-authorization.md)
Expand Down
141 changes: 141 additions & 0 deletions docs/advanced-techniques.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,147 @@ Resolve comes with a set of adapters covering popular DBMS choices. You can also

Note that reSolve does not force you to use adapters. For example, you may need to implement a Read Model on top of some arbitrary system, such as a full-text-search engine, OLAP or a particular SQL database. In such case, you can just work with that system in the code of the projection function and query resolver, without writing a new Read Model adapter.

## Custom Read Model Connectors

You can implement a custom Read Model connector to define how a Read Model's data is stored. A connector implements the following functions:

- **connect** - Initializes a connection to a storage.
- **disconnect** - Closes the storage connection.
- **drop** - Removes the Read Model's data from storage.
- **dispose** - Forcefully disposes all unmanaged resources used by Read Models served by this connector.

The code sample below demonstrates how to implement a connector that provides a file-based storage for Read Models.

##### common/read-models/custom-read-model-connector.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/connector.js)
```js
import fs from 'fs'

const safeUnlinkSync = filename => {
if (fs.existsSync(filename)) {
fs.unlinkSync(filename)
}
}

export default options => {
const prefix = String(options.prefix)
const readModels = new Set()
const connect = async readModelName => {
fs.writeFileSync(`${prefix}${readModelName}.lock`, true, { flag: 'wx' })
readModels.add(readModelName)
const store = {
get() {
return JSON.parse(String(fs.readFileSync(`${prefix}${readModelName}`)))
},
set(value) {
fs.writeFileSync(`${prefix}${readModelName}`, JSON.stringify(value))
}
}
return store
}
const disconnect = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
readModels.delete(readModelName)
}
const drop = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
safeUnlinkSync(`${prefix}${readModelName}`)
}
const dispose = async () => {
for (const readModelName of readModels) {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
}
readModels.clear()
}
return {
connect,
disconnect,
drop,
dispose
}
}
```

<!-- prettier-ignore-end -->

A connector is defined as a function that receives an `options` argument. This argument contains a custom set of options that you can specify in the connector's configuration.

Register the connector in the application's configuration file.

##### config.app.js:

```js
readModelConnectors: {
customReadModelConnector: {
module: 'common/read-models/custom-read-model-connector.js',
options: {
prefix: path.join(__dirname, 'data') + path.sep // Path to a folder that contains custom Read Model store files
}
}
}
```

Now you can assign the custom connector to a Read Model by name as shown below.

##### config.app.js:

```js
readModels: [
{
name: 'CustomReadModel',
projection: 'common/read-models/custom-read-model.projection.js',
resolvers: 'common/read-models/custom-read-model.resolvers.js',
connectorName: 'customReadModelConnector'
}
...
]
```

The code sample below demonstrates how you can use the custom store's API in the Read Model's code.

##### common/read-models/custom-read-model.projection.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/projection.js)
```js
const projection = {
Init: async store => {
await store.set(0)
},
INCREMENT: async (store, event) => {
await store.set((await store.get()) + event.payload)
},
DECREMENT: async (store, event) => {
await store.set((await store.get()) - event.payload)
}
}

export default projection
```

<!-- prettier-ignore-end -->

##### common/read-models/custom-read-model.resolvers.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/resolvers.js)
```js
const resolvers = {
read: async store => {
return await store.get()
}
}

export default resolvers
```

<!-- prettier-ignore-end -->

## Modules

In reSolve, a module encapsulates a fragment of functionality that can be included by an application. A module can include any structural parts of a reSolve application in any combination.
Expand Down
10 changes: 5 additions & 5 deletions docs/api-reference.md
Original file line number Diff line number Diff line change
Expand Up @@ -1840,11 +1840,11 @@ Multiple middleware functions are run in the order they are specified in the opt
This section lists request middleware included into the @resolve-js/client package. The following middleware is available:
| Name | Description |
| ------------------- | --------------------------------------------------------- |
| [parseResponse]() | Deserializes the response data if it contains valid JSON. |
| [retryOnError]() | Retries the request if the server responds with an error. |
| [waitForResponse]() | Validates the response and retries if validation fails. |
| Name | Description |
| ----------------------------------- | --------------------------------------------------------- |
| [parseResponse](#parseresponse) | Deserializes the response data if it contains valid JSON. |
| [retryOnError](#retryonerror) | Retries the request if the server responds with an error. |
| [waitForResponse](#waitforresponse) | Validates the response and retries if validation fails. |
##### parseResponse
Expand Down
2 changes: 1 addition & 1 deletion docs/application-configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ This document describes configuration options available for a reSolve applicatio
In a new reSolve application, configuration settings are split across the following files for different run targets:

- **config.app.js** - Contains general app configuration settings. In this file, you should register the application's aggregates, Read Models and View Models.
- **config.cloud.js** - Contains configuration settings that target the [reSolve Cloud](cloud-overview.md) environment.
- **config.cloud.js** - Contains configuration settings that target the reSolve Cloud environment.
- **config.dev.js** - Contains configuration settings that target the development server.
- **config.prod.js** - Contains configuration settings that target the production server.
- **config.test_functional.js** - Contains configuration settings that target the test environment.
Expand Down
157 changes: 9 additions & 148 deletions docs/read-side.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,147 +99,6 @@ const appConfig = {

In the configuration object, specify the View Model's name and the path to the file containing projection definition. You can also specify the View Model snapshot storage adapter. Use the **serializeState** and **deserializeState** options to specify paths to a View Model's serializer and deserializer functions. Specify the **resolver** option to add a [View Model resolver](#view-model-resolver) to the View Model.

### Custom Read Models

To create a custom Read Model, you need to manually implement a Read Model connector. A connector defines functions that manage a custom Read Model's store. The following functions can be defined:

- **connect** - Initializes a connection to a storage.
- **disconnect** - Closes the storage connection.
- **drop** - Removes the Read Model's data from storage.
- **dispose** - Forcefully disposes all unmanaged resources used by Read Models served by this connector.

The code sample below demonstrates how to implement a connector that provides a file-based storage for Read Models.

##### common/read-models/custom-read-model-connector.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/connector.js)
```js
import fs from 'fs'

const safeUnlinkSync = filename => {
if (fs.existsSync(filename)) {
fs.unlinkSync(filename)
}
}

export default options => {
const prefix = String(options.prefix)
const readModels = new Set()
const connect = async readModelName => {
fs.writeFileSync(`${prefix}${readModelName}.lock`, true, { flag: 'wx' })
readModels.add(readModelName)
const store = {
get() {
return JSON.parse(String(fs.readFileSync(`${prefix}${readModelName}`)))
},
set(value) {
fs.writeFileSync(`${prefix}${readModelName}`, JSON.stringify(value))
}
}
return store
}
const disconnect = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
readModels.delete(readModelName)
}
const drop = async (store, readModelName) => {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
safeUnlinkSync(`${prefix}${readModelName}`)
}
const dispose = async () => {
for (const readModelName of readModels) {
safeUnlinkSync(`${prefix}${readModelName}.lock`)
}
readModels.clear()
}
return {
connect,
disconnect,
drop,
dispose
}
}
```

<!-- prettier-ignore-end -->

A connector is defined as a function that receives an `options` argument. This argument contains a custom set of options that you can specify in the connector's configuration.

Register the connector in the application's configuration file.

##### config.app.js:

```js
readModelConnectors: {
customReadModelConnector: {
module: 'common/read-models/custom-read-model-connector.js',
options: {
prefix: path.join(__dirname, 'data') + path.sep // Path to a folder that contains custom Read Model store files
}
}
}
```

Now you can assign the custom connector to a Read Model by name as shown below.

##### config.app.js:

```js
readModels: [
{
name: 'CustomReadModel',
projection: 'common/read-models/custom-read-model.projection.js',
resolvers: 'common/read-models/custom-read-model.resolvers.js',
connectorName: 'customReadModelConnector'
}
...
]
```

The code sample below demonstrates how you can use the custom store's API in the Read Model's code.

##### common/read-models/custom-read-model.projection.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/projection.js)
```js
const projection = {
Init: async store => {
await store.set(0)
},
INCREMENT: async (store, event) => {
await store.set((await store.get()) + event.payload)
},
DECREMENT: async (store, event) => {
await store.set((await store.get()) - event.payload)
}
}

export default projection
```

<!-- prettier-ignore-end -->

##### common/read-models/custom-read-model.resolvers.js:

<!-- prettier-ignore-start -->

[mdis]:# (../tests/custom-readmodel-sample/resolvers.js)
```js
const resolvers = {
read: async store => {
return await store.get()
}
}

export default resolvers
```

<!-- prettier-ignore-end -->

## Initialize a Read Model

Each Read Model projection object should define an **Init** function that initializes the Read Model storage.
Expand Down Expand Up @@ -324,11 +183,15 @@ Refer to the [Query a Read Model](#query-a-read-model) section for information o

## View Model Specifics

**View Models** are a special kind of Read Models. They are queried based on aggregate ID and and can automatically provide updates to Redux state on the client. View Models are defined in a special isomorphic format so their code can also be used on the client side to provide reducer logic.
**View Models** are ephemeral Read Models that are queried based on aggregate ID. They have the following properties:

- View Models are rebuilt on every request. They do not store persistent state and do not use the Read Model store.
- View Models are queried based on aggregate ID and can maintain a WebSocket connection to push data updates to the client.
- View Model projections are defined in a format that is isomorphic with Redux reducers so their code can also be used on the client side to define reducer logic.

Use View Models in the following scenarios:

- To create aggregate-centric views. Such views request relatively small portions of data based on aggregate IDs.
- To create aggregate-centric views that request relatively small portions of data based on aggregate IDs.
- To create reactive components, whose state is kept up-to date on the client.

A View Model's projection function receives a state and an event object, and returns an updated state. A projection function runs for every event with the specified aggregate ID from the beginning of the history on every request so it is important to keep View Models small. You can also store snapshots of the View Model state to optimize system resource consumption.
Expand Down Expand Up @@ -356,8 +219,6 @@ The code sample below demonstrates a View Model projection function:

Refer to the [Query a View Model](#query-a-view-model) section, for information on how to query a View Model.

Note that a View Model does not use the Read Model store.

## View Model Resolver

A View Model's **resolver** allows you to restrict a user's access to the View Model's data. A resolver function receives the following parameters:
Expand All @@ -370,9 +231,9 @@ In the resolver's code, you can use arbitrary logic to check a user's access per

The resolver function should return a built View Model data object and a meta object that contains the following data:

- A cursor returned by the `buildViewModel` function;
- A list of event types;
- A list of aggregate IDs.
- The data cursor used to traverse the events included into the query result set. The initial cursor is returned by the `buildViewModel` function;
- A list of event types available to the client;
- A list of aggregate IDs available to the client.

The code sample below demonstrates a View Model resolver implementation:

Expand Down
Loading

0 comments on commit b8ace32

Please sign in to comment.