Skip to content

Commit e956707

Browse files
authored
Merge pull request #105 from dolittle/express-extension
Express extension
2 parents 1d51be6 + 0f3ce24 commit e956707

36 files changed

Lines changed: 862 additions & 23 deletions

Samples/ExpressJS/.eslintrc.js

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Dolittle. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
module.exports = {
5+
extends: '../../.eslintrc.js',
6+
rules: {
7+
'no-restricted-globals': 'off',
8+
'@typescript-eslint/naming-convention' : 'off',
9+
'jsdoc/require-jsdoc': 'off',
10+
}
11+
};

Samples/ExpressJS/Chef.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Dolittle. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
// Sample code for the tutorial at https://dolittle.io/docs/tutorials/projections/
5+
6+
export class Chef {
7+
constructor(
8+
public name: string = '',
9+
public dishes: string[] = []
10+
) { }
11+
}

Samples/ExpressJS/DishCounter.ts

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
// Copyright (c) Dolittle. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
// Sample code for the tutorial at https://dolittle.io/tutorials/projections/typescript/
5+
6+
import { ProjectionContext, projection, on } from '@dolittle/sdk.projections';
7+
8+
import { DishPrepared } from './DishPrepared';
9+
10+
@projection('98f9db66-b6ca-4e5f-9fc3-638626c9ecfa')
11+
export class DishCounter {
12+
numberOfTimesPrepared: number = 0;
13+
14+
@on(DishPrepared, _ => _.keyFromProperty('Dish'))
15+
on(event: DishPrepared, projectionContext: ProjectionContext) {
16+
this.numberOfTimesPrepared ++;
17+
}
18+
}

Samples/ExpressJS/DishPrepared.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright (c) Dolittle. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
// Sample code for the tutorial at https://dolittle.io/tutorials/projections/typescript/
5+
6+
import { eventType } from '@dolittle/sdk.events';
7+
8+
@eventType('1844473f-d714-4327-8b7f-5b3c2bdfc26a')
9+
export class DishPrepared {
10+
constructor(readonly Dish: string, readonly Chef: string) {}
11+
}

Samples/ExpressJS/index.ts

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Copyright (c) Dolittle. All rights reserved.
2+
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
3+
4+
// Sample code for the tutorial at https://dolittle.io/docs/tutorials/projections/
5+
6+
import createApplication, { static as serveStatic } from 'express';
7+
import { json } from 'body-parser';
8+
import { Logger } from 'winston';
9+
import { dolittle, inject } from '@dolittle/sdk.extensions.express';
10+
import { IProjectionStore } from '@dolittle/sdk.projections';
11+
12+
import { DishCounter } from './DishCounter';
13+
import { DishPrepared } from './DishPrepared';
14+
15+
const application = createApplication();
16+
17+
application.use(dolittle());
18+
application.use(json());
19+
20+
application.post('/prepare', (req, res, next) => {
21+
const { chef, dish } = req.body;
22+
req.dolittle.logger.info(`Received request to prepare dish ${dish} by chef ${chef}`);
23+
req.dolittle.eventStore
24+
.commit(new DishPrepared(dish, chef), 'Dolittle Tacos')
25+
.then(result => res.json(result))
26+
.catch(next);
27+
});
28+
29+
application.get(
30+
'/counters',
31+
inject(IProjectionStore, 'Logger')(
32+
(req, res, next, projections, logger: Logger) => {
33+
logger.info('Received reqest to get DishCounter projection');
34+
projections.getAll(DishCounter)
35+
.then(result => Array.from(result.values()))
36+
.then(results => results.map(({ key, state }) => ({ dish: key.value, ...state })))
37+
.then(result => res.json(result))
38+
.catch(next);
39+
}
40+
));
41+
42+
application.use('/', serveStatic('public'));
43+
44+
const port = 8080;
45+
application.listen(port, () => {
46+
console.log(`Listening on port ${port}`);
47+
});

