@stone-js/starters 0.8.3 → 0.8.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/basic-react-declarative/app/Application.tsx +22 -6
- package/basic-react-declarative/assets/css/index.css +163 -18
- package/basic-react-declarative/package.json +8 -5
- package/basic-react-declarative/public/logo.svg +16 -0
- package/basic-react-declarative/tests/Application.spec.ts +13 -4
- package/basic-react-imperative/app/Application.tsx +22 -6
- package/basic-react-imperative/assets/css/index.css +163 -18
- package/basic-react-imperative/package.json +8 -5
- package/basic-react-imperative/public/logo.svg +16 -0
- package/basic-react-imperative/tests/Application.spec.ts +15 -4
- package/basic-service-declarative/app/Application.ts +18 -3
- package/basic-service-declarative/package.json +7 -4
- package/basic-service-declarative/tests/Application.spec.ts +3 -2
- package/basic-service-imperative/app/Application.ts +18 -3
- package/basic-service-imperative/package.json +7 -4
- package/basic-service-imperative/tests/Application.spec.ts +3 -2
- package/continuum-showcase/LICENSE +21 -0
- package/continuum-showcase/app/Application.ts +45 -0
- package/continuum-showcase/app/layouts/MainLayout.tsx +13 -8
- package/continuum-showcase/app/pages/BlogPostPage.tsx +6 -3
- package/continuum-showcase/app/pages/HomePage.tsx +21 -8
- package/continuum-showcase/assets/css/index.css +198 -0
- package/continuum-showcase/assets/images/logo.svg +16 -0
- package/continuum-showcase/package.json +28 -10
- package/continuum-showcase/tests/HomePage.spec.tsx +41 -0
- package/continuum-showcase/tsconfig.json +33 -0
- package/continuum-showcase/vitest.config.ts +31 -0
- package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
- package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
- package/full-react-declarative/assets/css/index.css +167 -0
- package/full-react-declarative/package.json +13 -10
- package/full-react-declarative/public/logo.svg +16 -0
- package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
- package/full-react-declarative/tests/HomePage.spec.ts +38 -0
- package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
- package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
- package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
- package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
- package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
- package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
- package/full-react-imperative/assets/css/index.css +167 -0
- package/full-react-imperative/package.json +14 -12
- package/full-react-imperative/public/logo.svg +16 -0
- package/full-react-imperative/tests/HomePage.spec.ts +45 -0
- package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
- package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
- package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-service-declarative/package.json +12 -9
- package/full-service-declarative/tests/PostService.spec.ts +56 -0
- package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
- package/full-service-declarative/vitest.config.ts +1 -1
- package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
- package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
- package/full-service-imperative/package.json +12 -9
- package/full-service-imperative/tests/PostService.spec.ts +47 -0
- package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
- package/full-service-imperative/vitest.config.ts +1 -1
- package/package.json +1 -1
- package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -4
- package/standard-react-declarative/assets/css/index.css +163 -18
- package/standard-react-declarative/package.json +11 -8
- package/standard-react-declarative/public/logo.svg +16 -0
- package/standard-react-declarative/tests/WelcomePage.spec.ts +71 -0
- package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -4
- package/standard-react-imperative/assets/css/index.css +163 -18
- package/standard-react-imperative/package.json +11 -8
- package/standard-react-imperative/public/logo.svg +16 -0
- package/standard-react-imperative/tests/WelcomePage.spec.ts +53 -0
- package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
- package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
- package/standard-service-declarative/package.json +9 -6
- package/standard-service-declarative/tests/Application.spec.ts +100 -0
- package/standard-service-declarative/vitest.config.ts +1 -1
- package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
- package/standard-service-imperative/app/services/welcomeService.ts +28 -2
- package/standard-service-imperative/package.json +9 -6
- package/standard-service-imperative/tests/Application.spec.ts +84 -0
- package/standard-service-imperative/vitest.config.ts +1 -1
- package/basic-react-declarative/public/logo.png +0 -0
- package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/basic-react-imperative/public/logo.png +0 -0
- package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
- package/full-react-declarative/assets/img/logo.png +0 -0
- package/full-react-declarative/public/logo.png +0 -0
- package/full-react-imperative/assets/img/logo.png +0 -0
- package/full-react-imperative/public/logo.png +0 -0
|
@@ -1,26 +1,171 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
1
|
+
/**
|
|
2
|
+
* Stone.js starter — brand welcome screen.
|
|
3
|
+
* Identity: "Obsidienne & Braise" — obsidian ground, ember (braise) accent, the Portal mark.
|
|
4
|
+
* Theme-aware: dark by default, light under prefers-color-scheme: light.
|
|
5
|
+
*/
|
|
6
|
+
:root {
|
|
7
|
+
--ink: #14100E;
|
|
8
|
+
--ink-2: #1E1815;
|
|
9
|
+
--cream: #F4EFE6;
|
|
10
|
+
--cream-dim: #B8AE9E;
|
|
11
|
+
--ember-1: #FFC46B;
|
|
12
|
+
--ember-2: #FF5A1F;
|
|
13
|
+
--line: rgba(244, 239, 230, 0.10);
|
|
14
|
+
--card: rgba(244, 239, 230, 0.04);
|
|
15
|
+
--bg: radial-gradient(1200px 600px at 50% -10%, #241C17 0%, var(--ink) 55%);
|
|
9
16
|
}
|
|
10
|
-
|
|
17
|
+
|
|
18
|
+
@media (prefers-color-scheme: light) {
|
|
19
|
+
:root {
|
|
20
|
+
--ink: #F4EFE6;
|
|
21
|
+
--ink-2: #ffffff;
|
|
22
|
+
--cream: #1B1613;
|
|
23
|
+
--cream-dim: #6B6154;
|
|
24
|
+
--line: rgba(27, 22, 19, 0.10);
|
|
25
|
+
--card: rgba(27, 22, 19, 0.03);
|
|
26
|
+
--bg: radial-gradient(1200px 600px at 50% -10%, #FFFFFF 0%, #F1E9DC 60%);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
* { box-sizing: border-box; }
|
|
31
|
+
|
|
32
|
+
body {
|
|
11
33
|
margin: 0;
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
color: var(--cream);
|
|
36
|
+
background: var(--ink);
|
|
37
|
+
font-family: ui-sans-serif, system-ui, -apple-system, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
|
38
|
+
-webkit-font-smoothing: antialiased;
|
|
15
39
|
}
|
|
16
|
-
|
|
17
|
-
|
|
40
|
+
|
|
41
|
+
.stone-welcome {
|
|
42
|
+
position: relative;
|
|
43
|
+
min-height: 100vh;
|
|
18
44
|
display: flex;
|
|
45
|
+
flex-direction: column;
|
|
46
|
+
align-items: center;
|
|
19
47
|
justify-content: center;
|
|
48
|
+
padding: 48px 24px;
|
|
49
|
+
background: var(--bg);
|
|
50
|
+
overflow: hidden;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/* Ember glow behind the mark. */
|
|
54
|
+
.stone-welcome .glow {
|
|
55
|
+
position: absolute;
|
|
56
|
+
top: 8%;
|
|
57
|
+
left: 50%;
|
|
58
|
+
width: 520px;
|
|
59
|
+
height: 520px;
|
|
60
|
+
transform: translateX(-50%);
|
|
61
|
+
background: radial-gradient(circle, rgba(255, 90, 31, 0.22) 0%, rgba(255, 90, 31, 0) 68%);
|
|
62
|
+
filter: blur(8px);
|
|
63
|
+
pointer-events: none;
|
|
64
|
+
z-index: 0;
|
|
20
65
|
}
|
|
21
|
-
|
|
66
|
+
|
|
67
|
+
.stone-welcome .hero {
|
|
68
|
+
position: relative;
|
|
69
|
+
z-index: 1;
|
|
70
|
+
width: 100%;
|
|
71
|
+
max-width: 680px;
|
|
22
72
|
text-align: center;
|
|
73
|
+
animation: rise 0.7s cubic-bezier(0.2, 0.7, 0.2, 1) both;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.stone-welcome .mark {
|
|
77
|
+
width: 104px;
|
|
78
|
+
height: 104px;
|
|
79
|
+
filter: drop-shadow(0 8px 28px rgba(255, 90, 31, 0.35));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
.stone-welcome .eyebrow {
|
|
83
|
+
margin: 22px 0 0;
|
|
84
|
+
font-size: 12px;
|
|
85
|
+
font-weight: 600;
|
|
86
|
+
letter-spacing: 0.28em;
|
|
87
|
+
text-transform: uppercase;
|
|
88
|
+
color: var(--cream-dim);
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.stone-welcome .title {
|
|
92
|
+
margin: 8px 0 0;
|
|
93
|
+
font-size: clamp(44px, 8vw, 76px);
|
|
94
|
+
font-weight: 800;
|
|
95
|
+
letter-spacing: -0.03em;
|
|
96
|
+
line-height: 1.02;
|
|
97
|
+
background: linear-gradient(92deg, var(--ember-1), var(--ember-2));
|
|
98
|
+
-webkit-background-clip: text;
|
|
99
|
+
background-clip: text;
|
|
100
|
+
color: transparent;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.stone-welcome .lead {
|
|
104
|
+
margin: 18px auto 0;
|
|
105
|
+
font-size: 20px;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
color: var(--cream);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.stone-welcome .tagline {
|
|
111
|
+
margin: 10px auto 0;
|
|
112
|
+
max-width: 46ch;
|
|
113
|
+
font-size: 16px;
|
|
114
|
+
line-height: 1.6;
|
|
115
|
+
color: var(--cream-dim);
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
.stone-welcome .links {
|
|
119
|
+
margin-top: 34px;
|
|
120
|
+
display: flex;
|
|
121
|
+
flex-wrap: wrap;
|
|
122
|
+
gap: 12px;
|
|
123
|
+
justify-content: center;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.stone-welcome .links a,
|
|
127
|
+
.stone-welcome .links .edit {
|
|
128
|
+
display: inline-flex;
|
|
129
|
+
align-items: center;
|
|
130
|
+
gap: 8px;
|
|
131
|
+
padding: 11px 18px;
|
|
132
|
+
border: 1px solid var(--line);
|
|
133
|
+
border-radius: 12px;
|
|
134
|
+
background: var(--card);
|
|
135
|
+
color: var(--cream);
|
|
136
|
+
font-size: 14px;
|
|
137
|
+
font-weight: 600;
|
|
138
|
+
text-decoration: none;
|
|
139
|
+
transition: border-color 0.2s, transform 0.2s, background 0.2s;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
.stone-welcome .links a:hover {
|
|
143
|
+
transform: translateY(-2px);
|
|
144
|
+
border-color: rgba(255, 90, 31, 0.6);
|
|
145
|
+
background: rgba(255, 90, 31, 0.08);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
.stone-welcome .links .edit {
|
|
149
|
+
font-family: ui-monospace, 'SF Mono', Menlo, Monaco, Consolas, monospace;
|
|
150
|
+
color: var(--cream-dim);
|
|
151
|
+
}
|
|
152
|
+
.stone-welcome .links .edit b { color: var(--ember-1); font-weight: 700; }
|
|
153
|
+
|
|
154
|
+
.stone-welcome .brand {
|
|
155
|
+
position: relative;
|
|
156
|
+
z-index: 1;
|
|
157
|
+
margin-top: 56px;
|
|
158
|
+
font-size: 13px;
|
|
159
|
+
letter-spacing: 0.02em;
|
|
160
|
+
color: var(--cream-dim);
|
|
161
|
+
}
|
|
162
|
+
.stone-welcome .brand .dot { color: var(--ember-2); }
|
|
163
|
+
|
|
164
|
+
@keyframes rise {
|
|
165
|
+
from { opacity: 0; transform: translateY(14px); }
|
|
166
|
+
to { opacity: 1; transform: translateY(0); }
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
@media (prefers-reduced-motion: reduce) {
|
|
170
|
+
.stone-welcome .hero { animation: none; }
|
|
23
171
|
}
|
|
24
|
-
.mt-64 {
|
|
25
|
-
margin-top: 64px;
|
|
26
|
-
}
|
|
@@ -20,23 +20,26 @@
|
|
|
20
20
|
"dependencies": {
|
|
21
21
|
"react": "^19.0.0",
|
|
22
22
|
"react-dom": "^19.0.0",
|
|
23
|
-
"@stone-js/use-react": "^0.8.
|
|
24
|
-
"@stone-js/browser-adapter": "^0.8.
|
|
25
|
-
"@stone-js/pipeline": "^0.8.
|
|
26
|
-
"@stone-js/http-core": "^0.8.
|
|
27
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
28
|
-
"@stone-js/router": "^0.8.
|
|
29
|
-
"@stone-js/core": "^0.8.
|
|
23
|
+
"@stone-js/use-react": "^0.8.5",
|
|
24
|
+
"@stone-js/browser-adapter": "^0.8.5",
|
|
25
|
+
"@stone-js/pipeline": "^0.8.5",
|
|
26
|
+
"@stone-js/http-core": "^0.8.5",
|
|
27
|
+
"@stone-js/node-http-adapter": "^0.8.5",
|
|
28
|
+
"@stone-js/router": "^0.8.5",
|
|
29
|
+
"@stone-js/core": "^0.8.5"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
33
33
|
"@babel/preset-env": "^7.26.9",
|
|
34
34
|
"@babel/preset-typescript": "^7.27.0",
|
|
35
|
-
"@stone-js/cli": "^0.8.
|
|
35
|
+
"@stone-js/cli": "^0.8.5",
|
|
36
36
|
"@types/node": "^24.0.7",
|
|
37
37
|
"@types/react": "^19.0.7",
|
|
38
38
|
"@types/react-dom": "^19.0.3",
|
|
39
39
|
"@vitest/coverage-v8": "^3.0.9",
|
|
40
40
|
"vitest": "^3.0.9"
|
|
41
|
+
},
|
|
42
|
+
"overrides": {
|
|
43
|
+
"glob": "^11.0.0"
|
|
41
44
|
}
|
|
42
45
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 96 96" width="96" height="96" role="img" aria-label="Stone.js">
|
|
2
|
+
<title>Stone.js</title>
|
|
3
|
+
<style>
|
|
4
|
+
.ink { stroke: #1B1613; }
|
|
5
|
+
@media (prefers-color-scheme: dark) { .ink { stroke: #EFE8DC; } }
|
|
6
|
+
</style>
|
|
7
|
+
<defs>
|
|
8
|
+
<linearGradient id="braise" gradientUnits="userSpaceOnUse" x1="22" y1="14" x2="74" y2="26.1">
|
|
9
|
+
<stop offset="0" stop-color="#FFC46B" />
|
|
10
|
+
<stop offset="1" stop-color="#FF5A1F" />
|
|
11
|
+
</linearGradient>
|
|
12
|
+
</defs>
|
|
13
|
+
<path class="ink" d="M 79.96 36.4 A 34 34 0 0 1 53.9 81.5" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
14
|
+
<path class="ink" d="M 42.1 81.5 A 34 34 0 0 1 16.05 36.4" fill="none" stroke-width="11" stroke-linecap="round" opacity=".85" />
|
|
15
|
+
<path d="M 22 26.1 A 34 34 0 0 1 74 26.1" fill="none" stroke="url(#braise)" stroke-width="11" stroke-linecap="round" />
|
|
16
|
+
</svg>
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { ILogger } from '@stone-js/core'
|
|
2
|
+
import { renderToString } from 'react-dom/server'
|
|
3
|
+
import { ReactIncomingEvent } from '@stone-js/use-react'
|
|
4
|
+
import { WelcomePage } from '../app/pages/welcome/WelcomePage'
|
|
5
|
+
import { WelcomeService } from '../app/services/WelcomeService'
|
|
6
|
+
|
|
7
|
+
describe('WelcomePage', () => {
|
|
8
|
+
let page: any
|
|
9
|
+
let mockedLogger: ILogger
|
|
10
|
+
|
|
11
|
+
beforeEach(() => {
|
|
12
|
+
mockedLogger = {
|
|
13
|
+
info: vi.fn(),
|
|
14
|
+
} as unknown as ILogger
|
|
15
|
+
|
|
16
|
+
page = WelcomePage({ welcomeService: WelcomeService({ logger: mockedLogger }) })
|
|
17
|
+
})
|
|
18
|
+
|
|
19
|
+
it('should handle incoming events', () => {
|
|
20
|
+
// Arrange
|
|
21
|
+
const expectedMessage = 'Hello World!'
|
|
22
|
+
const event = { get: () => 'World' } as unknown as ReactIncomingEvent
|
|
23
|
+
|
|
24
|
+
// Act
|
|
25
|
+
const response = page.handle(event) as { message: string }
|
|
26
|
+
|
|
27
|
+
// Assert
|
|
28
|
+
expect(response.message).toBe(expectedMessage)
|
|
29
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
|
|
30
|
+
})
|
|
31
|
+
|
|
32
|
+
it('should render the message', () => {
|
|
33
|
+
// Arrange
|
|
34
|
+
const message = 'Hello World!'
|
|
35
|
+
|
|
36
|
+
// Act
|
|
37
|
+
const response = renderToString(page.render({ data: { message } } as any))
|
|
38
|
+
|
|
39
|
+
// Assert
|
|
40
|
+
expect(response).toContain(message)
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('should render the Stone.js logo', () => {
|
|
44
|
+
// Act
|
|
45
|
+
const response = renderToString(page.render({ data: { message: 'Hello World!' } } as any))
|
|
46
|
+
|
|
47
|
+
// Assert
|
|
48
|
+
expect(response).toContain('/logo.svg')
|
|
49
|
+
expect(response).toContain('alt="Stone.js"')
|
|
50
|
+
expect(response).toContain('Welcome')
|
|
51
|
+
expect(response).toContain('Stone.js')
|
|
52
|
+
})
|
|
53
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { EventHandler, Get } from '@stone-js/router'
|
|
2
|
-
import { WelcomeService } from '../services/WelcomeService'
|
|
2
|
+
import { WelcomeService, WelcomeResponse } from '../services/WelcomeService'
|
|
3
3
|
import { IncomingHttpEvent, JsonHttpResponse } from '@stone-js/http-core'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -39,7 +39,7 @@ export class WelcomeEventHandler {
|
|
|
39
39
|
*/
|
|
40
40
|
@Get('/:name?')
|
|
41
41
|
@JsonHttpResponse(200)
|
|
42
|
-
welcome (event: IncomingHttpEvent):
|
|
42
|
+
welcome (event: IncomingHttpEvent): WelcomeResponse {
|
|
43
43
|
return this.welcomeService.welcome(event.get<string>('name', 'World'))
|
|
44
44
|
}
|
|
45
45
|
}
|
|
@@ -7,6 +7,24 @@ export interface WelcomeServiceOptions {
|
|
|
7
7
|
logger: ILogger
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Framework information carried by the welcome payload.
|
|
12
|
+
*/
|
|
13
|
+
export interface FrameworkInfo {
|
|
14
|
+
name: string
|
|
15
|
+
tagline: string
|
|
16
|
+
docs: string
|
|
17
|
+
github: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The branded welcome payload returned to the client.
|
|
22
|
+
*/
|
|
23
|
+
export interface WelcomeResponse {
|
|
24
|
+
message: string
|
|
25
|
+
framework: FrameworkInfo
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
/**
|
|
11
29
|
* Welcome Service
|
|
12
30
|
*
|
|
@@ -31,8 +49,16 @@ export class WelcomeService {
|
|
|
31
49
|
* @param name - The name
|
|
32
50
|
* @returns A welcome message
|
|
33
51
|
*/
|
|
34
|
-
welcome (name: string):
|
|
52
|
+
welcome (name: string): WelcomeResponse {
|
|
35
53
|
this.logger.info(`Welcome ${name}`)
|
|
36
|
-
return {
|
|
54
|
+
return {
|
|
55
|
+
message: `Hello ${name}! Welcome to Stone.js.`,
|
|
56
|
+
framework: {
|
|
57
|
+
name: 'Stone.js',
|
|
58
|
+
tagline: 'The continuum framework',
|
|
59
|
+
docs: 'https://stonejs.dev',
|
|
60
|
+
github: 'https://github.com/stone-foundation/stone-js-framework'
|
|
61
|
+
}
|
|
62
|
+
}
|
|
37
63
|
}
|
|
38
64
|
}
|
|
@@ -18,19 +18,22 @@
|
|
|
18
18
|
"test:cvg": "npm run test -- --coverage"
|
|
19
19
|
},
|
|
20
20
|
"dependencies": {
|
|
21
|
-
"@stone-js/pipeline": "^0.8.
|
|
22
|
-
"@stone-js/http-core": "^0.8.
|
|
23
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
24
|
-
"@stone-js/router": "^0.8.
|
|
25
|
-
"@stone-js/core": "^0.8.
|
|
21
|
+
"@stone-js/pipeline": "^0.8.5",
|
|
22
|
+
"@stone-js/http-core": "^0.8.5",
|
|
23
|
+
"@stone-js/node-http-adapter": "^0.8.5",
|
|
24
|
+
"@stone-js/router": "^0.8.5",
|
|
25
|
+
"@stone-js/core": "^0.8.5"
|
|
26
26
|
},
|
|
27
27
|
"devDependencies": {
|
|
28
28
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
29
29
|
"@babel/preset-env": "^7.26.9",
|
|
30
30
|
"@babel/preset-typescript": "^7.27.0",
|
|
31
|
-
"@stone-js/cli": "^0.8.
|
|
31
|
+
"@stone-js/cli": "^0.8.5",
|
|
32
32
|
"@types/node": "^24.0.7",
|
|
33
33
|
"@vitest/coverage-v8": "^3.0.9",
|
|
34
34
|
"vitest": "^3.0.9"
|
|
35
|
+
},
|
|
36
|
+
"overrides": {
|
|
37
|
+
"glob": "^11.0.0"
|
|
35
38
|
}
|
|
36
39
|
}
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { ILogger } from '@stone-js/core'
|
|
2
|
+
import { IncomingHttpEvent } from '@stone-js/http-core'
|
|
3
|
+
import { WelcomeService } from '../app/services/WelcomeService'
|
|
4
|
+
import { WelcomeEventHandler } from '../app/handlers/WelcomeEventHandler'
|
|
5
|
+
|
|
6
|
+
// We must mock decorators to lighten the test environment
|
|
7
|
+
vi.mock(import('@stone-js/core'), async (importOriginal) => {
|
|
8
|
+
const actual = await importOriginal()
|
|
9
|
+
return {
|
|
10
|
+
...actual,
|
|
11
|
+
Service: vi.fn(() => vi.fn())
|
|
12
|
+
}
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
// We must mock decorators to lighten the test environment
|
|
16
|
+
vi.mock(import('@stone-js/router'), async (importOriginal) => {
|
|
17
|
+
const actual = await importOriginal()
|
|
18
|
+
return {
|
|
19
|
+
...actual,
|
|
20
|
+
EventHandler: vi.fn(() => vi.fn()),
|
|
21
|
+
Get: vi.fn(() => vi.fn())
|
|
22
|
+
}
|
|
23
|
+
})
|
|
24
|
+
|
|
25
|
+
// We must mock decorators to lighten the test environment
|
|
26
|
+
vi.mock(import('@stone-js/http-core'), async (importOriginal) => {
|
|
27
|
+
const actual = await importOriginal()
|
|
28
|
+
return {
|
|
29
|
+
...actual,
|
|
30
|
+
JsonHttpResponse: vi.fn(() => vi.fn())
|
|
31
|
+
}
|
|
32
|
+
})
|
|
33
|
+
|
|
34
|
+
describe('WelcomeService', () => {
|
|
35
|
+
let mockedLogger: ILogger
|
|
36
|
+
let service: WelcomeService
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
mockedLogger = {
|
|
40
|
+
info: vi.fn()
|
|
41
|
+
} as unknown as ILogger
|
|
42
|
+
|
|
43
|
+
service = new WelcomeService({ logger: mockedLogger })
|
|
44
|
+
})
|
|
45
|
+
|
|
46
|
+
it('should build a branded welcome message for the given name', () => {
|
|
47
|
+
// Act
|
|
48
|
+
const response = service.welcome('Stone')
|
|
49
|
+
|
|
50
|
+
// Assert
|
|
51
|
+
expect(response.message).toBe('Hello Stone! Welcome to Stone.js.')
|
|
52
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
53
|
+
expect(response.framework).toMatchObject({
|
|
54
|
+
name: 'Stone.js',
|
|
55
|
+
tagline: 'The continuum framework',
|
|
56
|
+
docs: 'https://stonejs.dev',
|
|
57
|
+
github: 'https://github.com/stone-foundation/stone-js-framework'
|
|
58
|
+
})
|
|
59
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome Stone')
|
|
60
|
+
})
|
|
61
|
+
})
|
|
62
|
+
|
|
63
|
+
describe('WelcomeEventHandler', () => {
|
|
64
|
+
let mockedLogger: ILogger
|
|
65
|
+
let handler: WelcomeEventHandler
|
|
66
|
+
|
|
67
|
+
beforeEach(() => {
|
|
68
|
+
mockedLogger = {
|
|
69
|
+
info: vi.fn()
|
|
70
|
+
} as unknown as ILogger
|
|
71
|
+
|
|
72
|
+
handler = new WelcomeEventHandler({ welcomeService: new WelcomeService({ logger: mockedLogger }) })
|
|
73
|
+
})
|
|
74
|
+
|
|
75
|
+
it('should welcome the name carried by the event', () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
const event = { get: () => 'World' } as unknown as IncomingHttpEvent
|
|
78
|
+
|
|
79
|
+
// Act
|
|
80
|
+
const response = handler.welcome(event)
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
expect(response.message).toBe('Hello World! Welcome to Stone.js.')
|
|
84
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
85
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
it('should fall back to the default name when the event carries none', () => {
|
|
89
|
+
// Arrange
|
|
90
|
+
const event = { get: (_key: string, fallback: string) => fallback } as unknown as IncomingHttpEvent
|
|
91
|
+
|
|
92
|
+
// Act
|
|
93
|
+
const response = handler.welcome(event)
|
|
94
|
+
|
|
95
|
+
// Assert
|
|
96
|
+
expect(response.message).toBe('Hello World! Welcome to Stone.js.')
|
|
97
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
98
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
|
|
99
|
+
})
|
|
100
|
+
})
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { IncomingHttpEvent } from '@stone-js/http-core'
|
|
2
|
-
import { WelcomeService } from '../services/welcomeService'
|
|
2
|
+
import { WelcomeService, WelcomeResponse } from '../services/welcomeService'
|
|
3
3
|
import { defineRoute, FactoryEventHandler, FunctionalEventHandler } from '@stone-js/router'
|
|
4
4
|
|
|
5
5
|
/**
|
|
@@ -12,9 +12,7 @@ export interface WelcomeEventHandlerOptions {
|
|
|
12
12
|
/**
|
|
13
13
|
* Response data
|
|
14
14
|
*/
|
|
15
|
-
export
|
|
16
|
-
message: string
|
|
17
|
-
}
|
|
15
|
+
export type ResponseData = WelcomeResponse
|
|
18
16
|
|
|
19
17
|
/**
|
|
20
18
|
* Welcome
|
|
@@ -7,6 +7,24 @@ export interface WelcomeServiceOptions {
|
|
|
7
7
|
logger: ILogger
|
|
8
8
|
}
|
|
9
9
|
|
|
10
|
+
/**
|
|
11
|
+
* Framework information carried by the welcome payload.
|
|
12
|
+
*/
|
|
13
|
+
export interface FrameworkInfo {
|
|
14
|
+
name: string
|
|
15
|
+
tagline: string
|
|
16
|
+
docs: string
|
|
17
|
+
github: string
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* The branded welcome payload returned to the client.
|
|
22
|
+
*/
|
|
23
|
+
export interface WelcomeResponse {
|
|
24
|
+
message: string
|
|
25
|
+
framework: FrameworkInfo
|
|
26
|
+
}
|
|
27
|
+
|
|
10
28
|
/**
|
|
11
29
|
* Welcome Service Type
|
|
12
30
|
*/
|
|
@@ -23,9 +41,17 @@ export const WelcomeService = ({ logger }: WelcomeServiceOptions): Record<Proper
|
|
|
23
41
|
* @param name - The name
|
|
24
42
|
* @returns A welcome message
|
|
25
43
|
*/
|
|
26
|
-
welcome (name: string):
|
|
44
|
+
welcome (name: string): WelcomeResponse {
|
|
27
45
|
logger.info(`Welcome ${name}`)
|
|
28
|
-
return {
|
|
46
|
+
return {
|
|
47
|
+
message: `Hello ${name}! Welcome to Stone.js.`,
|
|
48
|
+
framework: {
|
|
49
|
+
name: 'Stone.js',
|
|
50
|
+
tagline: 'The continuum framework',
|
|
51
|
+
docs: 'https://stonejs.dev',
|
|
52
|
+
github: 'https://github.com/stone-foundation/stone-js-framework'
|
|
53
|
+
}
|
|
54
|
+
}
|
|
29
55
|
}
|
|
30
56
|
})
|
|
31
57
|
|
|
@@ -17,19 +17,22 @@
|
|
|
17
17
|
"test:cvg": "npm run test -- --coverage"
|
|
18
18
|
},
|
|
19
19
|
"dependencies": {
|
|
20
|
-
"@stone-js/pipeline": "^0.8.
|
|
21
|
-
"@stone-js/http-core": "^0.8.
|
|
22
|
-
"@stone-js/node-http-adapter": "^0.8.
|
|
23
|
-
"@stone-js/router": "^0.8.
|
|
24
|
-
"@stone-js/core": "^0.8.
|
|
20
|
+
"@stone-js/pipeline": "^0.8.5",
|
|
21
|
+
"@stone-js/http-core": "^0.8.5",
|
|
22
|
+
"@stone-js/node-http-adapter": "^0.8.5",
|
|
23
|
+
"@stone-js/router": "^0.8.5",
|
|
24
|
+
"@stone-js/core": "^0.8.5"
|
|
25
25
|
},
|
|
26
26
|
"devDependencies": {
|
|
27
27
|
"@babel/plugin-proposal-decorators": "^7.25.9",
|
|
28
28
|
"@babel/preset-env": "^7.26.9",
|
|
29
29
|
"@babel/preset-typescript": "^7.27.0",
|
|
30
|
-
"@stone-js/cli": "^0.8.
|
|
30
|
+
"@stone-js/cli": "^0.8.5",
|
|
31
31
|
"@types/node": "^24.0.7",
|
|
32
32
|
"@vitest/coverage-v8": "^3.0.9",
|
|
33
33
|
"vitest": "^3.0.9"
|
|
34
|
+
},
|
|
35
|
+
"overrides": {
|
|
36
|
+
"glob": "^11.0.0"
|
|
34
37
|
}
|
|
35
38
|
}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { ILogger } from '@stone-js/core'
|
|
2
|
+
import { IncomingHttpEvent } from '@stone-js/http-core'
|
|
3
|
+
import { WelcomeService } from '../app/services/welcomeService'
|
|
4
|
+
import { WelcomeEventHandler } from '../app/handlers/welcomeEventHandler'
|
|
5
|
+
|
|
6
|
+
describe('WelcomeService', () => {
|
|
7
|
+
let mockedLogger: ILogger
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
mockedLogger = {
|
|
11
|
+
info: vi.fn()
|
|
12
|
+
} as unknown as ILogger
|
|
13
|
+
})
|
|
14
|
+
|
|
15
|
+
it('should build a welcome message for the given name', () => {
|
|
16
|
+
// Arrange
|
|
17
|
+
const service = WelcomeService({ logger: mockedLogger })
|
|
18
|
+
|
|
19
|
+
// Act
|
|
20
|
+
const response = service.welcome('Stone')
|
|
21
|
+
|
|
22
|
+
// Assert
|
|
23
|
+
expect(response.message).toBe('Hello Stone! Welcome to Stone.js.')
|
|
24
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
25
|
+
expect(response.framework).toMatchObject({
|
|
26
|
+
name: 'Stone.js',
|
|
27
|
+
tagline: 'The continuum framework',
|
|
28
|
+
docs: 'https://stonejs.dev',
|
|
29
|
+
github: 'https://github.com/stone-foundation/stone-js-framework'
|
|
30
|
+
})
|
|
31
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome Stone')
|
|
32
|
+
})
|
|
33
|
+
})
|
|
34
|
+
|
|
35
|
+
describe('WelcomeEventHandler', () => {
|
|
36
|
+
let mockedLogger: ILogger
|
|
37
|
+
|
|
38
|
+
beforeEach(() => {
|
|
39
|
+
mockedLogger = {
|
|
40
|
+
info: vi.fn()
|
|
41
|
+
} as unknown as ILogger
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
it('should create a functional event handler', () => {
|
|
45
|
+
// Arrange
|
|
46
|
+
const welcomeService = WelcomeService({ logger: mockedLogger })
|
|
47
|
+
|
|
48
|
+
// Act
|
|
49
|
+
const handler = WelcomeEventHandler({ welcomeService })
|
|
50
|
+
|
|
51
|
+
// Assert
|
|
52
|
+
expect(handler).toBeTypeOf('function')
|
|
53
|
+
})
|
|
54
|
+
|
|
55
|
+
it('should welcome the name carried by the event', () => {
|
|
56
|
+
// Arrange
|
|
57
|
+
const welcomeService = WelcomeService({ logger: mockedLogger })
|
|
58
|
+
const handler = WelcomeEventHandler({ welcomeService })
|
|
59
|
+
const event = { get: () => 'World' } as unknown as IncomingHttpEvent
|
|
60
|
+
|
|
61
|
+
// Act
|
|
62
|
+
const response = handler(event)
|
|
63
|
+
|
|
64
|
+
// Assert
|
|
65
|
+
expect(response.message).toBe('Hello World! Welcome to Stone.js.')
|
|
66
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
67
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
|
|
68
|
+
})
|
|
69
|
+
|
|
70
|
+
it('should fall back to the default name when the event carries none', () => {
|
|
71
|
+
// Arrange
|
|
72
|
+
const welcomeService = WelcomeService({ logger: mockedLogger })
|
|
73
|
+
const handler = WelcomeEventHandler({ welcomeService })
|
|
74
|
+
const event = { get: (_key: string, fallback: string) => fallback } as unknown as IncomingHttpEvent
|
|
75
|
+
|
|
76
|
+
// Act
|
|
77
|
+
const response = handler(event)
|
|
78
|
+
|
|
79
|
+
// Assert
|
|
80
|
+
expect(response.message).toBe('Hello World! Welcome to Stone.js.')
|
|
81
|
+
expect(response.framework.name).toBe('Stone.js')
|
|
82
|
+
expect(mockedLogger.info).toHaveBeenCalledWith('Welcome World')
|
|
83
|
+
})
|
|
84
|
+
})
|
|
Binary file
|