@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
@@ -0,0 +1,108 @@
1
+ import { RawResponseWrapper } from './RawResponseWrapper'
2
+ import { NativeAdapterContext, NativeResponse, RawNativeResponseOptions } from './declarations'
3
+ import { NativeEventSource, NativeNavigationEvent, nativeEventSource } from './NativeEventSource'
4
+ import { Adapter, AdapterEventBuilder, AdapterEventHandlerType, IBlueprint } from '@stone-js/core'
5
+ import { IncomingBrowserEvent, IncomingBrowserEventOptions, OutgoingBrowserResponse } from '@stone-js/browser-core'
6
+
7
+ /**
8
+ * Native proof-of-concept adapter for Stone.js.
9
+ *
10
+ * Captures navigation intents from the in-memory event source, normalizes them
11
+ * into `IncomingBrowserEvent` instances, sends them through the kernel and
12
+ * executes the deferred render effect. It is the exact native counterpart of
13
+ * `BrowserAdapter`, with the event source replacing `window`.
14
+ *
15
+ * Temporary by design: it prefigures `@stone-js/react-native-adapter`, which
16
+ * will capture real deep links (`Linking`) and app-state changes on top of the
17
+ * same skeleton.
18
+ */
19
+ export class NativeAdapter extends Adapter<
20
+ NativeNavigationEvent,
21
+ NativeResponse,
22
+ NativeEventSource,
23
+ IncomingBrowserEvent,
24
+ IncomingBrowserEventOptions,
25
+ OutgoingBrowserResponse,
26
+ NativeAdapterContext
27
+ > {
28
+ private unsubscribe?: () => void
29
+
30
+ /**
31
+ * Creates an instance of the `NativeAdapter`.
32
+ *
33
+ * @param blueprint - The application blueprint.
34
+ * @returns A new instance of `NativeAdapter`.
35
+ */
36
+ static create (blueprint: IBlueprint): NativeAdapter {
37
+ return new this(blueprint)
38
+ }
39
+
40
+ /**
41
+ * Executes the adapter: subscribes to navigation intents and dispatches the
42
+ * initial event, exactly like the browser adapter dispatches its synthetic
43
+ * first navigation on startup.
44
+ *
45
+ * Idempotent: running again tears down the previous subscription first.
46
+ */
47
+ public async run<ExecutionResultType = undefined>(): Promise<ExecutionResultType> {
48
+ await this.executeHooks('onStart')
49
+
50
+ const eventHandler = this.resolveEventHandler()
51
+
52
+ await this.executeEventHandlerHooks('onInit', eventHandler)
53
+
54
+ this.unsubscribe?.()
55
+ this.unsubscribe = nativeEventSource.subscribe((rawEvent) => {
56
+ void this.eventListener(eventHandler, rawEvent)
57
+ })
58
+
59
+ await this.eventListener(eventHandler, { url: nativeEventSource.initialUrl })
60
+
61
+ return undefined as ExecutionResultType
62
+ }
63
+
64
+ /**
65
+ * Tear down the adapter: remove the navigation subscription and run the
66
+ * `onStop` hooks. Safe to call multiple times.
67
+ */
68
+ public async stop (): Promise<void> {
69
+ if (this.unsubscribe === undefined) { return }
70
+ this.unsubscribe()
71
+ this.unsubscribe = undefined
72
+ await this.executeHooks('onStop')
73
+ }
74
+
75
+ /**
76
+ * Processes one navigation intent through the kernel.
77
+ *
78
+ * @param eventHandler - The resolved kernel event handler.
79
+ * @param rawEvent - The raw navigation event.
80
+ * @returns The platform response.
81
+ */
82
+ protected async eventListener (
83
+ eventHandler: AdapterEventHandlerType<IncomingBrowserEvent, OutgoingBrowserResponse>,
84
+ rawEvent: NativeNavigationEvent
85
+ ): Promise<NativeResponse> {
86
+ const incomingEventBuilder = AdapterEventBuilder.create<IncomingBrowserEventOptions, IncomingBrowserEvent>({
87
+ resolver: (options) => IncomingBrowserEvent.create(options)
88
+ })
89
+
90
+ const rawResponseBuilder = AdapterEventBuilder.create<RawNativeResponseOptions, RawResponseWrapper>({
91
+ resolver: (options) => RawResponseWrapper.create(options)
92
+ })
93
+
94
+ const context: NativeAdapterContext = {
95
+ rawEvent,
96
+ executionContext: nativeEventSource,
97
+ rawResponseBuilder,
98
+ incomingEventBuilder
99
+ }
100
+
101
+ try {
102
+ return await this.sendEventThroughDestination(context, eventHandler)
103
+ } catch (error: any) {
104
+ const builder = await this.handleError(error, context)
105
+ return await this.buildRawResponse({ ...context, rawResponseBuilder: builder })
106
+ }
107
+ }
108
+ }
@@ -0,0 +1,53 @@
1
+ import { emitNativeError } from './renderSink'
2
+ import { NativeResponse } from './declarations'
3
+ import { NativeEventSource, NativeNavigationEvent } from './NativeEventSource'
4
+ import {
5
+ ILogger,
6
+ IBlueprint,
7
+ LoggerResolver,
8
+ AdapterErrorContext,
9
+ IAdapterErrorHandler,
10
+ defaultLoggerResolver,
11
+ AdapterEventBuilderType
12
+ } from '@stone-js/core'
13
+
14
+ /**
15
+ * NativeErrorHandler options.
16
+ */
17
+ export interface NativeErrorHandlerOptions {
18
+ blueprint: IBlueprint
19
+ }
20
+
21
+ /**
22
+ * Adapter-level error handler: logs the error and surfaces it on screen
23
+ * through the render sink, so a broken chain is visible instead of silent.
24
+ */
25
+ export class NativeErrorHandler implements IAdapterErrorHandler<NativeNavigationEvent, NativeResponse, NativeEventSource> {
26
+ private readonly logger: ILogger
27
+
28
+ /**
29
+ * Create a NativeErrorHandler.
30
+ *
31
+ * @param options - NativeErrorHandler options.
32
+ */
33
+ constructor ({ blueprint }: NativeErrorHandlerOptions) {
34
+ this.logger = blueprint.get<LoggerResolver>('stone.logger.resolver', defaultLoggerResolver)(blueprint)
35
+ }
36
+
37
+ /**
38
+ * Handle an error.
39
+ *
40
+ * @param error - The error to handle.
41
+ * @param context - The context of the adapter.
42
+ * @returns The raw response builder.
43
+ */
44
+ public handle (
45
+ error: Error,
46
+ context: AdapterErrorContext<NativeNavigationEvent, NativeResponse, NativeEventSource>
47
+ ): AdapterEventBuilderType<NativeResponse> {
48
+ this.logger.error(error.message, { error })
49
+ emitNativeError(error)
50
+
51
+ return context.rawResponseBuilder
52
+ }
53
+ }
@@ -0,0 +1,58 @@
1
+ /**
2
+ * The native cause captured by the adapter: an intent to show a URL.
3
+ *
4
+ * On a real device this is what a deep link, a push-notification tap or an
5
+ * in-app navigation collapses to. The proof-of-concept models all of them as
6
+ * one tiny in-memory event source; the future `@stone-js/react-native-adapter`
7
+ * will plug `Linking` and `AppState` into the exact same shape.
8
+ */
9
+ export interface NativeNavigationEvent {
10
+ /** The URL to resolve, e.g. `stone://app/hello/Noowow`. */
11
+ url: string
12
+ /** Optional navigation metadata, mirroring the browser's `history.state`. */
13
+ metadata?: unknown
14
+ }
15
+
16
+ /**
17
+ * A navigation listener registered by the adapter.
18
+ */
19
+ export type NativeNavigationListener = (event: NativeNavigationEvent) => void
20
+
21
+ /**
22
+ * The in-memory navigation event source: the native counterpart of `window`
23
+ * for the browser adapter. It is pure JavaScript, so the same code runs under
24
+ * React Native, Node (tests) and anywhere else.
25
+ */
26
+ export class NativeEventSource {
27
+ /** The synthetic first event, dispatched when the adapter starts. */
28
+ public readonly initialUrl = 'stone://app/'
29
+
30
+ private readonly listeners = new Set<NativeNavigationListener>()
31
+
32
+ /**
33
+ * Register a navigation listener.
34
+ *
35
+ * @param listener - The listener to register.
36
+ * @returns A teardown function removing the listener.
37
+ */
38
+ subscribe (listener: NativeNavigationListener): () => void {
39
+ this.listeners.add(listener)
40
+ return () => { this.listeners.delete(listener) }
41
+ }
42
+
43
+ /**
44
+ * Emit a navigation intent, exactly like the router does in the browser
45
+ * through the `@stonejs/router.navigate` custom event.
46
+ *
47
+ * @param url - The URL to navigate to.
48
+ * @param metadata - Optional navigation metadata.
49
+ */
50
+ navigate (url: string, metadata?: unknown): void {
51
+ this.listeners.forEach((listener) => listener({ url, metadata }))
52
+ }
53
+ }
54
+
55
+ /**
56
+ * The application-wide event source instance.
57
+ */
58
+ export const nativeEventSource = new NativeEventSource()
@@ -0,0 +1,30 @@
1
+ import { IRawResponseWrapper } from '@stone-js/core'
2
+ import { NativeResponse, RawNativeResponseOptions } from './declarations'
3
+
4
+ /**
5
+ * Wraps the raw native response: the platform effect is the deferred `render`
6
+ * closure contributed by the response middleware, executed once the kernel has
7
+ * resolved the outgoing response.
8
+ */
9
+ export class RawResponseWrapper implements IRawResponseWrapper<NativeResponse> {
10
+ /**
11
+ * Factory method to create an instance of `RawResponseWrapper`.
12
+ *
13
+ * @param options - The raw response options.
14
+ * @returns A new instance of `RawResponseWrapper`.
15
+ */
16
+ static create (options: RawNativeResponseOptions): RawResponseWrapper {
17
+ return new this(options)
18
+ }
19
+
20
+ private constructor (private readonly options: RawNativeResponseOptions) {}
21
+
22
+ /**
23
+ * Execute the deferred render effect and return its result.
24
+ *
25
+ * @returns The platform response.
26
+ */
27
+ async respond (): Promise<NativeResponse> {
28
+ return await this.options?.render?.()
29
+ }
30
+ }
@@ -0,0 +1,40 @@
1
+ import { RawResponseWrapper } from './RawResponseWrapper'
2
+ import { NativeEventSource, NativeNavigationEvent } from './NativeEventSource'
3
+ import { AdapterContext, IAdapterEventBuilder, RawResponseOptions } from '@stone-js/core'
4
+ import { IncomingBrowserEvent, IncomingBrowserEventOptions, OutgoingBrowserResponse } from '@stone-js/browser-core'
5
+
6
+ /**
7
+ * The platform identifier exposed on every incoming event's source.
8
+ */
9
+ export const NATIVE_PLATFORM = 'react-native'
10
+
11
+ /**
12
+ * The raw platform response. The native platform has no transport response:
13
+ * the effect is a render, so the raw response is whatever the renderer returns.
14
+ */
15
+ export type NativeResponse = unknown
16
+
17
+ /**
18
+ * Raw response options carrying the deferred `render` effect, mirroring the
19
+ * browser adapter's contract.
20
+ */
21
+ export interface RawNativeResponseOptions extends RawResponseOptions {
22
+ render?: () => NativeResponse | Promise<NativeResponse>
23
+ }
24
+
25
+ /**
26
+ * The adapter context for the native proof-of-concept adapter.
27
+ */
28
+ export type NativeAdapterContext = AdapterContext<
29
+ NativeNavigationEvent,
30
+ NativeResponse,
31
+ NativeEventSource,
32
+ IncomingBrowserEvent,
33
+ IncomingBrowserEventOptions,
34
+ OutgoingBrowserResponse
35
+ >
36
+
37
+ /**
38
+ * The response builder used by the native adapter middleware.
39
+ */
40
+ export type NativeAdapterResponseBuilder = IAdapterEventBuilder<RawNativeResponseOptions, RawResponseWrapper>
@@ -0,0 +1,73 @@
1
+ import { emitNativeRender } from './renderSink'
2
+ import { NextMiddleware } from '@stone-js/core'
3
+ import { CookieCollection } from '@stone-js/browser-core'
4
+ import { NATIVE_PLATFORM, NativeAdapterContext, NativeAdapterResponseBuilder } from './declarations'
5
+
6
+ /**
7
+ * Transforms the raw navigation event into the intention: an
8
+ * `IncomingBrowserEvent`, reused as-is from `@stone-js/browser-core`.
9
+ *
10
+ * This is the native mirror of the browser adapter's IncomingEventMiddleware:
11
+ * `location` comes from the event's URL instead of `window.location`, and the
12
+ * cookie collection is purely in memory (no `document`).
13
+ */
14
+ export class IncomingEventMiddleware {
15
+ /**
16
+ * Handle the incoming raw event.
17
+ *
18
+ * @param context - The adapter context.
19
+ * @param next - The next middleware.
20
+ * @returns The response builder.
21
+ */
22
+ async handle (
23
+ context: NativeAdapterContext,
24
+ next: NextMiddleware<NativeAdapterContext, NativeAdapterResponseBuilder>
25
+ ): Promise<NativeAdapterResponseBuilder> {
26
+ const url = new URL(context.rawEvent.url)
27
+
28
+ context
29
+ .incomingEventBuilder
30
+ .add('url', url)
31
+ .add('queryString', url.search)
32
+ .add('protocol', url.protocol.replace(':', ''))
33
+ .add('metadata', context.rawEvent.metadata ?? {})
34
+ .add('cookies', CookieCollection.create(undefined, {}))
35
+ .add('source', {
36
+ rawEvent: context.rawEvent,
37
+ platform: NATIVE_PLATFORM,
38
+ rawContext: context.executionContext
39
+ })
40
+
41
+ return await next(context)
42
+ }
43
+ }
44
+
45
+ /**
46
+ * Contributes the deferred render effect: once the kernel has produced the
47
+ * outgoing response, hand it to the registered render target.
48
+ *
49
+ * This is the native mirror of the role `BrowserResponseMiddleware` plays for
50
+ * the browser platform in `@stone-js/use-react`.
51
+ */
52
+ export class ResponseMiddleware {
53
+ /**
54
+ * Handle the outgoing response.
55
+ *
56
+ * @param context - The adapter context.
57
+ * @param next - The next middleware.
58
+ * @returns The response builder.
59
+ */
60
+ async handle (
61
+ context: NativeAdapterContext,
62
+ next: NextMiddleware<NativeAdapterContext, NativeAdapterResponseBuilder>
63
+ ): Promise<NativeAdapterResponseBuilder> {
64
+ context.rawResponseBuilder.add('render', () => {
65
+ if (context.outgoingResponse !== undefined) {
66
+ emitNativeRender(context.outgoingResponse)
67
+ }
68
+ return context.outgoingResponse
69
+ })
70
+
71
+ return await next(context)
72
+ }
73
+ }
@@ -0,0 +1,51 @@
1
+ import { NATIVE_PLATFORM } from './declarations'
2
+ import { NativeAdapter } from './NativeAdapter'
3
+ import { NativeErrorHandler } from './NativeErrorHandler'
4
+ import { IncomingEventMiddleware, ResponseMiddleware } from './middleware'
5
+ import { AdapterConfig, AppConfig, defaultKernelResolver, IBlueprint, StoneBlueprint } from '@stone-js/core'
6
+ import { OutgoingBrowserResponse, OutgoingBrowserResponseOptions } from '@stone-js/browser-core'
7
+
8
+ /**
9
+ * Adapter resolver for the native proof-of-concept adapter.
10
+ *
11
+ * @param blueprint - The `IBlueprint` providing configuration and dependencies.
12
+ * @returns A `NativeAdapter` instance.
13
+ */
14
+ export const nativeAdapterResolver = (blueprint: IBlueprint): NativeAdapter => {
15
+ return NativeAdapter.create(blueprint)
16
+ }
17
+
18
+ /**
19
+ * Blueprint for the native proof-of-concept adapter.
20
+ *
21
+ * Activated the canonical way: handed to `@StoneApp(options, [blueprints])`
22
+ * (declarative) or `defineStoneApp(handler, options, [blueprints])`
23
+ * (imperative). Being the only adapter, it is selected automatically.
24
+ */
25
+ export const nativeAdapterBlueprint: StoneBlueprint = {
26
+ stone: {
27
+ kernel: {
28
+ responseResolver: (options: OutgoingBrowserResponseOptions) => {
29
+ return OutgoingBrowserResponse.create({ ...options, statusCode: options.statusCode ?? 200 })
30
+ }
31
+ },
32
+ adapters: [
33
+ {
34
+ current: false,
35
+ default: true,
36
+ variant: 'native',
37
+ platform: NATIVE_PLATFORM,
38
+ middleware: [
39
+ { module: IncomingEventMiddleware, isClass: true, priority: 0 },
40
+ { module: ResponseMiddleware, isClass: true, priority: 10 }
41
+ ],
42
+ resolver: nativeAdapterResolver,
43
+ eventHandlerResolver: defaultKernelResolver,
44
+ events: [],
45
+ errorHandlers: {
46
+ default: { module: NativeErrorHandler, isClass: true }
47
+ }
48
+ } as unknown as AdapterConfig
49
+ ]
50
+ } as Partial<AppConfig> as AppConfig
51
+ }
@@ -0,0 +1,52 @@
1
+ import { OutgoingBrowserResponse } from '@stone-js/browser-core'
2
+
3
+ /**
4
+ * A render listener: receives the kernel's outgoing response for display.
5
+ */
6
+ export type NativeRenderListener = (response: OutgoingBrowserResponse) => void
7
+
8
+ /**
9
+ * An error listener: receives adapter-level errors for display.
10
+ */
11
+ export type NativeErrorListener = (error: Error) => void
12
+
13
+ let renderListener: NativeRenderListener | undefined
14
+ let errorListener: NativeErrorListener | undefined
15
+
16
+ /**
17
+ * Register the render effect target. In this proof of concept the target is
18
+ * the React root component's state; the future `@stone-js/use-react-native`
19
+ * response middleware will push into the native navigation stack instead.
20
+ *
21
+ * @param listener - The render listener.
22
+ */
23
+ export function onNativeRender (listener: NativeRenderListener): void {
24
+ renderListener = listener
25
+ }
26
+
27
+ /**
28
+ * Register the error display target.
29
+ *
30
+ * @param listener - The error listener.
31
+ */
32
+ export function onNativeError (listener: NativeErrorListener): void {
33
+ errorListener = listener
34
+ }
35
+
36
+ /**
37
+ * Push an outgoing response to the registered render target.
38
+ *
39
+ * @param response - The kernel's outgoing response.
40
+ */
41
+ export function emitNativeRender (response: OutgoingBrowserResponse): void {
42
+ renderListener?.(response)
43
+ }
44
+
45
+ /**
46
+ * Push an error to the registered error target.
47
+ *
48
+ * @param error - The error to display.
49
+ */
50
+ export function emitNativeError (error: Error): void {
51
+ errorListener?.(error)
52
+ }
@@ -0,0 +1,17 @@
1
+ import { Routing } from '@stone-js/router'
2
+ import { LogLevel, StoneApp } from '@stone-js/core'
3
+ import { nativeAdapterBlueprint } from '../adapter/nativeAdapterBlueprint'
4
+
5
+ /**
6
+ * Application
7
+ *
8
+ * This is the main application entry point.
9
+ *
10
+ * @Routing() enables the Stone router: incoming navigation events are matched
11
+ * against the route definitions declared by the controllers.
12
+ * @StoneApp() enables the Stone application (required). Extra blueprints are
13
+ * activated through its second argument: here, the native adapter.
14
+ */
15
+ @Routing()
16
+ @StoneApp({ name: 'Stone.js Native', logger: { level: LogLevel.INFO } }, [nativeAdapterBlueprint])
17
+ export class Application {}
@@ -0,0 +1,66 @@
1
+ import { Controller, Match } from '@stone-js/router'
2
+ import { IncomingBrowserEvent } from '@stone-js/browser-core'
3
+
4
+ /**
5
+ * The payload every route of this starter returns.
6
+ */
7
+ export interface WelcomeData {
8
+ message: string
9
+ route: string
10
+ framework: {
11
+ name: string
12
+ tagline: string
13
+ docs: string
14
+ }
15
+ }
16
+
17
+ /**
18
+ * WelcomeController
19
+ *
20
+ * The routes are matched against the URL carried by each navigation event,
21
+ * exactly like they would be in a browser SPA or behind an HTTP adapter:
22
+ * the domain does not know it is running inside a native application.
23
+ */
24
+ @Controller()
25
+ export class WelcomeController {
26
+ /**
27
+ * The landing route, dispatched once when the application starts.
28
+ *
29
+ * @returns The welcome payload.
30
+ */
31
+ @Match('/')
32
+ welcome (): WelcomeData {
33
+ return this.present('Welcome to Stone.js on React Native!', '/')
34
+ }
35
+
36
+ /**
37
+ * A parameterized route: the `name` route parameter is read from the event.
38
+ *
39
+ * @param event - The incoming navigation event.
40
+ * @returns The greeting payload.
41
+ */
42
+ @Match('/hello/:name')
43
+ hello (event: IncomingBrowserEvent): WelcomeData {
44
+ const name = String(event.get<string>('name', 'World'))
45
+ return this.present(`Hello ${name}! Same domain, native screen.`, `/hello/${name}`)
46
+ }
47
+
48
+ /**
49
+ * Build the branded payload returned by every route.
50
+ *
51
+ * @param message - The message to display.
52
+ * @param route - The matched route path.
53
+ * @returns The payload.
54
+ */
55
+ private present (message: string, route: string): WelcomeData {
56
+ return {
57
+ message,
58
+ route,
59
+ framework: {
60
+ name: 'Stone.js',
61
+ tagline: 'The continuum framework',
62
+ docs: 'https://stonejs.dev'
63
+ }
64
+ }
65
+ }
66
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "expo": {
3
+ "name": "basic-react-native-declarative",
4
+ "slug": "basic-react-native-declarative",
5
+ "version": "1.0.0",
6
+ "orientation": "portrait",
7
+ "icon": "./assets/icon.png",
8
+ "userInterfaceStyle": "light",
9
+ "ios": {
10
+ "supportsTablet": true
11
+ },
12
+ "android": {
13
+ "adaptiveIcon": {
14
+ "backgroundColor": "#E6F4FE",
15
+ "foregroundImage": "./assets/android-icon-foreground.png",
16
+ "backgroundImage": "./assets/android-icon-background.png",
17
+ "monochromeImage": "./assets/android-icon-monochrome.png"
18
+ },
19
+ "predictiveBackGestureEnabled": false
20
+ },
21
+ "web": {
22
+ "favicon": "./assets/favicon.png"
23
+ }
24
+ }
25
+ }
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Stone.js runs on TC39 stage-3 decorators (2023-11) with `Symbol.metadata`.
3
+ *
4
+ * `babel-preset-expo` applies `@babel/plugin-proposal-decorators` itself (in
5
+ * legacy mode by default): the decorator semantics are configured through the
6
+ * preset's `decorators` option, never by adding the plugin separately, so the
7
+ * preset keeps its ordering guarantees against the class-field transforms.
8
+ */
9
+ module.exports = function (api) {
10
+ api.cache(true)
11
+ return {
12
+ presets: [
13
+ ['babel-preset-expo', { decorators: { version: '2023-11' } }]
14
+ ]
15
+ }
16
+ }
@@ -0,0 +1,22 @@
1
+ /**
2
+ * Platform polyfills come first, before anything from Stone.js loads.
3
+ *
4
+ * - React Native's built-in `URL` is a stub (no `pathname`, no `searchParams`);
5
+ * Stone.js relies on the full WHATWG URL API, so the polyfill is required.
6
+ * - `TextEncoder` is installed only when the JS engine does not provide it
7
+ * (older Hermes versions).
8
+ */
9
+ import 'react-native-url-polyfill/auto'
10
+
11
+ if (typeof globalThis.TextEncoder === 'undefined') {
12
+ require('fast-text-encoding')
13
+ }
14
+
15
+ import { registerRootComponent } from 'expo'
16
+
17
+ import App from './App'
18
+
19
+ // registerRootComponent calls AppRegistry.registerComponent('main', () => App);
20
+ // It also ensures that whether you load the app in Expo Go or in a native build,
21
+ // the environment is set up appropriately
22
+ registerRootComponent(App)
@@ -0,0 +1,7 @@
1
+ const { getDefaultConfig } = require('expo/metro-config')
2
+
3
+ /**
4
+ * Pinning the project root keeps Metro scoped to this starter, even when the
5
+ * starter lives inside a larger workspace (as it does in the Stone.js monorepo).
6
+ */
7
+ module.exports = getDefaultConfig(__dirname)