@stone-js/starters 0.8.8 → 0.8.10

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 (101) hide show
  1. package/README.md +2 -2
  2. package/basic-react-declarative/app/Application.tsx +0 -2
  3. package/basic-react-declarative/package.json +7 -7
  4. package/basic-react-declarative/public/favicon.svg +16 -0
  5. package/basic-react-declarative/tests/Application.spec.ts +25 -100
  6. package/basic-react-imperative/app/Application.tsx +2 -17
  7. package/basic-react-imperative/package.json +7 -7
  8. package/basic-react-imperative/public/favicon.svg +16 -0
  9. package/basic-react-imperative/tests/Application.spec.ts +25 -86
  10. package/basic-react-native-declarative/App.tsx +192 -0
  11. package/basic-react-native-declarative/LICENSE +21 -0
  12. package/basic-react-native-declarative/README.md +35 -0
  13. package/basic-react-native-declarative/adapter/NativeAdapter.ts +108 -0
  14. package/basic-react-native-declarative/adapter/NativeErrorHandler.ts +53 -0
  15. package/basic-react-native-declarative/adapter/NativeEventSource.ts +58 -0
  16. package/basic-react-native-declarative/adapter/RawResponseWrapper.ts +30 -0
  17. package/basic-react-native-declarative/adapter/declarations.ts +40 -0
  18. package/basic-react-native-declarative/adapter/middleware.ts +73 -0
  19. package/basic-react-native-declarative/adapter/nativeAdapterBlueprint.ts +51 -0
  20. package/basic-react-native-declarative/adapter/renderSink.ts +52 -0
  21. package/basic-react-native-declarative/app/Application.ts +17 -0
  22. package/basic-react-native-declarative/app/WelcomeController.ts +66 -0
  23. package/basic-react-native-declarative/app.json +25 -0
  24. package/basic-react-native-declarative/assets/android-icon-background.png +0 -0
  25. package/basic-react-native-declarative/assets/android-icon-foreground.png +0 -0
  26. package/basic-react-native-declarative/assets/android-icon-monochrome.png +0 -0
  27. package/basic-react-native-declarative/assets/favicon.png +0 -0
  28. package/basic-react-native-declarative/assets/icon.png +0 -0
  29. package/basic-react-native-declarative/assets/splash-icon.png +0 -0
  30. package/basic-react-native-declarative/babel.config.js +16 -0
  31. package/basic-react-native-declarative/index.ts +22 -0
  32. package/basic-react-native-declarative/metro.config.js +7 -0
  33. package/basic-react-native-declarative/package.json +41 -0
  34. package/basic-react-native-declarative/tests/Application.spec.ts +65 -0
  35. package/basic-react-native-declarative/tsconfig.json +16 -0
  36. package/basic-react-native-declarative/vitest.config.ts +27 -0
  37. package/basic-service-declarative/app/Application.ts +0 -2
  38. package/basic-service-declarative/package.json +6 -6
  39. package/basic-service-declarative/tests/Application.spec.ts +20 -53
  40. package/basic-service-imperative/app/Application.ts +2 -14
  41. package/basic-service-imperative/package.json +6 -6
  42. package/basic-service-imperative/tests/Application.spec.ts +19 -44
  43. package/continuum-showcase/package.json +11 -10
  44. package/continuum-showcase/public/favicon.svg +16 -0
  45. package/continuum-showcase/stone.config.mjs +2 -2
  46. package/continuum-showcase/tests/application.spec.ts +30 -0
  47. package/full-react-declarative/app/Application.ts +0 -4
  48. package/full-react-declarative/package.json +12 -12
  49. package/full-react-declarative/public/favicon.svg +16 -0
  50. package/full-react-declarative/stone.config.mjs +2 -2
  51. package/full-react-declarative/tests/application.spec.ts +30 -0
  52. package/full-react-imperative/app/Application.ts +1 -3
  53. package/full-react-imperative/package.json +12 -12
  54. package/full-react-imperative/public/favicon.svg +16 -0
  55. package/full-react-imperative/stone.config.mjs +2 -2
  56. package/full-react-imperative/tests/application.spec.ts +30 -0
  57. package/full-service-declarative/app/Application.ts +5 -2
  58. package/full-service-declarative/app/configurations/AppConfiguration.ts +1 -3
  59. package/full-service-declarative/package.json +13 -12
  60. package/full-service-declarative/stone.config.mjs +2 -2
  61. package/full-service-declarative/tests/application.spec.ts +30 -0
  62. package/full-service-imperative/app/Application.ts +8 -2
  63. package/full-service-imperative/app/configurations/AppConfiguration.ts +1 -3
  64. package/full-service-imperative/package.json +13 -12
  65. package/full-service-imperative/stone.config.mjs +2 -2
  66. package/full-service-imperative/tests/application.spec.ts +30 -0
  67. package/package.json +15 -1
  68. package/standard-react-declarative/package.json +11 -10
  69. package/standard-react-declarative/public/favicon.svg +16 -0
  70. package/standard-react-declarative/tests/application.spec.ts +30 -0
  71. package/standard-react-imperative/package.json +11 -10
  72. package/standard-react-imperative/public/favicon.svg +16 -0
  73. package/standard-react-imperative/tests/application.spec.ts +30 -0
  74. package/standard-service-declarative/package.json +9 -8
  75. package/standard-service-declarative/tests/Application.spec.ts +22 -94
  76. package/standard-service-imperative/package.json +9 -8
  77. package/standard-service-imperative/tests/Application.spec.ts +22 -78
  78. package/basic-react-declarative/vitest.config.ts +0 -24
  79. package/basic-react-imperative/vitest.config.ts +0 -24
  80. package/basic-service-declarative/vitest.config.ts +0 -24
  81. package/basic-service-imperative/vitest.config.ts +0 -24
  82. package/continuum-showcase/tests/HomePage.spec.tsx +0 -41
  83. package/continuum-showcase/vitest.config.ts +0 -31
  84. package/full-react-declarative/tests/AppLayout.spec.ts +0 -51
  85. package/full-react-declarative/tests/HomePage.spec.ts +0 -38
  86. package/full-react-declarative/vitest.config.ts +0 -24
  87. package/full-react-imperative/tests/HomePage.spec.ts +0 -45
  88. package/full-react-imperative/tests/LayoutHeader.spec.ts +0 -39
  89. package/full-react-imperative/vitest.config.ts +0 -24
  90. package/full-service-declarative/tests/PostService.spec.ts +0 -56
  91. package/full-service-declarative/tests/WelcomeEventHandler.spec.ts +0 -54
  92. package/full-service-declarative/vitest.config.ts +0 -24
  93. package/full-service-imperative/tests/PostService.spec.ts +0 -47
  94. package/full-service-imperative/tests/WelcomeEventHandler.spec.ts +0 -31
  95. package/full-service-imperative/vitest.config.ts +0 -24
  96. package/standard-react-declarative/tests/WelcomePage.spec.ts +0 -71
  97. package/standard-react-declarative/vitest.config.ts +0 -24
  98. package/standard-react-imperative/tests/WelcomePage.spec.ts +0 -53
  99. package/standard-react-imperative/vitest.config.ts +0 -24
  100. package/standard-service-declarative/vitest.config.ts +0 -24
  101. package/standard-service-imperative/vitest.config.ts +0 -24
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  [![License: MIT](https://img.shields.io/npm/l/@stone-js/starters)](https://opensource.org/licenses/MIT)
4
4
  [![Maintenance](https://img.shields.io/maintenance/yes/2026)](https://stonejs.dev)
5
- [![Used by Stone CLI](https://img.shields.io/badge/Used%20by-Stone%20CLI-blue)](https://github.com/stone-foundation/stone-js-cli)
5
+ [![Used by Stone CLI](https://img.shields.io/badge/Used%20by-Stone%20CLI-blue)](https://github.com/stone-foundation/stone-js-framework)
6
6
 
7
7
  This repository contains official **starter templates** for Stone.js applications.
8
8
 
@@ -98,4 +98,4 @@ Explore the full documentation: [https://stonejs.dev](https://stonejs.dev)
98
98
 
99
99
  ## Contributing
100
100
 
101
- See [Contributing Guide](https://github.com/stone-foundation/stone-js-starters/blob/main/CONTRIBUTING.md)
101
+ See [Contributing Guide](https://github.com/stone-foundation/stone-js-framework/blob/main/CONTRIBUTING.md)
@@ -1,6 +1,5 @@
1
1
  import { JSX } from 'react'
2
2
  import { Browser } from '@stone-js/browser-adapter'
3
- import { NodeConsole } from '@stone-js/node-cli-adapter'
4
3
  import { ILogger, LogLevel, Promiseable, StoneApp } from '@stone-js/core'
5
4
  import { ReactIncomingEvent, UseReact, HeadContext, IPage, PageHeadContext, PageRenderContext } from '@stone-js/use-react'
6
5
 
@@ -15,7 +14,6 @@ import { ReactIncomingEvent, UseReact, HeadContext, IPage, PageHeadContext, Page
15
14
  */
16
15
  @Browser()
17
16
  @UseReact()
18
- @NodeConsole()
19
17
  @StoneApp({ logger: { level: LogLevel.INFO } })
20
18
  export class Application implements IPage<ReactIncomingEvent> {
21
19
  /**
@@ -14,22 +14,22 @@
14
14
  "build": "stone build",
15
15
  "preview": "stone preview",
16
16
  "build:ssr": "stone build --ssr",
17
- "test": "vitest run",
18
- "test:cvg": "npm run test -- --coverage"
17
+ "test": "stone test",
18
+ "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.8",
24
- "@stone-js/use-react": "^0.8.8",
25
- "@stone-js/browser-adapter": "^0.8.8",
26
- "@stone-js/node-cli-adapter": "^0.8.8"
23
+ "@stone-js/core": "^0.8.10",
24
+ "@stone-js/use-react": "^0.8.10",
25
+ "@stone-js/browser-adapter": "^0.8.10"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
30
29
  "@babel/preset-env": "^7.26.9",
31
30
  "@babel/preset-typescript": "^7.27.0",
32
- "@stone-js/cli": "^0.8.8",
31
+ "@stone-js/cli": "^0.8.10",
32
+ "@stone-js/testing": "^0.8.10",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -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>
@@ -1,105 +1,30 @@
1
- import { ILogger } from '@stone-js/core'
2
- import { Application } from '../app/Application'
3
- import { renderToString } from 'react-dom/server'
4
- import { ReactIncomingEvent } from '@stone-js/use-react'
5
-
6
- // We must mock decorators to lighten the test environment
7
- vi.mock('@stone-js/core', async (importOriginal) => {
8
- const actual: any = await importOriginal()
9
- return {
10
- ...actual,
11
- StoneApp: vi.fn(() => vi.fn()),
12
- }
13
- })
14
-
15
- // We must mock decorators to lighten the test environment
16
- vi.mock('@stone-js/node-cli-adapter', async (importOriginal) => {
17
- const actual: any = await importOriginal()
18
- return {
19
- ...actual,
20
- NodeConsole: vi.fn(() => vi.fn()),
21
- }
22
- })
23
-
24
- // We must mock decorators to lighten the test environment
25
- vi.mock('@stone-js/browser-adapter', async (importOriginal) => {
26
- const actual: any = await importOriginal()
27
- return {
28
- ...actual,
29
- Browser: vi.fn(() => vi.fn()),
30
- }
31
- })
32
-
33
- // We must mock decorators to lighten the test environment
34
- vi.mock('@stone-js/use-react', async (importOriginal) => {
35
- const actual: any = await importOriginal()
36
- return {
37
- ...actual,
38
- UseReact: vi.fn(() => vi.fn()),
39
- }
40
- })
41
-
42
- describe('Application', () => {
43
- let app: Application
44
- let mockedLogger: ILogger
45
-
46
- beforeEach(() => {
47
- mockedLogger = {
48
- info: vi.fn(),
49
- } as unknown as ILogger
50
-
51
- app = new Application({ logger: mockedLogger })
52
- })
53
-
54
- it('should create an application instance', () => {
55
- // Assert
56
- expect(app).toBeInstanceOf(Application)
1
+ import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
2
+
3
+ /**
4
+ * The whole application, booted in memory and asked a real question.
5
+ *
6
+ * Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
7
+ * through the same kernel production runs. If this passes the application works; if it fails the
8
+ * application is broken. That is the only kind of test worth shipping in a starter — the previous one
9
+ * stubbed out the framework's own decorators, which meant it could pass while nothing worked.
10
+ */
11
+ describe('Pages', () => {
12
+ it('renders the home page, data included', async () => {
13
+ const app = await createTestApp()
14
+
15
+ const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
16
+
17
+ expect(response.statusCode).toBe(200)
18
+ // A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
19
+ // whatever you already use (happy-dom, jsdom, Testing Library): none is bundled here.
20
+ expect(response.html()).toContain('Ada')
57
21
  })
58
22
 
59
- it('get head values', async () => {
60
- // Arrange
61
- const event = { get: () => 'World' } as unknown as ReactIncomingEvent
23
+ it('renders without a name', async () => {
24
+ const app = await createTestApp()
62
25
 
63
- // Act
64
- const head = await app.head({ event })
26
+ const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
65
27
 
66
- // Assert
67
- expect(head).toHaveProperty('metas')
68
- expect(head.title).toContain('Welcome to Stone.js')
69
- expect(head.title).toContain('World')
70
- expect(head).toHaveProperty('description')
28
+ expect(response.statusCode).toBe(200)
71
29
  })
72
-
73
- it('should handle incoming events', () => {
74
- // Arrange
75
- const expectedMessage = 'Hello World!'
76
- const event = { get: () => 'World' } as unknown as ReactIncomingEvent
77
-
78
- // Act
79
- const response = app.handle(event) as { message: string }
80
-
81
- // Assert
82
- expect(response.message).toBe(expectedMessage)
83
- expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
84
- })
85
-
86
- it('should render the message', () => {
87
- // Arrange
88
- const message = 'Hello World!'
89
-
90
- // Act
91
- const response = renderToString(app.render({ data: { message } } as any))
92
-
93
- // Assert
94
- expect(response).toContain(message)
95
- })
96
-
97
- it('should render the Stone.js logo', () => {
98
- // Act
99
- const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
100
-
101
- // Assert
102
- expect(response).toContain('/logo.svg')
103
- expect(response).toContain('alt="Stone.js"')
104
- })
105
- })
30
+ })
@@ -1,7 +1,6 @@
1
1
  import { JSX } from 'react'
2
2
  import { browserAdapterBlueprint } from '@stone-js/browser-adapter'
3
- import { defineConfig, IBlueprint, ILogger, isNotEmpty, Promiseable } from '@stone-js/core'
4
- import { defineCommand, FactoryCommandHandler, NODE_CONSOLE_PLATFORM, nodeConsoleAdapterBlueprint } from '@stone-js/node-cli-adapter'
3
+ import { ILogger, Promiseable } from '@stone-js/core'
5
4
  import { defineStoneReactApp, HeadContext, IPage, PageHeadContext, PageRenderContext, ReactIncomingEvent } from '@stone-js/use-react'
6
5
 
7
6
  /**
@@ -73,23 +72,9 @@ export const FactoryHandler = ({ logger }: AppOptions): IPage<ReactIncomingEvent
73
72
  export const Application = defineStoneReactApp(
74
73
  FactoryHandler,
75
74
  { debug: true, isFactory: true },
76
- [browserAdapterBlueprint, nodeConsoleAdapterBlueprint]
75
+ [browserAdapterBlueprint]
77
76
  )
78
77
 
79
- /**
80
- * Application configuration.
81
- */
82
- export const AppConfig = defineConfig({
83
- afterConfigure (blueprint: IBlueprint) {
84
- if (
85
- isNotEmpty<FactoryCommandHandler>(FactoryHandler) &&
86
- blueprint.is('stone.adapter.platform', NODE_CONSOLE_PLATFORM)
87
- ) {
88
- blueprint.set(defineCommand(FactoryHandler, { name: '*', isFactory: true }))
89
- }
90
- }
91
- })
92
-
93
78
  /**
94
79
  * Application options.
95
80
  */
@@ -14,22 +14,22 @@
14
14
  "build": "stone build",
15
15
  "preview": "stone preview",
16
16
  "typings": "stone typings",
17
- "test": "vitest run",
18
- "test:cvg": "npm run test -- --coverage"
17
+ "test": "stone test",
18
+ "test:cvg": "stone test --coverage"
19
19
  },
20
20
  "dependencies": {
21
21
  "react": "^19.0.0",
22
22
  "react-dom": "^19.0.0",
23
- "@stone-js/core": "^0.8.8",
24
- "@stone-js/use-react": "^0.8.8",
25
- "@stone-js/browser-adapter": "^0.8.8",
26
- "@stone-js/node-cli-adapter": "^0.8.8"
23
+ "@stone-js/core": "^0.8.10",
24
+ "@stone-js/use-react": "^0.8.10",
25
+ "@stone-js/browser-adapter": "^0.8.10"
27
26
  },
28
27
  "devDependencies": {
29
28
  "@babel/plugin-proposal-decorators": "^7.25.9",
30
29
  "@babel/preset-env": "^7.26.9",
31
30
  "@babel/preset-typescript": "^7.27.0",
32
- "@stone-js/cli": "^0.8.8",
31
+ "@stone-js/cli": "^0.8.10",
32
+ "@stone-js/testing": "^0.8.10",
33
33
  "@types/node": "^24.0.7",
34
34
  "@types/react": "^19.0.7",
35
35
  "@types/react-dom": "^19.0.3",
@@ -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>
@@ -1,91 +1,30 @@
1
- import { renderToString } from 'react-dom/server'
2
- import { ILogger, IncomingEvent } from '@stone-js/core'
3
- import { FactoryHandler, AppConfig } from '../app/Application'
4
-
5
- vi.mock('@stone-js/core', async (mod) => {
6
- const actual: any = await mod()
7
- return {
8
- ...actual,
9
- isNotEmpty: vi.fn(() => true),
10
- defineConfig: (config: any) => config
11
- }
12
- })
13
-
14
- describe('Application', () => {
15
- let app: any
16
- let mockedLogger: ILogger
17
-
18
- beforeEach(() => {
19
- mockedLogger = {
20
- info: vi.fn(),
21
- } as unknown as ILogger
22
-
23
- app = FactoryHandler({ logger: mockedLogger })
24
- })
25
-
26
- it('should create an application instance', () => {
27
- // Assert
28
- expect(app).toBeInstanceOf(Object)
29
- })
30
-
31
- it('get head values', async () => {
32
- // Arrange
33
- const event = { get: () => 'World' } as unknown as IncomingEvent
34
-
35
- // Act
36
- const head = await app.head({ event })
37
-
38
- // Assert
39
- expect(head).toHaveProperty('metas')
40
- expect(head.title).toContain('Welcome to Stone.js')
41
- expect(head.title).toContain('World')
42
- expect(head).toHaveProperty('description')
1
+ import { createTestApp, makeIncomingHttpEvent } from '@stone-js/testing'
2
+
3
+ /**
4
+ * The whole application, booted in memory and asked a real question.
5
+ *
6
+ * Nothing is mocked and nothing is listed: `createTestApp()` discovers `app/**`, and the event goes
7
+ * through the same kernel production runs. If this passes the application works; if it fails the
8
+ * application is broken. That is the only kind of test worth shipping in a starter — the previous one
9
+ * stubbed out the framework's own decorators, which meant it could pass while nothing worked.
10
+ */
11
+ describe('Pages', () => {
12
+ it('renders the home page, data included', async () => {
13
+ const app = await createTestApp()
14
+
15
+ const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/?name=Ada' }))
16
+
17
+ expect(response.statusCode).toBe(200)
18
+ // A page is a handler, so a rendered page is a response whose body is HTML. Query that HTML with
19
+ // whatever you already use (happy-dom, jsdom, Testing Library): none is bundled here.
20
+ expect(response.html()).toContain('Ada')
43
21
  })
44
22
 
45
- it('should handle incoming events', () => {
46
- // Arrange
47
- const expectedMessage = 'Hello World!'
48
- const event = { get: () => 'World' } as unknown as IncomingEvent
23
+ it('renders without a name', async () => {
24
+ const app = await createTestApp()
49
25
 
50
- // Act
51
- const response = app.handle(event) as { message: string }
26
+ const response = await app.send(makeIncomingHttpEvent({ method: 'GET', url: '/' }))
52
27
 
53
- // Assert
54
- expect(response.message).toBe(expectedMessage)
55
- expect(mockedLogger.info).toHaveBeenCalledWith(expectedMessage)
28
+ expect(response.statusCode).toBe(200)
56
29
  })
57
-
58
- it('should render the message', () => {
59
- // Arrange
60
- const message = 'Hello World!'
61
-
62
- // Act
63
- const response = renderToString(app.render({ data: { message } } as any))
64
-
65
- // Assert
66
- expect(response).toContain(message)
67
- })
68
-
69
- it('should render the Stone.js logo', () => {
70
- // Act
71
- const response = renderToString(app.render({ data: { message: 'Hello World!' } } as any))
72
-
73
- // Assert
74
- expect(response).toContain('/logo.svg')
75
- expect(response).toContain('alt="Stone.js"')
76
- expect(response).toContain('Welcome to')
77
- expect(response).toContain('Stone.js')
78
- })
79
-
80
- it('should get config', () => {
81
- // Arrange
82
- const config: any = AppConfig
83
- const blueprint: any = { is: () => true, set: vi.fn() }
84
-
85
- // Act
86
- config.afterConfigure(blueprint)
87
-
88
- // Assert
89
- expect(blueprint.set).toHaveBeenCalled()
90
- })
91
- })
30
+ })
@@ -0,0 +1,192 @@
1
+ import { stoneApp } from '@stone-js/core'
2
+ import { StatusBar } from 'expo-status-bar'
3
+ import { useEffect, useState } from 'react'
4
+ import { Application } from './app/Application'
5
+ import { WelcomeData, WelcomeController } from './app/WelcomeController'
6
+ import { nativeEventSource } from './adapter/NativeEventSource'
7
+ import { onNativeError, onNativeRender } from './adapter/renderSink'
8
+ import { Pressable, ScrollView, StyleSheet, Text, View } from 'react-native'
9
+
10
+ /**
11
+ * One platform self-check, shown as a green or red row on screen.
12
+ */
13
+ interface PlatformCheck {
14
+ label: string
15
+ passed: boolean
16
+ detail: string
17
+ }
18
+
19
+ /**
20
+ * A minimal stage-3 decorator writing into `context.metadata`: if Babel emits
21
+ * the 2023-11 semantics correctly under Metro/Hermes, the value is readable
22
+ * back through `Symbol.metadata` (or its `Symbol.for` fallback, the one
23
+ * Stone.js core uses).
24
+ */
25
+ const probe: any = (_value: unknown, context: { metadata: Record<PropertyKey, unknown> }): undefined => {
26
+ context.metadata.stonePoc = 'ok'
27
+ }
28
+
29
+ @probe
30
+ class MetadataProbe {}
31
+
32
+ /**
33
+ * Run the runtime platform checks Stone.js depends on.
34
+ *
35
+ * @returns The list of checks with their outcome.
36
+ */
37
+ function runPlatformChecks (): PlatformCheck[] {
38
+ const checks: PlatformCheck[] = []
39
+
40
+ const metadataSymbol: symbol = (Symbol as any).metadata ?? Symbol.for('Symbol.metadata')
41
+ const probeMetadata = (MetadataProbe as any)[metadataSymbol]
42
+ checks.push({
43
+ label: 'Decorators 2023-11 + Symbol.metadata',
44
+ passed: probeMetadata?.stonePoc === 'ok',
45
+ detail: probeMetadata?.stonePoc === 'ok' ? 'context.metadata readable' : 'metadata missing'
46
+ })
47
+
48
+ let urlPassed = false
49
+ let urlDetail = 'URL API incomplete'
50
+ try {
51
+ const url = new URL('stone://app/hello/Noowow?from=poc')
52
+ urlPassed = url.pathname === '/hello/Noowow' && url.searchParams.get('from') === 'poc'
53
+ urlDetail = urlPassed ? 'pathname + searchParams OK' : `pathname=${String(url.pathname)}`
54
+ } catch (error: any) {
55
+ urlDetail = String(error?.message ?? error)
56
+ }
57
+ checks.push({ label: 'WHATWG URL (polyfill)', passed: urlPassed, detail: urlDetail })
58
+
59
+ const encoderPassed = typeof globalThis.TextEncoder !== 'undefined' &&
60
+ new TextEncoder().encode('stone').byteLength === 5
61
+ checks.push({
62
+ label: 'TextEncoder',
63
+ passed: encoderPassed,
64
+ detail: encoderPassed ? 'available' : 'missing'
65
+ })
66
+
67
+ return checks
68
+ }
69
+
70
+ /**
71
+ * The proof-of-concept screen: boots the Stone.js application through the
72
+ * native adapter, shows the platform checks and lets you navigate between
73
+ * routes of the same domain you would deploy on any other platform.
74
+ */
75
+ export default function App (): React.JSX.Element {
76
+ const [checks, setChecks] = useState<PlatformCheck[]>([])
77
+ const [payload, setPayload] = useState<WelcomeData>()
78
+ const [error, setError] = useState<string>()
79
+ const [booted, setBooted] = useState(false)
80
+
81
+ useEffect(() => {
82
+ setChecks(runPlatformChecks())
83
+
84
+ onNativeRender((response) => {
85
+ setError(undefined)
86
+ setBooted(true)
87
+ setPayload(response.content as WelcomeData)
88
+ })
89
+
90
+ onNativeError((err) => {
91
+ setError(String(err?.message ?? err))
92
+ })
93
+
94
+ stoneApp({ modules: [Application, WelcomeController] })
95
+ .run()
96
+ .catch((err: Error) => setError(String(err?.message ?? err)))
97
+ }, [])
98
+
99
+ const allPassed = checks.every((check) => check.passed) && booted && error === undefined
100
+
101
+ return (
102
+ <View style={styles.container}>
103
+ <StatusBar style='light' />
104
+ <ScrollView contentContainerStyle={styles.content}>
105
+ <Text style={styles.title}>Stone.js × React Native</Text>
106
+ <Text style={styles.subtitle}>Continuum proof of concept</Text>
107
+
108
+ <View style={[styles.banner, allPassed ? styles.bannerOk : styles.bannerKo]}>
109
+ <Text style={styles.bannerText}>
110
+ {allPassed ? 'ALL CHECKS GREEN' : (booted ? 'CHECKS FAILING' : 'BOOTING…')}
111
+ </Text>
112
+ </View>
113
+
114
+ {checks.map((check) => (
115
+ <View key={check.label} style={styles.checkRow}>
116
+ <Text style={check.passed ? styles.checkOk : styles.checkKo}>
117
+ {check.passed ? '✓' : '✗'} {check.label}
118
+ </Text>
119
+ <Text style={styles.checkDetail}>{check.detail}</Text>
120
+ </View>
121
+ ))}
122
+
123
+ <View style={styles.checkRow}>
124
+ <Text style={booted ? styles.checkOk : styles.checkKo}>
125
+ {booted ? '✓' : '…'} Kernel boot + router dispatch
126
+ </Text>
127
+ <Text style={styles.checkDetail}>
128
+ {booted ? `matched ${String(payload?.route)}` : 'waiting for the first render'}
129
+ </Text>
130
+ </View>
131
+
132
+ {error !== undefined && (
133
+ <View style={styles.errorBox}>
134
+ <Text style={styles.errorText}>{error}</Text>
135
+ </View>
136
+ )}
137
+
138
+ {payload !== undefined && (
139
+ <View style={styles.payloadBox}>
140
+ <Text style={styles.payloadMessage}>{payload.message}</Text>
141
+ <Text style={styles.payloadMeta}>
142
+ {payload.framework.name} · {payload.framework.tagline}
143
+ </Text>
144
+ </View>
145
+ )}
146
+
147
+ <Text style={styles.sectionTitle}>Navigate the same domain</Text>
148
+ <View style={styles.buttons}>
149
+ <NavButton label='Home' url='stone://app/' />
150
+ <NavButton label='Hello Noowow' url='stone://app/hello/Noowow' />
151
+ <NavButton label='Hello Mr. Stone' url='stone://app/hello/Mr.%20Stone' />
152
+ <NavButton label='Unknown route' url='stone://app/nowhere' />
153
+ </View>
154
+ </ScrollView>
155
+ </View>
156
+ )
157
+ }
158
+
159
+ /**
160
+ * A navigation button emitting an intent into the native event source.
161
+ */
162
+ function NavButton ({ label, url }: { label: string, url: string }): React.JSX.Element {
163
+ return (
164
+ <Pressable style={styles.button} onPress={() => nativeEventSource.navigate(url)}>
165
+ <Text style={styles.buttonText}>{label}</Text>
166
+ </Pressable>
167
+ )
168
+ }
169
+
170
+ const styles = StyleSheet.create({
171
+ container: { flex: 1, backgroundColor: '#141210' },
172
+ content: { padding: 24, paddingTop: 72 },
173
+ title: { color: '#f5f1ea', fontSize: 26, fontWeight: '700' },
174
+ subtitle: { color: '#b8b0a4', fontSize: 14, marginTop: 4, marginBottom: 20 },
175
+ banner: { borderRadius: 8, paddingVertical: 10, alignItems: 'center', marginBottom: 20 },
176
+ bannerOk: { backgroundColor: '#1f5130' },
177
+ bannerKo: { backgroundColor: '#5e2b1e' },
178
+ bannerText: { color: '#f5f1ea', fontWeight: '700', letterSpacing: 1 },
179
+ checkRow: { marginBottom: 12 },
180
+ checkOk: { color: '#7fd39a', fontSize: 15, fontWeight: '600' },
181
+ checkKo: { color: '#e88d70', fontSize: 15, fontWeight: '600' },
182
+ checkDetail: { color: '#8d8578', fontSize: 12, marginLeft: 18, marginTop: 2 },
183
+ errorBox: { backgroundColor: '#3a1f18', borderRadius: 8, padding: 12, marginTop: 8 },
184
+ errorText: { color: '#e88d70', fontSize: 13 },
185
+ payloadBox: { backgroundColor: '#221e1a', borderRadius: 8, padding: 16, marginTop: 16 },
186
+ payloadMessage: { color: '#f5f1ea', fontSize: 16, fontWeight: '600' },
187
+ payloadMeta: { color: '#b8b0a4', fontSize: 12, marginTop: 6 },
188
+ sectionTitle: { color: '#f5f1ea', fontSize: 16, fontWeight: '700', marginTop: 28, marginBottom: 12 },
189
+ buttons: { gap: 10 },
190
+ button: { backgroundColor: '#2e2823', borderRadius: 8, paddingVertical: 12, alignItems: 'center' },
191
+ buttonText: { color: '#f5f1ea', fontSize: 15, fontWeight: '600' }
192
+ })
@@ -0,0 +1,21 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2015-present 650 Industries, Inc. (aka Expo)
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,35 @@
1
+ # Stone.js · Basic React Native starter (declarative API)
2
+
3
+ The same Stone.js domain you deploy on Node, serverless, edge or the browser, running as a **native mobile application** with React Native and Expo.
4
+
5
+ ## What this starter demonstrates
6
+
7
+ - A Stone.js domain (`app/`) written with the declarative API: `@StoneApp`, `@Routing`, `@Controller`, `@Match`. The domain never knows it runs inside a native application.
8
+ - The canonical activation paths: modules are enabled by their decorator, and the local adapter blueprint is activated through `@StoneApp(options, [blueprints])`.
9
+ - Stage-3 decorators (2023-11) with `Symbol.metadata`, transformed by Babel under Metro (see `babel.config.js`: the semantics are set through `babel-preset-expo`'s `decorators` option).
10
+ - The universal Stone router matching navigation intents (`stone://app/hello/:name`) exactly like it matches URLs in a browser SPA or paths behind an HTTP adapter.
11
+ - A minimal native adapter (`adapter/`): it captures navigation intents, normalizes them into `IncomingBrowserEvent` and executes the render effect. It prefigures `@stone-js/react-native-adapter` and will be replaced by it once published.
12
+ - The platform polyfills React Native needs (`index.ts`): the WHATWG `URL` API and, on older Hermes versions, `TextEncoder`.
13
+
14
+ The start screen is a live self-check: decorators, URL API, TextEncoder, kernel boot and router dispatch are verified at runtime and shown as green or red rows.
15
+
16
+ ## Run it
17
+
18
+ ```bash
19
+ npm install
20
+ npm run ios # opens the iOS simulator directly
21
+ npm start # or the interactive way: then press i (iOS) or a (Android)
22
+ ```
23
+
24
+ ## Test it
25
+
26
+ The whole chain (domain, router, adapter) is pure JavaScript, so the exact modules the application boots are also tested under Node:
27
+
28
+ ```bash
29
+ npm test
30
+ npm run typecheck
31
+ ```
32
+
33
+ ## What is shared with your other platforms
34
+
35
+ Domain, routing, services and data loading. Not the UI components: native screens are React Native components, browser pages are DOM components.