Comparing version 0.3.13 to 1.0.0
@@ -1,2 +0,17 @@ | ||
module.exports.Repository = require('./repository'); | ||
module.exports.Aggregate = require('./aggregate'); | ||
"use strict"; | ||
Object.defineProperty(exports, "__esModule", { | ||
value: true | ||
}); | ||
_exportStar(require("./aggregate"), exports); | ||
_exportStar(require("./repository"), exports); | ||
function _exportStar(from, to) { | ||
Object.keys(from).forEach(function(k) { | ||
if (k !== "default" && !Object.prototype.hasOwnProperty.call(to, k)) Object.defineProperty(to, k, { | ||
enumerable: true, | ||
get: function() { | ||
return from[k]; | ||
} | ||
}); | ||
}); | ||
return from; | ||
} |
{ | ||
"name": "demeine", | ||
"version": "0.3.13", | ||
"version": "1.0.0", | ||
"description": "DDDD - Distributed Domain Driven Design", | ||
"main": "index.js", | ||
"main": "lib/index.js", | ||
"types": "lib/index.d.ts", | ||
"scripts": { | ||
"test": "mocha test --recursive", | ||
"test.watch": "mocha test --color --watch --recursive", | ||
"coverage": "istanbul cover node_modules/mocha/bin/_mocha -- --recursive test -u exports -R spec", | ||
"coverage-start": "istanbul cover node_modules/mocha/bin/_mocha test --recursive -- -u exports -R spec && start coverage/lcov-report/index.html", | ||
"jshint": "jshint ./lib", | ||
"jscs": "jscs -p google ./lib ./test" | ||
"build": "rimraf lib && concurrently \"npm run build:src\" \"npm run build:types\"", | ||
"build:watch": "rimraf lib && concurrently \"npm run build:src:watch\" \"npm run build:types:watch\"", | ||
"build:src": "swc src --out-dir lib", | ||
"build:src:watch": "npm run build:src -- --watch", | ||
"build:types": "tsc", | ||
"build:types:watch": "npm run build:types -- --watch", | ||
"test": "jest --colors", | ||
"test:watch": "npm test -- --watch", | ||
"test:coverage": "npm test -- --collectCoverage", | ||
"test:coverage:start": "npm run test:coverage && start coverage/lcov-report/index.html", | ||
"lint": "eslint src", | ||
"lint:fix": "eslint --fix src", | ||
"format:check": "prettier --check src", | ||
"format:fix": "prettier --write src", | ||
"prepare": "npm test && npm run build" | ||
}, | ||
@@ -27,12 +37,19 @@ "repository": { | ||
"devDependencies": { | ||
"babel-core": "^6.7.7", | ||
"babel-preset-es2015": "^6.6.0", | ||
"istanbul": "^0.3.2", | ||
"jscs": "^1.11.3", | ||
"jshint": "^2.5.10", | ||
"mocha": "^2.5.3", | ||
"prettier": "^2.1.2", | ||
"@swc/cli": "^0.1.57", | ||
"@swc/core": "^1.2.237", | ||
"@swc/jest": "^0.2.22", | ||
"@types/bluebird": "^3.5.36", | ||
"@types/jest": "^28.1.7", | ||
"@types/node": "^18.7.6", | ||
"@types/uuid": "^8.3.4", | ||
"concurrently": "^7.3.0", | ||
"eslint": "^8.22.0", | ||
"eslint-config-surikat": "^4.0.0", | ||
"jest": "^27.4.4", | ||
"prettier": "^2.7.1", | ||
"prettier-config-surikaterna": "^1.0.1", | ||
"should": "^4.1.0", | ||
"slf-debug": "^0.2.0" | ||
"regenerator-runtime": "^0.13.9", | ||
"rimraf": "^3.0.2", | ||
"slf-debug": "^0.2.0", | ||
"typescript": "^4.7.4" | ||
}, | ||
@@ -44,16 +61,7 @@ "bugs": { | ||
"dependencies": { | ||
"bluebird": "^2.9.12", | ||
"slf": "^1.x", | ||
"uuid": "^3.0.1" | ||
}, | ||
"babel": { | ||
"presets": [ | ||
"es2015" | ||
] | ||
}, | ||
"browserify": { | ||
"presets": [ | ||
"es2015" | ||
] | ||
"bluebird": "^3.7.2", | ||
"p-queue": "^6.6.2", | ||
"slf": "^2.0.2", | ||
"uuid": "^8.3.2" | ||
} | ||
} |
275
README.md
@@ -1,2 +0,273 @@ | ||
# demeine | ||
DDDD - Distributed Domain Drive Design | ||
demeine | ||
======= | ||
[Demeine](https://github.com/surikaterna/demeine) is a library supporting DDDD (Distributed Domain Drive Design). | ||
* [Purpose](#purpose) | ||
* [Installation](#installation) | ||
* [Usage](#usage) | ||
* [Components](#components) | ||
* [Aggregate](#aggregate) | ||
* [clearUncommittedEvents](#clearuncommittedevents) | ||
* [delete](#delete) | ||
* [getUncommittedEvents](#getuncommittedevents) | ||
* [getUncommittedEventsAsync](#getuncommittedeventsasync) | ||
* [getVersion](#getversion) | ||
* [Repository](#repository) | ||
* [checkConcurrencyStrategy](#checkconcurrencystrategy) | ||
* [findById](#findbyid) | ||
* [findByQueryStreamWithSnapshot](#findbyquerystreamwithsnapshot) | ||
* [findBySnapshot](#findbysnapshot) | ||
* [findEventsById](#findeventsbyid) | ||
* [save](#save) | ||
## Purpose | ||
Provide the base building blocks for implementing DDD in a distributed environment. This repository contains an | ||
implementation of a _repository_, as well as a base _aggregate_ that domain specific aggregates can extend. The flow of | ||
action → command → event → state update is supported. | ||
## Installation | ||
```shell | ||
npm install demeine | ||
``` | ||
## Usage | ||
This example uses [TapeWORM](https://github.com/surikaterna/tapeworm) with its default in memory partition. | ||
```ts | ||
// User.ts | ||
export const USER_AGGREGATE_TYPE = 'user'; | ||
export interface UserState { | ||
id: string; | ||
name: string; | ||
} | ||
export class User extends Aggregate<UserState> { | ||
constructor(commandSink?: CommandSink, eventHandler?: EventHandler, commandHandler?: CommandHandler) { | ||
super(commandSink, eventHandler, commandHandler); | ||
this._state = { | ||
id: this.id, | ||
name: '' | ||
}; | ||
} | ||
register(id: string, name: string) { | ||
this.id = id; | ||
return this._sink({ | ||
type: 'user.register.command', | ||
payload: { id, name }, | ||
aggregateId: this.id | ||
}); | ||
} | ||
processChangeName(command: Command<UserState>) { | ||
return this._apply( | ||
{ | ||
type: 'location.name_changed.event', | ||
payload: command.payload, | ||
aggregateId: | ||
this.id | ||
}, | ||
true | ||
); | ||
} | ||
applyNameChanged(event: Event<UserState>) { | ||
this._state.name = event.payload.name; | ||
} | ||
} | ||
// userFactory.ts | ||
import { User, UserState, USER_AGGREGATE_TYPE } from './User'; | ||
export function userFactory(id: string) { | ||
const user = new User(); | ||
user.id = id; | ||
user._state = { id, name: '' }; | ||
user.type = USER_AGGREGATE_TYPE; | ||
return user; | ||
} | ||
// index.ts | ||
import { Aggregate, Repository } from 'demeine'; | ||
import TapeWorm from 'tapeworm'; | ||
import { User, USER_AGGREGATE_TYPE } from './User'; | ||
import { userFactory } from './userFactory'; | ||
const tapeWorm = new TapeWorm(); | ||
const partition = await tapeWorm.openPartition('my_partition'); | ||
const userRepository = new Repository<User>(partition, USER_AGGREGATE_TYPE, userFactory); | ||
// Not found, name: '' | ||
const user = await userRepository.findById('123'); | ||
await user.register('123', 'Jeff'); | ||
await userRepository.save(user); | ||
// Exists since save(user), name: 'Jeff' | ||
const existingUser = await userRepository.findById('123'); | ||
``` | ||
## Components | ||
### Aggregate | ||
Base class for aggregate classes representing domain concepts. | ||
#### clearUncommittedEvents | ||
Remove the events created through calling the aggregate methods. | ||
Is used in the [`save`](#save) method on the Repository in order to remove local events after they've been committed. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
// Adds `user.registered.event` to uncommitted events | ||
await user.register('123', 'Jeff'); | ||
// Removes the `registered` event | ||
await user.clearUncommittedEvents(); | ||
await userRepository.save(user); | ||
``` | ||
#### delete | ||
Creates a `$stream.deleted.event` for the aggregate, which the persistence partition should handle by removing the | ||
aggregate data. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
await user.delete(); | ||
await userRepository.save(user); | ||
// Should not exist anymore | ||
const nonExistingUser = await userRepository.findById('123'); | ||
``` | ||
#### getUncommittedEvents | ||
Retrieves the list of events created by calling aggregate methods. Prefer to use the async version, as this will | ||
throw if there are unprocessed commands. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
await user.register('123', 'Jeff'); | ||
await user.registerEmail('jeff@21jumpstreet.us'); | ||
// [ Event { type: 'user.registered.event' }, Event { type: 'user.email.registered.event' }] | ||
const events = user.getUncommittedEvents(); | ||
``` | ||
#### getUncommittedEventsAsync | ||
Retrieves the uncommitted events as soon as the processing queue is empty. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
await user.register('123', 'Jeff'); | ||
await user.registerEmail('jeff@21jumpstreet.us'); | ||
// [ Event { type: 'user.registered.event' }, Event { type: 'user.email.registered.event' }] | ||
const events = await user.getUncommittedEventsAsync(); | ||
``` | ||
#### getVersion | ||
Retrieves the version of the aggregate, including increments for the processed uncommitted events. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
// -1, non existing | ||
const initialVersion = user.getVersion(); | ||
await user.register('123', 'Jeff'); | ||
await user.registerEmail('jeff@21jumpstreet.us'); | ||
// 2, set to 0 for initial + 2 increments for the events | ||
const newVersion = user.getVersion(); | ||
``` | ||
### Repository | ||
You will need to provide a Partition to the Repository. | ||
#### checkConcurrencyStrategy | ||
Checks the concurrency strategy provided in the Repository constructor. Returns a Promise with a boolean stating whether | ||
it should throw an error or not. Defaults to resolving `false` if no concurrency strategy was provided. | ||
Is used in the [`save`](#save) method on the Repository in order to throw a concurrency error when there's a version | ||
mismatch. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
const initialVersion = user.getVersion(); | ||
await user.register('123', 'Jeff'); | ||
await user.registerEmail('jeff@21jumpstreet.us'); | ||
// 2, set to 0 for initial + 2 increments for the events | ||
const newVersion = user.getVersion(); | ||
``` | ||
#### findById | ||
Will look up an Aggregate in the Partition provided to the Repository by the aggregate's ID. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
``` | ||
#### findByQueryStreamWithSnapshot | ||
Will create a rehydrated aggregate by looking up the events for a stream, and processing them. Returns the rehydrated | ||
aggregate. | ||
Requires the persistence partition to implement `queryStreamWithSnapshot`. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
``` | ||
#### findBySnapshot | ||
Will look up a snapshot for the aggregate and Will create a rehydrated aggregate by looking up the events for a stream, and processing them. Returns the rehydrated | ||
aggregate. | ||
Requires the persistence partition to implement `loadSnapshot` & `queryStream`. | ||
```typescript | ||
const user = await userRepository.findById('123'); | ||
``` | ||
#### findEventsById | ||
Retrieves the committed (processed) events for the aggregate stream by the aggregate's ID. | ||
```typescript | ||
// [{ type: 'user.registered.event' }. { type: 'user.email.registered.event' }] | ||
const events = await userRepository.findEventsById('123'); | ||
``` | ||
#### save | ||
Persists the aggregate including executed commands to the persistence partition. | ||
```typescript | ||
// { id: '123', contact: [] } | ||
const user = await userRepository.findById('123'); | ||
await user.registerEmail('jeff@21jumpstreet.us'); | ||
await userRepository.save(user); | ||
// { id: '123', contact: [{ value: 'jeff@21jumpstreet.us', type: 'email' }] } | ||
const updatedUser = await userRepository.findById('123'); | ||
``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
48280
6181
31
0
274
4
17
910
1
+ Addedp-queue@^6.6.2
+ Addedbluebird@3.7.2(transitive)
+ Addedeventemitter3@4.0.7(transitive)
+ Addedp-finally@1.0.0(transitive)
+ Addedp-queue@6.6.2(transitive)
+ Addedp-timeout@3.2.0(transitive)
+ Addedslf@2.0.3(transitive)
+ Addeduuid@8.3.2(transitive)
- Removedbluebird@2.11.0(transitive)
- Removedslf@1.1.0(transitive)
- Removeduuid@3.4.0(transitive)
Updatedbluebird@^3.7.2
Updatedslf@^2.0.2
Updateduuid@^8.3.2