@stone-js/starters 0.8.12 → 0.8.14
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/package.json +6 -5
- package/basic-react-declarative/tests/Application.spec.ts +4 -3
- package/basic-react-imperative/package.json +6 -5
- package/basic-react-imperative/tests/Application.spec.ts +4 -3
- package/basic-react-native-declarative/README.md +15 -22
- package/basic-react-native-declarative/package.json +7 -7
- package/basic-react-native-declarative/tests/HomeScreen.spec.ts +36 -0
- package/basic-react-native-declarative/tests/{Application.spec.ts → navigation.spec.ts} +10 -7
- package/basic-react-native-declarative/vitest.config.ts +6 -0
- package/basic-react-native-imperative/README.md +15 -22
- package/basic-react-native-imperative/package.json +7 -7
- package/basic-react-native-imperative/tests/HomeScreen.spec.ts +36 -0
- package/basic-react-native-imperative/tests/{Application.spec.ts → navigation.spec.ts} +10 -7
- package/basic-react-native-imperative/vitest.config.ts +6 -0
- package/basic-service-declarative/package.json +5 -4
- package/basic-service-declarative/tests/Application.spec.ts +2 -1
- package/basic-service-imperative/package.json +5 -4
- package/basic-service-imperative/tests/Application.spec.ts +2 -1
- package/continuum-showcase/package.json +9 -9
- package/continuum-showcase/tests/application.spec.ts +2 -1
- package/full-react-declarative/package.json +10 -10
- package/full-react-declarative/tests/application.spec.ts +2 -1
- package/full-react-imperative/package.json +10 -10
- package/full-react-imperative/tests/application.spec.ts +2 -1
- package/full-service-declarative/package.json +10 -10
- package/full-service-declarative/tests/application.spec.ts +2 -1
- package/full-service-imperative/package.json +10 -10
- package/full-service-imperative/tests/application.spec.ts +2 -1
- package/monorepo-declarative/LICENSE +21 -0
- package/monorepo-declarative/README.md +91 -0
- package/monorepo-declarative/apps/mobile/App.tsx +26 -0
- package/monorepo-declarative/apps/mobile/LICENSE +21 -0
- package/monorepo-declarative/apps/mobile/app/Application.ts +24 -0
- package/monorepo-declarative/apps/mobile/app/PortalMark.tsx +32 -0
- package/monorepo-declarative/apps/mobile/app/TaskListScreen.tsx +67 -0
- package/monorepo-declarative/apps/mobile/app/TaskListView.tsx +98 -0
- package/monorepo-declarative/apps/mobile/app/theme.ts +66 -0
- package/monorepo-declarative/apps/mobile/app.json +32 -0
- package/monorepo-declarative/apps/mobile/assets/android-icon-background.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/android-icon-foreground.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/android-icon-monochrome.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/favicon.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/icon.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/logo-dark.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/logo-light.png +0 -0
- package/monorepo-declarative/apps/mobile/assets/splash-icon.png +0 -0
- package/monorepo-declarative/apps/mobile/babel.config.js +16 -0
- package/monorepo-declarative/apps/mobile/index.ts +30 -0
- package/monorepo-declarative/apps/mobile/metro.config.js +13 -0
- package/monorepo-declarative/apps/mobile/package.json +47 -0
- package/monorepo-declarative/apps/mobile/tests/TaskListScreen.spec.ts +54 -0
- package/monorepo-declarative/apps/mobile/tests/stubs/react-native.ts +42 -0
- package/monorepo-declarative/apps/mobile/tsconfig.json +15 -0
- package/monorepo-declarative/apps/mobile/vitest.config.ts +35 -0
- package/monorepo-declarative/apps/web/LICENSE +21 -0
- package/monorepo-declarative/apps/web/app/Application.ts +23 -0
- package/monorepo-declarative/apps/web/app/TaskListPage.tsx +95 -0
- package/monorepo-declarative/apps/web/assets/css/index.css +220 -0
- package/monorepo-declarative/apps/web/package.json +40 -0
- package/monorepo-declarative/apps/web/public/favicon.svg +16 -0
- package/monorepo-declarative/apps/web/public/logo.svg +16 -0
- package/monorepo-declarative/apps/web/tests/TaskListPage.spec.ts +37 -0
- package/monorepo-declarative/apps/web/tsconfig.json +33 -0
- package/monorepo-declarative/package.json +28 -0
- package/monorepo-declarative/packages/domain/LICENSE +21 -0
- package/monorepo-declarative/packages/domain/package.json +32 -0
- package/monorepo-declarative/packages/domain/src/Task.ts +16 -0
- package/monorepo-declarative/packages/domain/src/TaskService.ts +75 -0
- package/monorepo-declarative/packages/domain/src/index.ts +33 -0
- package/monorepo-declarative/packages/domain/tests/TaskService.spec.ts +73 -0
- package/monorepo-declarative/packages/domain/tsconfig.build.json +4 -0
- package/monorepo-declarative/packages/domain/tsconfig.json +16 -0
- package/monorepo-declarative/packages/domain/vitest.config.ts +15 -0
- package/monorepo-declarative/pnpm-lock.yaml +7613 -0
- package/monorepo-declarative/pnpm-workspace.yaml +4 -0
- package/package.json +16 -1
- package/standard-react-declarative/package.json +9 -9
- package/standard-react-declarative/tests/application.spec.ts +2 -1
- package/standard-react-imperative/package.json +9 -9
- package/standard-react-imperative/tests/application.spec.ts +2 -1
- package/standard-service-declarative/package.json +7 -7
- package/standard-service-declarative/tests/Application.spec.ts +2 -1
- package/standard-service-imperative/package.json +7 -7
- package/standard-service-imperative/tests/Application.spec.ts +2 -1
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2020",
|
|
4
|
+
"useDefineForClassFields": true,
|
|
5
|
+
"module": "ESNext",
|
|
6
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
|
7
|
+
"skipLibCheck": true,
|
|
8
|
+
"experimentalDecorators": true,
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"isolatedModules": true,
|
|
14
|
+
"moduleDetection": "force",
|
|
15
|
+
"noEmit": true,
|
|
16
|
+
"jsx": "react-jsx",
|
|
17
|
+
"resolveJsonModule": true,
|
|
18
|
+
|
|
19
|
+
/* Linting */
|
|
20
|
+
"strict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"noFallthroughCasesInSwitch": true,
|
|
24
|
+
"noUncheckedSideEffectImports": true,
|
|
25
|
+
|
|
26
|
+
"types": ["node", "vite/client", "vitest/globals"],
|
|
27
|
+
},
|
|
28
|
+
"include": [
|
|
29
|
+
"app",
|
|
30
|
+
"tests",
|
|
31
|
+
"vitest.config.ts"
|
|
32
|
+
]
|
|
33
|
+
}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "acme",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "One domain, two applications: a Stone.js monorepo starter.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"engines": {
|
|
9
|
+
"node": ">=20.0.0"
|
|
10
|
+
},
|
|
11
|
+
"scripts": {
|
|
12
|
+
"build": "pnpm --filter @acme/domain build && pnpm --filter @acme/web build",
|
|
13
|
+
"dev:web": "pnpm --filter @acme/web dev",
|
|
14
|
+
"dev:mobile": "pnpm --filter @acme/mobile start",
|
|
15
|
+
"test": "pnpm -r run test",
|
|
16
|
+
"typecheck": "pnpm -r run typecheck",
|
|
17
|
+
"domain": "pnpm --filter @acme/domain"
|
|
18
|
+
},
|
|
19
|
+
"devDependencies": {
|
|
20
|
+
"typescript": "~5.9.0"
|
|
21
|
+
},
|
|
22
|
+
"pnpm": {
|
|
23
|
+
"overrides": {
|
|
24
|
+
"react": "19.2.3",
|
|
25
|
+
"react-dom": "19.2.3"
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright © 2026 Stone Foundation
|
|
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,32 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@acme/domain",
|
|
3
|
+
"private": true,
|
|
4
|
+
"version": "1.0.0",
|
|
5
|
+
"description": "The application's domain: entities and behaviour, with no platform in sight.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"default": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": ["dist"],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "rimraf dist && tsc -p tsconfig.build.json",
|
|
19
|
+
"typecheck": "tsc --noEmit",
|
|
20
|
+
"test": "vitest run",
|
|
21
|
+
"test:cvg": "npm run test -- --coverage"
|
|
22
|
+
},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@stone-js/core": "^0.8.13"
|
|
25
|
+
},
|
|
26
|
+
"devDependencies": {
|
|
27
|
+
"@vitest/coverage-v8": "^3.0.9",
|
|
28
|
+
"rimraf": "^6.0.1",
|
|
29
|
+
"typescript": "~5.9.0",
|
|
30
|
+
"vitest": "^3.0.9"
|
|
31
|
+
}
|
|
32
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* A task.
|
|
3
|
+
*
|
|
4
|
+
* The whole of what this application is about, and it says nothing about HTTP, about a screen, or
|
|
5
|
+
* about where it is stored. That is the point: a domain that names a platform can only run on it.
|
|
6
|
+
*/
|
|
7
|
+
export interface Task {
|
|
8
|
+
/** Stable identifier, used in routes. */
|
|
9
|
+
id: string
|
|
10
|
+
|
|
11
|
+
/** What the user wrote. */
|
|
12
|
+
title: string
|
|
13
|
+
|
|
14
|
+
/** Whether it is done. */
|
|
15
|
+
done: boolean
|
|
16
|
+
}
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
import { Task } from './Task.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The application's one piece of behaviour.
|
|
5
|
+
*
|
|
6
|
+
* It takes the tasks it works on, so a starter can hand it an array and a real application can hand
|
|
7
|
+
* it a repository, without a line here changing. What matters is what it does *not* import: no
|
|
8
|
+
* `Request`, no `Response`, no `window`, no `View`. It is the half of the application that is true
|
|
9
|
+
* on a server, in a browser, on a phone and at the edge, and it is written once.
|
|
10
|
+
*/
|
|
11
|
+
export class TaskService {
|
|
12
|
+
private readonly tasks: Task[]
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Create the service.
|
|
16
|
+
*
|
|
17
|
+
* @param tasks - Where the tasks are kept. The caller owns it, which is what lets a test hand in
|
|
18
|
+
* its own and an application hand in its store.
|
|
19
|
+
* @returns The service.
|
|
20
|
+
*/
|
|
21
|
+
static create (tasks: Task[]): TaskService {
|
|
22
|
+
return new TaskService(tasks)
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* @param tasks - Where the tasks are kept.
|
|
27
|
+
*/
|
|
28
|
+
private constructor (tasks: Task[]) {
|
|
29
|
+
this.tasks = tasks
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Every task.
|
|
34
|
+
*
|
|
35
|
+
* @returns A copy, so a caller cannot reach into the service's state.
|
|
36
|
+
*/
|
|
37
|
+
all (): Task[] {
|
|
38
|
+
return [...this.tasks]
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* One task by its identifier.
|
|
43
|
+
*
|
|
44
|
+
* @param id - The identifier.
|
|
45
|
+
* @returns The task, or `undefined` when there is none.
|
|
46
|
+
*/
|
|
47
|
+
find (id: string): Task | undefined {
|
|
48
|
+
return this.tasks.find((task) => task.id === id)
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Flip a task's state.
|
|
53
|
+
*
|
|
54
|
+
* @param id - The identifier.
|
|
55
|
+
* @returns The task as it now is, or `undefined` when there is none.
|
|
56
|
+
*/
|
|
57
|
+
toggle (id: string): Task | undefined {
|
|
58
|
+
const task = this.find(id)
|
|
59
|
+
|
|
60
|
+
if (task === undefined) { return undefined }
|
|
61
|
+
|
|
62
|
+
task.done = !task.done
|
|
63
|
+
|
|
64
|
+
return task
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* How many are left.
|
|
69
|
+
*
|
|
70
|
+
* @returns The number of tasks not done.
|
|
71
|
+
*/
|
|
72
|
+
remaining (): number {
|
|
73
|
+
return this.tasks.filter((task) => !task.done).length
|
|
74
|
+
}
|
|
75
|
+
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { Task } from './Task.js'
|
|
2
|
+
import { defineService } from '@stone-js/core'
|
|
3
|
+
import { TaskService } from './TaskService.js'
|
|
4
|
+
|
|
5
|
+
export * from './Task.js'
|
|
6
|
+
export * from './TaskService.js'
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Where the tasks actually live, for a starter that installs no database.
|
|
10
|
+
*
|
|
11
|
+
* A module-level array, deliberately, because Stone.js gives every event its own container: a
|
|
12
|
+
* service holding state in itself would forget it between two events. A real application injects a
|
|
13
|
+
* repository here and this constant disappears; nothing in `TaskService` changes.
|
|
14
|
+
*/
|
|
15
|
+
const store: Task[] = [
|
|
16
|
+
{ id: '1', title: 'Write the domain once', done: true },
|
|
17
|
+
{ id: '2', title: 'Run it on the web', done: true },
|
|
18
|
+
{ id: '3', title: 'Run the same code on a phone', done: false }
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* The domain, as something an application can activate.
|
|
23
|
+
*
|
|
24
|
+
* A module is activated by its decorator or by its blueprint, never by a third thing. A shared
|
|
25
|
+
* package takes the blueprint path: decorating `TaskService` would mean the build has to discover
|
|
26
|
+
* it, and a build discovers what is under `app/`, not what is in `node_modules`. So the domain hands
|
|
27
|
+
* out a blueprint fragment instead, and each application activates it in its own `@StoneApp`.
|
|
28
|
+
*
|
|
29
|
+
* A factory rather than a class, because `TaskService` has a private constructor and is created
|
|
30
|
+
* through `create`, which is the convention across Stone.js. The alias is what a page asks for:
|
|
31
|
+
* `constructor ({ tasks }: { tasks: TaskService })`.
|
|
32
|
+
*/
|
|
33
|
+
export const domainBlueprint = defineService(() => TaskService.create(store), { alias: 'tasks' })
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { Task, TaskService } from '../src/index.js'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* The domain, tested on its own.
|
|
5
|
+
*
|
|
6
|
+
* No application, no kernel, no adapter, no renderer, because none of them are involved. This is
|
|
7
|
+
* the half of the codebase that both applications share, and it is also the half that is cheapest
|
|
8
|
+
* to test: plain objects in, plain objects out, in milliseconds.
|
|
9
|
+
*/
|
|
10
|
+
describe('TaskService', () => {
|
|
11
|
+
const seed = (): Task[] => [
|
|
12
|
+
{ id: '1', title: 'Done already', done: true },
|
|
13
|
+
{ id: '2', title: 'Still to do', done: false }
|
|
14
|
+
]
|
|
15
|
+
|
|
16
|
+
it('lists the tasks it was given', () => {
|
|
17
|
+
const service = TaskService.create(seed())
|
|
18
|
+
|
|
19
|
+
expect(service.all()).toHaveLength(2)
|
|
20
|
+
expect(service.all()[0].title).toBe('Done already')
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
it('hands out a copy, so a caller cannot reach into its state', () => {
|
|
24
|
+
const service = TaskService.create(seed())
|
|
25
|
+
|
|
26
|
+
service.all().push({ id: '3', title: 'Injected', done: false })
|
|
27
|
+
|
|
28
|
+
expect(service.all()).toHaveLength(2)
|
|
29
|
+
})
|
|
30
|
+
|
|
31
|
+
it('finds a task by its identifier', () => {
|
|
32
|
+
const service = TaskService.create(seed())
|
|
33
|
+
|
|
34
|
+
expect(service.find('2')?.title).toBe('Still to do')
|
|
35
|
+
})
|
|
36
|
+
|
|
37
|
+
it('has nothing to find for an unknown identifier', () => {
|
|
38
|
+
const service = TaskService.create(seed())
|
|
39
|
+
|
|
40
|
+
expect(service.find('nope')).toBeUndefined()
|
|
41
|
+
})
|
|
42
|
+
|
|
43
|
+
it('flips a task and reports it as it now is', () => {
|
|
44
|
+
const service = TaskService.create(seed())
|
|
45
|
+
|
|
46
|
+
expect(service.toggle('2')?.done).toBe(true)
|
|
47
|
+
expect(service.find('2')?.done).toBe(true)
|
|
48
|
+
})
|
|
49
|
+
|
|
50
|
+
it('flips back', () => {
|
|
51
|
+
const service = TaskService.create(seed())
|
|
52
|
+
|
|
53
|
+
service.toggle('1')
|
|
54
|
+
|
|
55
|
+
expect(service.find('1')?.done).toBe(false)
|
|
56
|
+
})
|
|
57
|
+
|
|
58
|
+
it('has nothing to flip for an unknown identifier', () => {
|
|
59
|
+
const service = TaskService.create(seed())
|
|
60
|
+
|
|
61
|
+
expect(service.toggle('nope')).toBeUndefined()
|
|
62
|
+
})
|
|
63
|
+
|
|
64
|
+
it('counts what is left', () => {
|
|
65
|
+
const service = TaskService.create(seed())
|
|
66
|
+
|
|
67
|
+
expect(service.remaining()).toBe(1)
|
|
68
|
+
|
|
69
|
+
service.toggle('2')
|
|
70
|
+
|
|
71
|
+
expect(service.remaining()).toBe(0)
|
|
72
|
+
})
|
|
73
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"module": "NodeNext",
|
|
5
|
+
"moduleResolution": "NodeNext",
|
|
6
|
+
"lib": ["ES2022"],
|
|
7
|
+
"strict": true,
|
|
8
|
+
"declaration": true,
|
|
9
|
+
"skipLibCheck": true,
|
|
10
|
+
"esModuleInterop": true,
|
|
11
|
+
"forceConsistentCasingInFileNames": true,
|
|
12
|
+
"outDir": "dist",
|
|
13
|
+
"types": ["vitest/globals"]
|
|
14
|
+
},
|
|
15
|
+
"include": ["src", "tests"]
|
|
16
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { defineConfig } from 'vitest/config'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
test: {
|
|
5
|
+
globals: true,
|
|
6
|
+
environment: 'node',
|
|
7
|
+
include: ['./tests/**/*.{test,spec}.ts'],
|
|
8
|
+
coverage: {
|
|
9
|
+
provider: 'v8',
|
|
10
|
+
include: ['src/**/*.ts'],
|
|
11
|
+
reporter: ['text', 'html'],
|
|
12
|
+
reportsDirectory: './coverage'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
})
|