Samples/ExpressJS/package.json

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
{
2+
"name": "expressjs",
3+
"private": true,
4+
"version": "21.0.0-gimli.15",
5+
"main": "index.js",
6+
"author": "Dolittle",
7+
"license": "MIT",
8+
"scripts": {
9+
"watch": "cross-env NODE_ENV=development nodemon --watch **/*.ts --exec 'ts-node' index.ts",
10+
"start": "cross-env NODE_ENV=development ts-node index.ts",
11+
"test": "mocha",
12+
"build": "tsc -b ./tsconfig.json"
13+
},
14+
"dependencies": {
15+
"@dolittle/sdk": "21.0.0-gimli.15",
16+
"@dolittle/sdk.artifacts": "21.0.0-gimli.15",
17+
"@dolittle/sdk.events": "21.0.0-gimli.15",
18+
"@dolittle/sdk.events.handling": "21.0.0-gimli.15",
19+
"@dolittle/sdk.extensions.express": "21.0.0-gimli.15",
20+
"body-parser": "^1.19.1",
21+
"express": "^4.17.2",
22+
"winston": "^3.4.0"
23+
},
24+
"devDependencies": {
25+
"@types/express": "^4.17.13",
26+
"cross-env": "^7.0.3",
27+
"nodemon": "^2.0.4",
28+
"ts-node": "^8.10.1"
29+
}
30+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<title>Welcome to the Dolittle Tacos kitchen management system.</title>
5+
<style>
6+
p#error {
7+
color: red;
8+
display: none;
9+
}
10+
thead {
11+
font-weight: bold;
12+
}
13+
tbody tr td:nth-child(2) {
14+
text-align: right;
15+
}
16+
</style>
17+
<script>
18+
function stringIsEmpty(value) {
19+
return value === undefined || value === null || value.trim().length === 0;
20+
}
21+
22+
function updateCounterTable() {
23+
const table = document.querySelector('tbody#counters');
24+
fetch('/counters')
25+
.then(response => response.json())
26+
.then(counters => {
27+
console.log('Fetched projection result', counters);
28+
table.replaceChildren();
29+
for (const { dish, numberOfTimesPrepared } of counters) {
30+
const row = document.createElement('tr');
31+
row.appendChild(document.createElement('td')).innerText = dish;
32+
row.appendChild(document.createElement('td')).innerText = numberOfTimesPrepared;
33+
table.appendChild(row);
34+
}
35+
});
36+
}
37+
38+
function prepareDishFromFrom(event) {
39+
event.preventDefault();
40+
41+
const form = event.target;
42+
const warning = form.querySelector('p#error');
43+
44+
const chef = form.querySelector('input#chef').value;
45+
const dish = form.querySelector('input#dish').value;
46+
47+
if (stringIsEmpty(chef) || stringIsEmpty(dish)) {
48+
warning.style.display = 'initial';
49+
return;
50+
}
51+
52+
warning.style.display = 'none';
53+
54+
const body = JSON.stringify({ chef, dish });
55+
fetch('/prepare', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body })
56+
.then(response => response.json())
57+
.then(result => {
58+
console.log('Commit event result', result);
59+
setTimeout(updateCounterTable, 250);
60+
})
61+
.catch(error => console.error(error));
62+
}
63+
</script>
64+
</head>
65+
<body onload="updateCounterTable()">
66+
<h1>Welcome to the Dolittle Tacos kitchen management system.</h1>
67+
68+
<h2>Register a prepared dish:</h2>
69+
<form onsubmit="prepareDishFromFrom(event)">
70+
<p>
71+
<input type="text" id="chef" name="chef" placeholder="Chef">
72+
<input type="text" id="dish" name="dish" placeholder="Dish">
73+
<input type="submit" value="Prepare">
74+
</p>
75+
<p id="error">Both 'Chef' and 'Dish' must be specified</p>
76+
</form>
77+
78+
<h2>Previously prepared dishes:</h2>
79+
<table>
80+
<thead>
81+
<td>Dish:</td>
82+
<td>Number of times prepared:</td>
83+
</thead>
84+
<tbody id="counters">
85+
</tbody>
86+
</table>
87+
</body>
88+
</html>

Samples/ExpressJS/tsconfig.json

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
{
2+
"extends": "../../tsconfig.settings.json",
3+
"compilerOptions": {
4+
"sourceRoot": "../",
5+
"inlineSourceMap": true,
6+
"sourceMap": false,
7+
"outDir": "Distribution"
8+
},
9+
"include": ["**/*.ts"],
10+
"exclude": ["node_modules", "Distribution"],
11+
"references": [
12+
{ "path": "../../Source/sdk" },
13+
{ "path": "../../Source/extensions.express" },
14+
]
15+
}

Samples/tsconfig.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
"include": [],
44
"references": [
55
{ "path": "./Container"},
6+
{ "path": "./ExpressJS"},
67
{ "path": "./Tutorials/Aggregates" },
78
{ "path": "./Tutorials/Embeddings" },
89
{ "path": "./Tutorials/EventHorizon/Consumer" },

Source/dependencyInversion/ServiceIdentifier.ts

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,15 @@
11
// Copyright (c) Dolittle. All rights reserved.
22
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
33

4-
type Newable<T> = new (...args: never[]) => T;
4+
/**
5+
* Represents a type that can be constructed.
6+
*/
7+
export type Newable<T> = new (...args: never[]) => T;
58

6-
type Abstract<T> = { prototype: T };
9+
/**
10+
* Represents an abstract type.
11+
*/
12+
export type Abstract<T> = { prototype: T };
713

814
/**
915
* Represents an identifier of a service.

0 commit comments

Comments
 (0)