@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.
Files changed (91) hide show
  1. package/basic-react-declarative/app/Application.tsx +22 -6
  2. package/basic-react-declarative/assets/css/index.css +163 -18
  3. package/basic-react-declarative/package.json +8 -5
  4. package/basic-react-declarative/public/logo.svg +16 -0
  5. package/basic-react-declarative/tests/Application.spec.ts +13 -4
  6. package/basic-react-imperative/app/Application.tsx +22 -6
  7. package/basic-react-imperative/assets/css/index.css +163 -18
  8. package/basic-react-imperative/package.json +8 -5
  9. package/basic-react-imperative/public/logo.svg +16 -0
  10. package/basic-react-imperative/tests/Application.spec.ts +15 -4
  11. package/basic-service-declarative/app/Application.ts +18 -3
  12. package/basic-service-declarative/package.json +7 -4
  13. package/basic-service-declarative/tests/Application.spec.ts +3 -2
  14. package/basic-service-imperative/app/Application.ts +18 -3
  15. package/basic-service-imperative/package.json +7 -4
  16. package/basic-service-imperative/tests/Application.spec.ts +3 -2
  17. package/continuum-showcase/LICENSE +21 -0
  18. package/continuum-showcase/app/Application.ts +45 -0
  19. package/continuum-showcase/app/layouts/MainLayout.tsx +13 -8
  20. package/continuum-showcase/app/pages/BlogPostPage.tsx +6 -3
  21. package/continuum-showcase/app/pages/HomePage.tsx +21 -8
  22. package/continuum-showcase/assets/css/index.css +198 -0
  23. package/continuum-showcase/assets/images/logo.svg +16 -0
  24. package/continuum-showcase/package.json +28 -10
  25. package/continuum-showcase/tests/HomePage.spec.tsx +41 -0
  26. package/continuum-showcase/tsconfig.json +33 -0
  27. package/continuum-showcase/vitest.config.ts +31 -0
  28. package/full-react-declarative/app/layout/AppLayout/AppLayout.tsx +1 -1
  29. package/full-react-declarative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  30. package/full-react-declarative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  31. package/full-react-declarative/app/middleware/AuthMiddleware.ts +1 -1
  32. package/full-react-declarative/app/pages/home/HomePage.tsx +22 -21
  33. package/full-react-declarative/assets/css/index.css +167 -0
  34. package/full-react-declarative/package.json +13 -10
  35. package/full-react-declarative/public/logo.svg +16 -0
  36. package/full-react-declarative/tests/AppLayout.spec.ts +51 -0
  37. package/full-react-declarative/tests/HomePage.spec.ts +38 -0
  38. package/full-react-imperative/app/components/LayoutHeader/LayoutHeader.tsx +1 -1
  39. package/full-react-imperative/app/components/PostWidget/PostWidget.tsx +1 -1
  40. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.css +10 -0
  41. package/full-react-imperative/app/components/PullToRefresh/PullToRefresh.tsx +66 -0
  42. package/full-react-imperative/app/layout/ErrorLayout/ErrorLayout.tsx +1 -1
  43. package/full-react-imperative/app/layout/SecurityLayout/SecurityLayout.tsx +1 -1
  44. package/full-react-imperative/app/middleware/AuthMiddleware.ts +1 -1
  45. package/full-react-imperative/app/pages/home/HomePage.tsx +32 -16
  46. package/full-react-imperative/assets/css/index.css +167 -0
  47. package/full-react-imperative/package.json +14 -12
  48. package/full-react-imperative/public/logo.svg +16 -0
  49. package/full-react-imperative/tests/HomePage.spec.ts +45 -0
  50. package/full-react-imperative/tests/LayoutHeader.spec.ts +39 -0
  51. package/full-service-declarative/app/handlers/WelcomeEventHandler.ts +47 -0
  52. package/full-service-declarative/app/middleware/AuthMiddleware.ts +1 -1
  53. package/full-service-declarative/package.json +12 -9
  54. package/full-service-declarative/tests/PostService.spec.ts +56 -0
  55. package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +54 -0
  56. package/full-service-declarative/vitest.config.ts +1 -1
  57. package/full-service-imperative/app/handlers/WelcomeEventHandler.ts +51 -0
  58. package/full-service-imperative/app/middleware/AuthMiddleware.ts +1 -1
  59. package/full-service-imperative/package.json +12 -9
  60. package/full-service-imperative/tests/PostService.spec.ts +47 -0
  61. package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +31 -0
  62. package/full-service-imperative/vitest.config.ts +1 -1
  63. package/package.json +1 -1
  64. package/standard-react-declarative/app/pages/welcome/WelcomePage.tsx +37 -4
  65. package/standard-react-declarative/assets/css/index.css +163 -18
  66. package/standard-react-declarative/package.json +11 -8
  67. package/standard-react-declarative/public/logo.svg +16 -0
  68. package/standard-react-declarative/tests/WelcomePage.spec.ts +71 -0
  69. package/standard-react-imperative/app/pages/welcome/WelcomePage.tsx +37 -4
  70. package/standard-react-imperative/assets/css/index.css +163 -18
  71. package/standard-react-imperative/package.json +11 -8
  72. package/standard-react-imperative/public/logo.svg +16 -0
  73. package/standard-react-imperative/tests/WelcomePage.spec.ts +53 -0
  74. package/standard-service-declarative/app/handlers/WelcomeEventHandler.ts +2 -2
  75. package/standard-service-declarative/app/services/WelcomeService.ts +28 -2
  76. package/standard-service-declarative/package.json +9 -6
  77. package/standard-service-declarative/tests/Application.spec.ts +100 -0
  78. package/standard-service-declarative/vitest.config.ts +1 -1
  79. package/standard-service-imperative/app/handlers/welcomeEventHandler.ts +2 -4
  80. package/standard-service-imperative/app/services/welcomeService.ts +28 -2
  81. package/standard-service-imperative/package.json +9 -6
  82. package/standard-service-imperative/tests/Application.spec.ts +84 -0
  83. package/standard-service-imperative/vitest.config.ts +1 -1
  84. package/basic-react-declarative/public/logo.png +0 -0
  85. package/basic-react-declarative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  86. package/basic-react-imperative/public/logo.png +0 -0
  87. package/basic-react-imperative/tests/__snapshots__/Application.spec.ts.snap +0 -3
  88. package/full-react-declarative/assets/img/logo.png +0 -0
  89. package/full-react-declarative/public/logo.png +0 -0
  90. package/full-react-imperative/assets/img/logo.png +0 -0
  91. package/full-react-imperative/public/logo.png +0 -0
@@ -1,26 +1,171 @@
1
- body {
2
- margin: 0;
3
- padding: 0;
4
- color: #333;
5
- font-size: 14px;
6
- line-height: 1.5;
7
- background-color: #fff;
8
- font-family: 'Roboto', sans-serif;
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
- .h1 {
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
- font-weight: 500;
13
- font-size: 3rem;
14
- font-weight: bold;
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
- .container {
17
- width: 100vw;
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
- .text-center {
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.3",
24
- "@stone-js/browser-adapter": "^0.8.3",
25
- "@stone-js/pipeline": "^0.8.3",
26
- "@stone-js/http-core": "^0.8.3",
27
- "@stone-js/node-http-adapter": "^0.8.3",
28
- "@stone-js/router": "^0.8.3",
29
- "@stone-js/core": "^0.8.3"
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.3",
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): { message: string } {
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): { message: string } {
52
+ welcome (name: string): WelcomeResponse {
35
53
  this.logger.info(`Welcome ${name}`)
36
- return { message: `Hello ${name}!` }
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.3",
22
- "@stone-js/http-core": "^0.8.3",
23
- "@stone-js/node-http-adapter": "^0.8.3",
24
- "@stone-js/router": "^0.8.3",
25
- "@stone-js/core": "^0.8.3"
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.3",
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,4 +1,4 @@
1
- import { defineConfig } from 'vitest/config.js'
1
+ import { defineConfig } from 'vitest/config'
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
@@ -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 interface ResponseData {
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): { message: string } {
44
+ welcome (name: string): WelcomeResponse {
27
45
  logger.info(`Welcome ${name}`)
28
- return { message: `Hello ${name}!` }
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.3",
21
- "@stone-js/http-core": "^0.8.3",
22
- "@stone-js/node-http-adapter": "^0.8.3",
23
- "@stone-js/router": "^0.8.3",
24
- "@stone-js/core": "^0.8.3"
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.3",
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
+ })
@@ -1,4 +1,4 @@
1
- import { defineConfig } from 'vitest/config.js'
1
+ import { defineConfig } from 'vitest/config'
2
2
 
3
3
  export default defineConfig({
4
4
  test: {
@@ -1,3 +0,0 @@
1
- // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html
2
-
3
- exports[`Application > should render component 1`] = `"<section class="container"><h1 class="h1 text-center mt-64">Hello World!</h1></section>"`;