@domain-first/project-structure 2.6.0 → 2.7.1
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/README.md +236 -27
- package/dist/index.cjs +230 -19
- package/dist/index.js +230 -19
- package/dist/use-cases/index.d.ts +3 -0
- package/dist/use-cases/scaffold-new-application-service.d.ts +2 -0
- package/dist/use-cases/scaffold-new-infrastructure-service.d.ts +2 -0
- package/dist/use-cases/scaffold-new-presentation-service.d.ts +2 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,54 +1,263 @@
|
|
|
1
1
|
# @domain-first/project-structure
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
**Spend less time setting up files. Spend more time building your domain.**
|
|
4
|
+
|
|
5
|
+
An interactive CLI for growing TypeScript applications around business domains. Scaffold bounded contexts, aggregates, use cases, commands, queries, adapters, REST endpoints, and React modules with a consistent structure.
|
|
6
|
+
|
|
7
|
+
Choose what you need, answer a few prompts, and get the source files, optional test skeletons, and wiring to start implementing the feature.
|
|
4
8
|
|
|
5
9
|
```bash
|
|
10
|
+
npm install --save-dev @domain-first/project-structure
|
|
6
11
|
npx df-ps
|
|
7
12
|
```
|
|
8
13
|
|
|
9
|
-
|
|
14
|
+
[Quick start](#quick-start) · [Project structure](#project-structure) · [Generators](#generators) · [Configuration](#configuration) · [Example workflow](#example-workflow)
|
|
10
15
|
|
|
11
|
-
|
|
12
|
-
- **One consistent architecture** — every bounded context, layer and file lands in the same place, no matter who on the team runs it
|
|
13
|
-
- **Grows with your stack** — adapts output to whichever `@domain-first` packages (`types`, `errors`, `handlers`, `wire`, `handlers-rest`) you use, or stays framework-free if you don't
|
|
14
|
-
- **Interactive, not magic** — a guided CLI menu, not a rigid template you fight against
|
|
16
|
+
## Why use it?
|
|
15
17
|
|
|
16
|
-
|
|
18
|
+
Adding an aggregate often means adding a repository contract, infrastructure implementations, tests, and dependency wiring too. Repeating that setup by hand takes time and lets conventions drift.
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
20
|
+
`df-ps` turns those recurring decisions into a guided workflow:
|
|
21
|
+
|
|
22
|
+
- **Keep the team consistent.** Give each kind of component a predictable home, filename, and class name.
|
|
23
|
+
- **Grow feature by feature.** Add a bounded context or a single component as the application evolves.
|
|
24
|
+
- **Keep business code organized.** Group code by bounded context, with domain, application, infrastructure, and presentation layers inside it.
|
|
25
|
+
- **Prepare for multiple implementations.** Generate primary and test implementations of repositories, execution commands, queries, and application ports.
|
|
26
|
+
- **Use the integrations you need.** Enable templates for the `@domain-first` packages in your project and optional React scaffolding.
|
|
27
|
+
|
|
28
|
+
The generated files belong to your project. Fill in the business rules, schemas, dependencies, and integration code as you would with any other source file.
|
|
21
29
|
|
|
22
30
|
## Quick start
|
|
23
31
|
|
|
32
|
+
Run the CLI inside a project that has a `package.json`. For a new project, create one with `npm init -y` first. Use Node.js 22.13+ within the Node 22 release line, or Node.js 24+.
|
|
33
|
+
|
|
34
|
+
### 1. Install and initialize
|
|
35
|
+
|
|
24
36
|
```bash
|
|
37
|
+
npm install --save-dev @domain-first/project-structure
|
|
25
38
|
npx df-ps
|
|
26
39
|
```
|
|
27
40
|
|
|
28
|
-
|
|
41
|
+
You can also run it without adding it as a development dependency:
|
|
29
42
|
|
|
30
|
-
|
|
43
|
+
```bash
|
|
44
|
+
npm exec --package=@domain-first/project-structure -- df-ps
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
On the first run, the CLI asks for:
|
|
48
|
+
|
|
49
|
+
| Setting | What to enter |
|
|
50
|
+
| ---------------------------------- | ----------------------------------------------------------------------------------------------------------- |
|
|
51
|
+
| Root folder | The source directory, such as `src` or `src/server`. Default: `src`. |
|
|
52
|
+
| `@domain-first` packages | The integrations to use in generated templates. |
|
|
53
|
+
| Default persistence implementation | A label such as `prisma`, used as the suggested implementation for execution commands and queries. |
|
|
54
|
+
| React | Whether to show the React module menu. |
|
|
55
|
+
| Unit tests | Whether to offer test generation, and which module to import test helpers from. Suggested module: `vitest`. |
|
|
31
56
|
|
|
32
|
-
|
|
57
|
+
Setup writes `domain-first.project-structure.config.json` beside your `package.json` and creates shared helpers for the selected `wire` and `errors` integrations. That first run then exits.
|
|
33
58
|
|
|
34
|
-
|
|
35
|
-
- **Application** — use cases, commands & queries (execution or orchestration), ports
|
|
36
|
-
- **Infrastructure** — adapter implementations per port/repository (e.g. `prisma`, `in-memory`)
|
|
37
|
-
- **Presentation** — REST endpoints, React pages/widgets
|
|
59
|
+
The CLI uses the nearest `package.json` found by walking up from the current directory. In a monorepo, run it inside the package you want to scaffold.
|
|
38
60
|
|
|
39
|
-
|
|
61
|
+
### 2. Create a bounded context
|
|
40
62
|
|
|
63
|
+
Run `npx df-ps` again, choose **Scaffold new bounded context**, and enter a name such as `sales`.
|
|
64
|
+
|
|
65
|
+
### 3. Add a component
|
|
66
|
+
|
|
67
|
+
Run `npx df-ps`, choose **Bounded Context: sales**, select a layer, and choose a generator. For example:
|
|
68
|
+
|
|
69
|
+
```text
|
|
70
|
+
Bounded Context: sales → Domain → New Aggregate
|
|
41
71
|
```
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
72
|
+
|
|
73
|
+
Each invocation performs one scaffolding operation. Run the command again to add the next component. Folders are created as needed.
|
|
74
|
+
|
|
75
|
+
## Project structure
|
|
76
|
+
|
|
77
|
+
A bounded context groups a business area and its implementation. Within it, each layer has a specific role:
|
|
78
|
+
|
|
79
|
+
| Location | Purpose |
|
|
80
|
+
| ----------------- | ----------------------------------------------------------------------------------------------------- |
|
|
81
|
+
| `domain/` | Aggregates, repository contracts, domain services, and business errors. |
|
|
82
|
+
| `application/` | Use cases, commands, queries, ports, and application services. |
|
|
83
|
+
| `infrastructure/` | Concrete repositories, adapters, execution handlers, and infrastructure services. |
|
|
84
|
+
| `presentation/` | REST endpoints and presentation services. |
|
|
85
|
+
| `wiring/` | Factories that construct components and select implementations, when `@domain-first/wire` is enabled. |
|
|
86
|
+
|
|
87
|
+
The **Shared Layer** menu provides cross-context errors, application ports, and application, infrastructure, and presentation services under `<rootFolder>/shared`.
|
|
88
|
+
|
|
89
|
+
A project using handler and wiring integrations can grow into this structure:
|
|
90
|
+
|
|
91
|
+
```text
|
|
92
|
+
src/
|
|
93
|
+
├── bounded-contexts/
|
|
94
|
+
│ └── sales/
|
|
95
|
+
│ ├── domain/
|
|
96
|
+
│ │ ├── aggregates/order/
|
|
97
|
+
│ │ ├── errors/
|
|
98
|
+
│ │ └── services/
|
|
99
|
+
│ ├── application/
|
|
100
|
+
│ │ ├── commands/ # execution/ and orchestration/
|
|
101
|
+
│ │ ├── queries/ # execution/ and orchestration/
|
|
102
|
+
│ │ ├── ports/
|
|
103
|
+
│ │ ├── services/
|
|
104
|
+
│ │ └── use-cases/
|
|
105
|
+
│ ├── infrastructure/
|
|
106
|
+
│ │ ├── application-adapters/
|
|
107
|
+
│ │ ├── commands/
|
|
108
|
+
│ │ ├── queries/
|
|
109
|
+
│ │ ├── repositories/
|
|
110
|
+
│ │ └── services/
|
|
111
|
+
│ ├── presentation/
|
|
112
|
+
│ │ ├── rest/
|
|
113
|
+
│ │ └── services/
|
|
114
|
+
│ └── wiring/
|
|
115
|
+
├── shared/
|
|
116
|
+
└── presentation/
|
|
117
|
+
├── rest/
|
|
118
|
+
└── react/
|
|
119
|
+
├── pages/
|
|
120
|
+
└── widgets/
|
|
50
121
|
```
|
|
51
122
|
|
|
123
|
+
This is an example of the accumulated output; creating a context alone creates its directory and, when enabled, its error namespace.
|
|
124
|
+
|
|
125
|
+
## Generators
|
|
126
|
+
|
|
127
|
+
Choose a bounded context, then a layer:
|
|
128
|
+
|
|
129
|
+
| Layer / menu option | Generated output |
|
|
130
|
+
| ----------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
|
|
131
|
+
| Domain → New Aggregate | Aggregate root and barrel export; optional repository contract, primary and test implementations, tests, and repository wiring. |
|
|
132
|
+
| Domain → Errors | Errors in the context namespace, nested namespaces, and exports. Uses `@domain-first/errors`. |
|
|
133
|
+
| Domain → New Service | Domain service class, with optional tests and wiring. |
|
|
134
|
+
| Application → New Use Case | Use case class; handler schemas and `handle` when enabled; optional tests and wiring. |
|
|
135
|
+
| Application → New Command / New Query | Execution or orchestration templates, with optional tests and wiring. |
|
|
136
|
+
| Application → New Port | Abstract port and an adapter implementation, with an optional test adapter, tests, and wiring. |
|
|
137
|
+
| Application → New Service | Application service class, with optional tests and wiring. |
|
|
138
|
+
| Infrastructure → New Service | Infrastructure service class, with optional tests and wiring. |
|
|
139
|
+
| Presentation → New Service | Presentation service class, with optional tests and wiring. |
|
|
140
|
+
| Presentation → New Handlers-REST Endpoint | Endpoint class for a controller, method, and relative path; wiring and handler exports when enabled. Requires `@domain-first/handlers-rest` to appear in the menu. |
|
|
141
|
+
|
|
142
|
+
### Commands and queries
|
|
143
|
+
|
|
144
|
+
Both generators offer two modes:
|
|
145
|
+
|
|
146
|
+
- **Execution (with infrastructure implementation):** creates an abstract application contract and a concrete implementation under `infrastructure/commands/<implementation>` or `infrastructure/queries/<implementation>`. You can add a separate implementation for tests.
|
|
147
|
+
- **Orchestrator (no infrastructure implementation):** creates an application class under `application/commands/orchestration` or `application/queries/orchestration`, ready for coordinating other operations.
|
|
148
|
+
|
|
149
|
+
With `@domain-first/handlers`, templates include input/output schemas and a typed handler contract or `defineHandler` implementation. Execution contracts go in the corresponding `execution/` directory; without that integration, they go directly in `application/commands` or `application/queries`.
|
|
150
|
+
|
|
151
|
+
### Wiring and tests
|
|
152
|
+
|
|
153
|
+
With `@domain-first/wire`, generators create `wireClass` factories. Repositories, execution commands, queries, and ports use the shared `envBranchedWire` helper to choose an implementation based on `NODE_ENV`:
|
|
154
|
+
|
|
155
|
+
| Environment | Implementation |
|
|
156
|
+
| ------------- | ---------------------------------------------------------------------------- |
|
|
157
|
+
| `test` | The test implementation, if requested; otherwise the primary implementation. |
|
|
158
|
+
| `development` | The primary implementation. Also used when `NODE_ENV` is unset. |
|
|
159
|
+
| `production` | The primary implementation. |
|
|
160
|
+
|
|
161
|
+
Implementation names such as `prisma`, `api`, `in-memory`, and `mock` determine names and locations for generated classes. Add the database queries, API calls, or in-memory behavior yourself. Fill in the dependency lists in wiring factories as you implement constructors.
|
|
162
|
+
|
|
163
|
+
When `testingLibrary` is configured, supported generators ask whether to add `.spec.ts` files. These start with construction or wiring checks; extend them with tests for your business behavior. Repository, command, query, and port test templates reference wiring factories, so use the wiring integration or adapt those tests to your own construction setup.
|
|
164
|
+
|
|
165
|
+
### REST endpoints
|
|
166
|
+
|
|
167
|
+
Select an existing controller or create one, choose `get`, `post`, `patch`, `delete`, or `put`, and enter a relative path. For example, context `sales`, controller `orders`, path `recent`, and method `get` produce:
|
|
168
|
+
|
|
169
|
+
```text
|
|
170
|
+
src/bounded-contexts/sales/presentation/rest/orders/recent/GET.ts
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
The route metadata uses `/sales/orders/recent`. Connect the endpoint to an application handler and provide its `EndpointGenerator`; the generated handler dependency starts as a `never` placeholder.
|
|
174
|
+
|
|
175
|
+
With wiring enabled, the generator also adds endpoint factories and exports collected as `restHandlers` in `<rootFolder>/presentation/rest/index.ts`. Connect these handlers to your HTTP server.
|
|
176
|
+
|
|
177
|
+
### React modules
|
|
178
|
+
|
|
179
|
+
Enable `useReact` and choose **Scaffold React module** from the main menu. Modules are created under `<rootFolder>/presentation/react`.
|
|
180
|
+
|
|
181
|
+
| Generator | Output |
|
|
182
|
+
| ------------------------------ | ---------------------------------------------------------------------------------------------------------- |
|
|
183
|
+
| New page | `pages/<name>-page.tsx` with a typed props object and a component. |
|
|
184
|
+
| New widget → monolithic | `widgets/<name>/index.tsx` with props and rendering in one file. |
|
|
185
|
+
| New widget → with separated ui | Widget entry point, `types.ts`, `ui/index.tsx`, and `hooks/use-ui-model.ts`. |
|
|
186
|
+
| New widget → headless | Widget entry point, types, and a UI-model hook; the rendering component is supplied through the `UI` prop. |
|
|
187
|
+
|
|
188
|
+
## Configuration
|
|
189
|
+
|
|
190
|
+
The project configuration is stored in `domain-first.project-structure.config.json`. Commit it to share generation settings with your team. For example:
|
|
191
|
+
|
|
192
|
+
```json
|
|
193
|
+
{
|
|
194
|
+
"rootFolder": "src",
|
|
195
|
+
"domainFirstPackages": [
|
|
196
|
+
"@domain-first/types",
|
|
197
|
+
"@domain-first/errors",
|
|
198
|
+
"@domain-first/handlers",
|
|
199
|
+
"@domain-first/wire",
|
|
200
|
+
"@domain-first/handlers-rest"
|
|
201
|
+
],
|
|
202
|
+
"defaultPersistenceLayerImplementation": "prisma",
|
|
203
|
+
"useReact": true,
|
|
204
|
+
"testingLibrary": "vitest"
|
|
205
|
+
}
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
| Field | Effect |
|
|
209
|
+
| --------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
210
|
+
| `rootFolder` | Base path for generated source, relative to the project root. |
|
|
211
|
+
| `domainFirstPackages` | Explicit list of integrations used by templates. An empty array selects basic class templates where supported. |
|
|
212
|
+
| `defaultPersistenceLayerImplementation` | Suggested implementation label for execution commands and queries. Repository and port prompts have their own defaults: `prisma` and `api`. |
|
|
213
|
+
| `useReact` | Shows or hides the React module menu. |
|
|
214
|
+
| `testingLibrary` | Module imported by generated tests. Omit this field to disable test prompts. Templates expect helpers such as `describe`, `test`, `expect`, and `beforeEach`. |
|
|
215
|
+
|
|
216
|
+
### Package integrations
|
|
217
|
+
|
|
218
|
+
| Package | What it enables |
|
|
219
|
+
| ----------------------------- | ------------------------------------------------------------------------------------------------------------------ |
|
|
220
|
+
| `@domain-first/types` | Aggregate roots extending `domainType()`. |
|
|
221
|
+
| `@domain-first/errors` | Context and shared error namespaces initialized during setup/context creation. Error generation uses this package. |
|
|
222
|
+
| `@domain-first/handlers` | Handler contracts, `defineHandler`, and input/output schema placeholders for use cases, commands, and queries. |
|
|
223
|
+
| `@domain-first/wire` | Construction factories and environment-based implementation selection. |
|
|
224
|
+
| `@domain-first/handlers-rest` | The REST endpoint generator using `EndpointGenerator`. |
|
|
225
|
+
|
|
226
|
+
Select the packages you intend to use and install them in your application separately. The CLI reads this list from configuration; it does not detect or install dependencies. React and your testing library also need their own project setup.
|
|
227
|
+
|
|
228
|
+
You can edit the configuration for future runs. Existing files are left as they are when you edit settings; enabling `wire` or `errors` later also requires adding the shared helpers that initial setup would create, plus context error namespaces where needed.
|
|
229
|
+
|
|
230
|
+
## Example workflow
|
|
231
|
+
|
|
232
|
+
To start an order feature, enable `@domain-first/wire` during initialization and run `npx df-ps` for each step:
|
|
233
|
+
|
|
234
|
+
1. Choose **Scaffold new bounded context** and name it `sales`.
|
|
235
|
+
2. Choose **Bounded Context: sales → Domain → New Aggregate**, name it `order`, and add a repository with `prisma` and an `in-memory` test implementation.
|
|
236
|
+
3. Choose **Application → New Use Case** within `sales` and name it `place-order`.
|
|
237
|
+
4. Implement the aggregate's rules, repository operations, and use case; connect their dependencies in the generated wiring files.
|
|
238
|
+
|
|
239
|
+
The key files from these selections are:
|
|
240
|
+
|
|
241
|
+
```text
|
|
242
|
+
src/bounded-contexts/sales/
|
|
243
|
+
├── domain/aggregates/order/
|
|
244
|
+
│ ├── index.ts
|
|
245
|
+
│ ├── order.aggregate-root.ts
|
|
246
|
+
│ └── order-repository.ts
|
|
247
|
+
├── application/use-cases/
|
|
248
|
+
│ └── place-order-use-case.ts
|
|
249
|
+
├── infrastructure/repositories/
|
|
250
|
+
│ ├── prisma/prisma-order-repository.ts
|
|
251
|
+
│ └── in-memory/in-memory-order-repository.ts
|
|
252
|
+
└── wiring/
|
|
253
|
+
├── repositories/wire-order-repository.ts
|
|
254
|
+
└── use-cases/wire-place-order-use-case.ts
|
|
255
|
+
```
|
|
256
|
+
|
|
257
|
+
Use lowercase kebab-case for entered names, such as `place-order` and `payment-gateway`. Names are used in paths, and class and variable names are derived from their hyphen-separated words: `place-order` becomes `PlaceOrderUseCase` for a use case.
|
|
258
|
+
|
|
259
|
+
Generation writes files directly and can overwrite matching filenames without confirmation. Use a new name for a new component, and review the generated changes before continuing implementation.
|
|
260
|
+
|
|
52
261
|
## License
|
|
53
262
|
|
|
54
|
-
MIT
|
|
263
|
+
[MIT](./LICENSE)
|
package/dist/index.cjs
CHANGED
|
@@ -447,6 +447,56 @@ export const wire${naming.ClassName} = envBranchedWire({
|
|
|
447
447
|
|
|
448
448
|
`.trim());
|
|
449
449
|
};
|
|
450
|
+
const scaffoldNewApplicationService = async (boundedContextFolder)=>{
|
|
451
|
+
const name = await (0, prompts_namespaceObject.input)({
|
|
452
|
+
message: 'Name: '
|
|
453
|
+
});
|
|
454
|
+
const naming = new UnknownFormatNaming(name);
|
|
455
|
+
let scaffoldUnitTests;
|
|
456
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await (0, prompts_namespaceObject.confirm)({
|
|
457
|
+
message: 'Scaffold unit-tests'
|
|
458
|
+
});
|
|
459
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
460
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
461
|
+
'wiring',
|
|
462
|
+
'application-services'
|
|
463
|
+
]);
|
|
464
|
+
const applicationServicesFolder = boundedContextFolder.subitem([
|
|
465
|
+
'application',
|
|
466
|
+
'services'
|
|
467
|
+
]);
|
|
468
|
+
applicationServicesFolder.createFile(`${naming.fileName}-application-service.ts`, `
|
|
469
|
+
export class ${naming.ClassName}ApplicationService {
|
|
470
|
+
|
|
471
|
+
}
|
|
472
|
+
`.trim());
|
|
473
|
+
if (scaffoldUnitTests) applicationServicesFolder.createFile(`${naming.fileName}-application-service.spec.ts`, `
|
|
474
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
475
|
+
${withWiring ? `import { type ${naming.ClassName}ApplicationService } from './${naming.fileName}-application-service'
|
|
476
|
+
import { wire${naming.ClassName}ApplicationService } from '../../wiring/application-services/wire-${naming.fileName}-application-service'` : `import { ${naming.ClassName}ApplicationService } from './${naming.fileName}-application-service'`}
|
|
477
|
+
|
|
478
|
+
let ${naming.variableName}ApplicationService: ${naming.ClassName}ApplicationService
|
|
479
|
+
|
|
480
|
+
beforeEach(() => {
|
|
481
|
+
${naming.variableName}ApplicationService = ${withWiring ? `wire${naming.ClassName}ApplicationService()` : `new ${naming.ClassName}ApplicationService()`}
|
|
482
|
+
})
|
|
483
|
+
|
|
484
|
+
describe('${naming.withSpaces}', () => {
|
|
485
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
486
|
+
expect(${naming.variableName}ApplicationService).toBeDefined()
|
|
487
|
+
})
|
|
488
|
+
})
|
|
489
|
+
`.trim());
|
|
490
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-application-service.ts`, `
|
|
491
|
+
import { wireClass } from '@domain-first/wire'
|
|
492
|
+
import { ${naming.ClassName}ApplicationService } from '../../application/services/${naming.fileName}-application-service'
|
|
493
|
+
|
|
494
|
+
export const wire${naming.ClassName}ApplicationService = wireClass(
|
|
495
|
+
${naming.ClassName}ApplicationService,
|
|
496
|
+
[]
|
|
497
|
+
)
|
|
498
|
+
`.trim());
|
|
499
|
+
};
|
|
450
500
|
const scaffoldNewBoundedContextUseCase = async ()=>{
|
|
451
501
|
const name = await (0, prompts_namespaceObject.input)({
|
|
452
502
|
message: 'Name:',
|
|
@@ -639,24 +689,46 @@ const scaffoldNewDomainService = async (boundedContextFolder)=>{
|
|
|
639
689
|
message: 'Name:'
|
|
640
690
|
});
|
|
641
691
|
const naming = new UnknownFormatNaming(portName);
|
|
642
|
-
|
|
692
|
+
let scaffoldUnitTests;
|
|
693
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await (0, prompts_namespaceObject.confirm)({
|
|
694
|
+
message: 'Scaffold unit-tests'
|
|
695
|
+
});
|
|
696
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
697
|
+
const domainServicesFolder = boundedContextFolder.subitem([
|
|
643
698
|
'domain',
|
|
644
699
|
'services'
|
|
645
|
-
])
|
|
646
|
-
|
|
700
|
+
]);
|
|
701
|
+
domainServicesFolder.createFile(`${naming.fileName}-domain-service.ts`, `
|
|
702
|
+
export class ${naming.ClassName}DomainService {
|
|
647
703
|
|
|
648
704
|
}
|
|
649
705
|
`.trim());
|
|
650
|
-
if (
|
|
706
|
+
if (scaffoldUnitTests) domainServicesFolder.createFile(`${naming.fileName}-domain-service.spec.ts`, `
|
|
707
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
708
|
+
${withWiring ? `import { type ${naming.ClassName}DomainService } from './${naming.fileName}-domain-service'
|
|
709
|
+
import { wire${naming.ClassName}DomainService } from '../../wiring/domain-services/wire-${naming.fileName}-domain-service'` : `import { ${naming.ClassName}DomainService } from './${naming.fileName}-domain-service'`}
|
|
710
|
+
|
|
711
|
+
let ${naming.variableName}DomainService: ${naming.ClassName}DomainService
|
|
712
|
+
|
|
713
|
+
beforeEach(() => {
|
|
714
|
+
${naming.variableName}DomainService = ${withWiring ? `wire${naming.ClassName}DomainService()` : `new ${naming.ClassName}DomainService()`}
|
|
715
|
+
})
|
|
716
|
+
|
|
717
|
+
describe('${naming.withSpaces}', () => {
|
|
718
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
719
|
+
expect(${naming.variableName}DomainService).toBeDefined()
|
|
720
|
+
})
|
|
721
|
+
})
|
|
722
|
+
`.trim());
|
|
723
|
+
if (withWiring) boundedContextFolder.subitem([
|
|
651
724
|
'wiring',
|
|
652
|
-
'domain'
|
|
653
|
-
|
|
654
|
-
]).createFile(`wire-${naming.fileName}-service.ts`, `
|
|
725
|
+
'domain-services'
|
|
726
|
+
]).createFile(`wire-${naming.fileName}-domain-service.ts`, `
|
|
655
727
|
import { wireClass } from '@domain-first/wire'
|
|
656
|
-
import { ${naming.ClassName}
|
|
728
|
+
import { ${naming.ClassName}DomainService } from '../../domain/services/${naming.fileName}-domain-service'
|
|
657
729
|
|
|
658
|
-
export const wire${naming.ClassName}
|
|
659
|
-
${naming.ClassName}
|
|
730
|
+
export const wire${naming.ClassName}DomainService = wireClass(
|
|
731
|
+
${naming.ClassName}DomainService,
|
|
660
732
|
[]
|
|
661
733
|
)
|
|
662
734
|
`.trim());
|
|
@@ -801,6 +873,106 @@ export const wire${endpointNaming.ClassName} = wireClass(
|
|
|
801
873
|
wiringFolder.file('index.ts').addLine(`export * from './${wireNaming.fileName}'`);
|
|
802
874
|
}
|
|
803
875
|
};
|
|
876
|
+
const scaffoldNewInfrastructureService = async (boundedContextFolder)=>{
|
|
877
|
+
const name = await (0, prompts_namespaceObject.input)({
|
|
878
|
+
message: 'Name: '
|
|
879
|
+
});
|
|
880
|
+
const naming = new UnknownFormatNaming(name);
|
|
881
|
+
let scaffoldUnitTests;
|
|
882
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await (0, prompts_namespaceObject.confirm)({
|
|
883
|
+
message: 'Scaffold unit-tests'
|
|
884
|
+
});
|
|
885
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
886
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
887
|
+
'wiring',
|
|
888
|
+
'infrastructure-services'
|
|
889
|
+
]);
|
|
890
|
+
const infrastructureServicesFolder = boundedContextFolder.subitem([
|
|
891
|
+
'infrastructure',
|
|
892
|
+
'services'
|
|
893
|
+
]);
|
|
894
|
+
infrastructureServicesFolder.createFile(`${naming.fileName}-infrastructure-service.ts`, `
|
|
895
|
+
export class ${naming.ClassName}InfrastructureService {
|
|
896
|
+
|
|
897
|
+
}
|
|
898
|
+
`.trim());
|
|
899
|
+
if (scaffoldUnitTests) infrastructureServicesFolder.createFile(`${naming.fileName}-infrastructure-service.spec.ts`, `
|
|
900
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
901
|
+
${withWiring ? `import { type ${naming.ClassName}InfrastructureService } from './${naming.fileName}-infrastructure-service'
|
|
902
|
+
import { wire${naming.ClassName}InfrastructureService } from '../../wiring/infrastructure-services/wire-${naming.fileName}-infrastructure-service'` : `import { ${naming.ClassName}InfrastructureService } from './${naming.fileName}-infrastructure-service'`}
|
|
903
|
+
|
|
904
|
+
let ${naming.variableName}InfrastructureService: ${naming.ClassName}InfrastructureService
|
|
905
|
+
|
|
906
|
+
beforeEach(() => {
|
|
907
|
+
${naming.variableName}InfrastructureService = ${withWiring ? `wire${naming.ClassName}InfrastructureService()` : `new ${naming.ClassName}InfrastructureService()`}
|
|
908
|
+
})
|
|
909
|
+
|
|
910
|
+
describe('${naming.withSpaces}', () => {
|
|
911
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
912
|
+
expect(${naming.variableName}InfrastructureService).toBeDefined()
|
|
913
|
+
})
|
|
914
|
+
})
|
|
915
|
+
`.trim());
|
|
916
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-infrastructure-service.ts`, `
|
|
917
|
+
import { wireClass } from '@domain-first/wire'
|
|
918
|
+
import { ${naming.ClassName}InfrastructureService } from '../../infrastructure/services/${naming.fileName}-infrastructure-service'
|
|
919
|
+
|
|
920
|
+
export const wire${naming.ClassName}InfrastructureService = wireClass(
|
|
921
|
+
${naming.ClassName}InfrastructureService,
|
|
922
|
+
[]
|
|
923
|
+
)
|
|
924
|
+
`.trim());
|
|
925
|
+
};
|
|
926
|
+
const scaffoldNewPresentationService = async (boundedContextFolder)=>{
|
|
927
|
+
const name = await (0, prompts_namespaceObject.input)({
|
|
928
|
+
message: 'Name: '
|
|
929
|
+
});
|
|
930
|
+
const naming = new UnknownFormatNaming(name);
|
|
931
|
+
let scaffoldUnitTests;
|
|
932
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await (0, prompts_namespaceObject.confirm)({
|
|
933
|
+
message: 'Scaffold unit-tests'
|
|
934
|
+
});
|
|
935
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
936
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
937
|
+
'wiring',
|
|
938
|
+
'presentation-services'
|
|
939
|
+
]);
|
|
940
|
+
const presentationServicesFolder = boundedContextFolder.subitem([
|
|
941
|
+
'presentation',
|
|
942
|
+
'services'
|
|
943
|
+
]);
|
|
944
|
+
presentationServicesFolder.createFile(`${naming.fileName}-presentation-service.ts`, `
|
|
945
|
+
export class ${naming.ClassName}PresentationService {
|
|
946
|
+
|
|
947
|
+
}
|
|
948
|
+
`.trim());
|
|
949
|
+
if (scaffoldUnitTests) presentationServicesFolder.createFile(`${naming.fileName}-presentation-service.spec.ts`, `
|
|
950
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
951
|
+
${withWiring ? `import { type ${naming.ClassName}PresentationService } from './${naming.fileName}-presentation-service'
|
|
952
|
+
import { wire${naming.ClassName}PresentationService } from '../../wiring/presentation-services/wire-${naming.fileName}-presentation-service'` : `import { ${naming.ClassName}PresentationService } from './${naming.fileName}-presentation-service'`}
|
|
953
|
+
|
|
954
|
+
let ${naming.variableName}PresentationService: ${naming.ClassName}PresentationService
|
|
955
|
+
|
|
956
|
+
beforeEach(() => {
|
|
957
|
+
${naming.variableName}PresentationService = ${withWiring ? `wire${naming.ClassName}PresentationService()` : `new ${naming.ClassName}PresentationService()`}
|
|
958
|
+
})
|
|
959
|
+
|
|
960
|
+
describe('${naming.withSpaces}', () => {
|
|
961
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
962
|
+
expect(${naming.variableName}PresentationService).toBeDefined()
|
|
963
|
+
})
|
|
964
|
+
})
|
|
965
|
+
`.trim());
|
|
966
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-presentation-service.ts`, `
|
|
967
|
+
import { wireClass } from '@domain-first/wire'
|
|
968
|
+
import { ${naming.ClassName}PresentationService } from '../../presentation/services/${naming.fileName}-presentation-service'
|
|
969
|
+
|
|
970
|
+
export const wire${naming.ClassName}PresentationService = wireClass(
|
|
971
|
+
${naming.ClassName}PresentationService,
|
|
972
|
+
[]
|
|
973
|
+
)
|
|
974
|
+
`.trim());
|
|
975
|
+
};
|
|
804
976
|
const scaffoldNewQuery = async (boundedContextFolder)=>{
|
|
805
977
|
const queriesFolder = boundedContextFolder.subitem([
|
|
806
978
|
'application',
|
|
@@ -975,6 +1147,11 @@ const scaffoldNewUseCase = async (boundedContextFolder)=>{
|
|
|
975
1147
|
message: 'Name: '
|
|
976
1148
|
});
|
|
977
1149
|
const naming = new UnknownFormatNaming(useCaseName);
|
|
1150
|
+
let scaffoldUnitTests;
|
|
1151
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await (0, prompts_namespaceObject.confirm)({
|
|
1152
|
+
message: 'Scaffold unit-tests'
|
|
1153
|
+
});
|
|
1154
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
978
1155
|
useCasesFolder.createFile(`${naming.fileName}-use-case.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers') ? `
|
|
979
1156
|
import { defineHandler } from '@domain-first/handlers'
|
|
980
1157
|
|
|
@@ -997,7 +1174,24 @@ export class ${naming.ClassName}UseCase {
|
|
|
997
1174
|
|
|
998
1175
|
}
|
|
999
1176
|
`.trim());
|
|
1000
|
-
if (
|
|
1177
|
+
if (scaffoldUnitTests) useCasesFolder.createFile(`${naming.fileName}-use-case.spec.ts`, `
|
|
1178
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
1179
|
+
${withWiring ? `import { type ${naming.ClassName}UseCase } from './${naming.fileName}-use-case'
|
|
1180
|
+
import { wire${naming.ClassName}UseCase } from '../../wiring/use-cases/wire-${naming.fileName}-use-case'` : `import { ${naming.ClassName}UseCase } from './${naming.fileName}-use-case'`}
|
|
1181
|
+
|
|
1182
|
+
let ${naming.variableName}UseCase: ${naming.ClassName}UseCase
|
|
1183
|
+
|
|
1184
|
+
beforeEach(() => {
|
|
1185
|
+
${naming.variableName}UseCase = ${withWiring ? `wire${naming.ClassName}UseCase()` : `new ${naming.ClassName}UseCase()`}
|
|
1186
|
+
})
|
|
1187
|
+
|
|
1188
|
+
describe('${naming.withSpaces} use case', () => {
|
|
1189
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
1190
|
+
expect(${naming.variableName}UseCase).toBeDefined()
|
|
1191
|
+
})
|
|
1192
|
+
})
|
|
1193
|
+
`.trim());
|
|
1194
|
+
if (withWiring) boundedContextFolder.subitem([
|
|
1001
1195
|
'wiring',
|
|
1002
1196
|
'use-cases'
|
|
1003
1197
|
]).createFile(`wire-${naming.fileName}-use-case.ts`, `
|
|
@@ -1201,13 +1395,11 @@ const main = async ()=>{
|
|
|
1201
1395
|
]);
|
|
1202
1396
|
const layer = await (0, prompts_namespaceObject.select)({
|
|
1203
1397
|
message: 'Layer:',
|
|
1204
|
-
choices:
|
|
1398
|
+
choices: [
|
|
1205
1399
|
'Domain',
|
|
1206
1400
|
'Application',
|
|
1401
|
+
'Infrastructure',
|
|
1207
1402
|
'Presentation'
|
|
1208
|
-
] : [
|
|
1209
|
-
'Domain',
|
|
1210
|
-
'Application'
|
|
1211
1403
|
]
|
|
1212
1404
|
});
|
|
1213
1405
|
if ('Domain' === layer) {
|
|
@@ -1237,9 +1429,11 @@ const main = async ()=>{
|
|
|
1237
1429
|
'New Query',
|
|
1238
1430
|
'New Command',
|
|
1239
1431
|
'New Use Case',
|
|
1240
|
-
'New Port'
|
|
1432
|
+
'New Port',
|
|
1433
|
+
'New Service'
|
|
1241
1434
|
] : [
|
|
1242
|
-
'New Port'
|
|
1435
|
+
'New Port',
|
|
1436
|
+
'New Service'
|
|
1243
1437
|
]
|
|
1244
1438
|
});
|
|
1245
1439
|
switch(operation){
|
|
@@ -1251,20 +1445,37 @@ const main = async ()=>{
|
|
|
1251
1445
|
return void await scaffoldNewUseCase(boundedContextFolder);
|
|
1252
1446
|
case 'New Port':
|
|
1253
1447
|
return void await scaffoldNewApplicationPort(boundedContextFolder);
|
|
1448
|
+
case 'New Service':
|
|
1449
|
+
return void await scaffoldNewApplicationService(boundedContextFolder);
|
|
1450
|
+
}
|
|
1451
|
+
}
|
|
1452
|
+
if ('Infrastructure' === layer) {
|
|
1453
|
+
const operation = await (0, prompts_namespaceObject.select)({
|
|
1454
|
+
message: 'Infrastructure layer operation:',
|
|
1455
|
+
choices: [
|
|
1456
|
+
'New Service'
|
|
1457
|
+
]
|
|
1458
|
+
});
|
|
1459
|
+
switch(operation){
|
|
1460
|
+
case 'New Service':
|
|
1461
|
+
return void await scaffoldNewInfrastructureService(boundedContextFolder);
|
|
1254
1462
|
}
|
|
1255
1463
|
}
|
|
1256
1464
|
if ('Presentation' === layer) {
|
|
1257
1465
|
const action = await (0, prompts_namespaceObject.select)({
|
|
1258
1466
|
message: 'Presentation layer operation:',
|
|
1259
1467
|
choices: specificContext ? [
|
|
1260
|
-
ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers-rest') ? 'New Handlers-REST Endpoint' : void 0
|
|
1468
|
+
ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers-rest') ? 'New Handlers-REST Endpoint' : void 0,
|
|
1469
|
+
'New Service'
|
|
1261
1470
|
].filter((x)=>x) : [
|
|
1262
|
-
''
|
|
1471
|
+
'New Service'
|
|
1263
1472
|
]
|
|
1264
1473
|
});
|
|
1265
1474
|
switch(action){
|
|
1266
1475
|
case 'New Handlers-REST Endpoint':
|
|
1267
1476
|
return void await scaffoldNewHandlersRestEndpoint(boundedContextFolder);
|
|
1477
|
+
case 'New Service':
|
|
1478
|
+
return void await scaffoldNewPresentationService(boundedContextFolder);
|
|
1268
1479
|
}
|
|
1269
1480
|
}
|
|
1270
1481
|
return;
|
package/dist/index.js
CHANGED
|
@@ -417,6 +417,56 @@ export const wire${naming.ClassName} = envBranchedWire({
|
|
|
417
417
|
|
|
418
418
|
`.trim());
|
|
419
419
|
};
|
|
420
|
+
const scaffoldNewApplicationService = async (boundedContextFolder)=>{
|
|
421
|
+
const name = await input({
|
|
422
|
+
message: 'Name: '
|
|
423
|
+
});
|
|
424
|
+
const naming = new UnknownFormatNaming(name);
|
|
425
|
+
let scaffoldUnitTests;
|
|
426
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await prompts_confirm({
|
|
427
|
+
message: 'Scaffold unit-tests'
|
|
428
|
+
});
|
|
429
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
430
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
431
|
+
'wiring',
|
|
432
|
+
'application-services'
|
|
433
|
+
]);
|
|
434
|
+
const applicationServicesFolder = boundedContextFolder.subitem([
|
|
435
|
+
'application',
|
|
436
|
+
'services'
|
|
437
|
+
]);
|
|
438
|
+
applicationServicesFolder.createFile(`${naming.fileName}-application-service.ts`, `
|
|
439
|
+
export class ${naming.ClassName}ApplicationService {
|
|
440
|
+
|
|
441
|
+
}
|
|
442
|
+
`.trim());
|
|
443
|
+
if (scaffoldUnitTests) applicationServicesFolder.createFile(`${naming.fileName}-application-service.spec.ts`, `
|
|
444
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
445
|
+
${withWiring ? `import { type ${naming.ClassName}ApplicationService } from './${naming.fileName}-application-service'
|
|
446
|
+
import { wire${naming.ClassName}ApplicationService } from '../../wiring/application-services/wire-${naming.fileName}-application-service'` : `import { ${naming.ClassName}ApplicationService } from './${naming.fileName}-application-service'`}
|
|
447
|
+
|
|
448
|
+
let ${naming.variableName}ApplicationService: ${naming.ClassName}ApplicationService
|
|
449
|
+
|
|
450
|
+
beforeEach(() => {
|
|
451
|
+
${naming.variableName}ApplicationService = ${withWiring ? `wire${naming.ClassName}ApplicationService()` : `new ${naming.ClassName}ApplicationService()`}
|
|
452
|
+
})
|
|
453
|
+
|
|
454
|
+
describe('${naming.withSpaces}', () => {
|
|
455
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
456
|
+
expect(${naming.variableName}ApplicationService).toBeDefined()
|
|
457
|
+
})
|
|
458
|
+
})
|
|
459
|
+
`.trim());
|
|
460
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-application-service.ts`, `
|
|
461
|
+
import { wireClass } from '@domain-first/wire'
|
|
462
|
+
import { ${naming.ClassName}ApplicationService } from '../../application/services/${naming.fileName}-application-service'
|
|
463
|
+
|
|
464
|
+
export const wire${naming.ClassName}ApplicationService = wireClass(
|
|
465
|
+
${naming.ClassName}ApplicationService,
|
|
466
|
+
[]
|
|
467
|
+
)
|
|
468
|
+
`.trim());
|
|
469
|
+
};
|
|
420
470
|
const scaffoldNewBoundedContextUseCase = async ()=>{
|
|
421
471
|
const name = await input({
|
|
422
472
|
message: 'Name:',
|
|
@@ -609,24 +659,46 @@ const scaffoldNewDomainService = async (boundedContextFolder)=>{
|
|
|
609
659
|
message: 'Name:'
|
|
610
660
|
});
|
|
611
661
|
const naming = new UnknownFormatNaming(portName);
|
|
612
|
-
|
|
662
|
+
let scaffoldUnitTests;
|
|
663
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await prompts_confirm({
|
|
664
|
+
message: 'Scaffold unit-tests'
|
|
665
|
+
});
|
|
666
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
667
|
+
const domainServicesFolder = boundedContextFolder.subitem([
|
|
613
668
|
'domain',
|
|
614
669
|
'services'
|
|
615
|
-
])
|
|
616
|
-
|
|
670
|
+
]);
|
|
671
|
+
domainServicesFolder.createFile(`${naming.fileName}-domain-service.ts`, `
|
|
672
|
+
export class ${naming.ClassName}DomainService {
|
|
617
673
|
|
|
618
674
|
}
|
|
619
675
|
`.trim());
|
|
620
|
-
if (
|
|
676
|
+
if (scaffoldUnitTests) domainServicesFolder.createFile(`${naming.fileName}-domain-service.spec.ts`, `
|
|
677
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
678
|
+
${withWiring ? `import { type ${naming.ClassName}DomainService } from './${naming.fileName}-domain-service'
|
|
679
|
+
import { wire${naming.ClassName}DomainService } from '../../wiring/domain-services/wire-${naming.fileName}-domain-service'` : `import { ${naming.ClassName}DomainService } from './${naming.fileName}-domain-service'`}
|
|
680
|
+
|
|
681
|
+
let ${naming.variableName}DomainService: ${naming.ClassName}DomainService
|
|
682
|
+
|
|
683
|
+
beforeEach(() => {
|
|
684
|
+
${naming.variableName}DomainService = ${withWiring ? `wire${naming.ClassName}DomainService()` : `new ${naming.ClassName}DomainService()`}
|
|
685
|
+
})
|
|
686
|
+
|
|
687
|
+
describe('${naming.withSpaces}', () => {
|
|
688
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
689
|
+
expect(${naming.variableName}DomainService).toBeDefined()
|
|
690
|
+
})
|
|
691
|
+
})
|
|
692
|
+
`.trim());
|
|
693
|
+
if (withWiring) boundedContextFolder.subitem([
|
|
621
694
|
'wiring',
|
|
622
|
-
'domain'
|
|
623
|
-
|
|
624
|
-
]).createFile(`wire-${naming.fileName}-service.ts`, `
|
|
695
|
+
'domain-services'
|
|
696
|
+
]).createFile(`wire-${naming.fileName}-domain-service.ts`, `
|
|
625
697
|
import { wireClass } from '@domain-first/wire'
|
|
626
|
-
import { ${naming.ClassName}
|
|
698
|
+
import { ${naming.ClassName}DomainService } from '../../domain/services/${naming.fileName}-domain-service'
|
|
627
699
|
|
|
628
|
-
export const wire${naming.ClassName}
|
|
629
|
-
${naming.ClassName}
|
|
700
|
+
export const wire${naming.ClassName}DomainService = wireClass(
|
|
701
|
+
${naming.ClassName}DomainService,
|
|
630
702
|
[]
|
|
631
703
|
)
|
|
632
704
|
`.trim());
|
|
@@ -771,6 +843,106 @@ export const wire${endpointNaming.ClassName} = wireClass(
|
|
|
771
843
|
wiringFolder.file('index.ts').addLine(`export * from './${wireNaming.fileName}'`);
|
|
772
844
|
}
|
|
773
845
|
};
|
|
846
|
+
const scaffoldNewInfrastructureService = async (boundedContextFolder)=>{
|
|
847
|
+
const name = await input({
|
|
848
|
+
message: 'Name: '
|
|
849
|
+
});
|
|
850
|
+
const naming = new UnknownFormatNaming(name);
|
|
851
|
+
let scaffoldUnitTests;
|
|
852
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await prompts_confirm({
|
|
853
|
+
message: 'Scaffold unit-tests'
|
|
854
|
+
});
|
|
855
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
856
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
857
|
+
'wiring',
|
|
858
|
+
'infrastructure-services'
|
|
859
|
+
]);
|
|
860
|
+
const infrastructureServicesFolder = boundedContextFolder.subitem([
|
|
861
|
+
'infrastructure',
|
|
862
|
+
'services'
|
|
863
|
+
]);
|
|
864
|
+
infrastructureServicesFolder.createFile(`${naming.fileName}-infrastructure-service.ts`, `
|
|
865
|
+
export class ${naming.ClassName}InfrastructureService {
|
|
866
|
+
|
|
867
|
+
}
|
|
868
|
+
`.trim());
|
|
869
|
+
if (scaffoldUnitTests) infrastructureServicesFolder.createFile(`${naming.fileName}-infrastructure-service.spec.ts`, `
|
|
870
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
871
|
+
${withWiring ? `import { type ${naming.ClassName}InfrastructureService } from './${naming.fileName}-infrastructure-service'
|
|
872
|
+
import { wire${naming.ClassName}InfrastructureService } from '../../wiring/infrastructure-services/wire-${naming.fileName}-infrastructure-service'` : `import { ${naming.ClassName}InfrastructureService } from './${naming.fileName}-infrastructure-service'`}
|
|
873
|
+
|
|
874
|
+
let ${naming.variableName}InfrastructureService: ${naming.ClassName}InfrastructureService
|
|
875
|
+
|
|
876
|
+
beforeEach(() => {
|
|
877
|
+
${naming.variableName}InfrastructureService = ${withWiring ? `wire${naming.ClassName}InfrastructureService()` : `new ${naming.ClassName}InfrastructureService()`}
|
|
878
|
+
})
|
|
879
|
+
|
|
880
|
+
describe('${naming.withSpaces}', () => {
|
|
881
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
882
|
+
expect(${naming.variableName}InfrastructureService).toBeDefined()
|
|
883
|
+
})
|
|
884
|
+
})
|
|
885
|
+
`.trim());
|
|
886
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-infrastructure-service.ts`, `
|
|
887
|
+
import { wireClass } from '@domain-first/wire'
|
|
888
|
+
import { ${naming.ClassName}InfrastructureService } from '../../infrastructure/services/${naming.fileName}-infrastructure-service'
|
|
889
|
+
|
|
890
|
+
export const wire${naming.ClassName}InfrastructureService = wireClass(
|
|
891
|
+
${naming.ClassName}InfrastructureService,
|
|
892
|
+
[]
|
|
893
|
+
)
|
|
894
|
+
`.trim());
|
|
895
|
+
};
|
|
896
|
+
const scaffoldNewPresentationService = async (boundedContextFolder)=>{
|
|
897
|
+
const name = await input({
|
|
898
|
+
message: 'Name: '
|
|
899
|
+
});
|
|
900
|
+
const naming = new UnknownFormatNaming(name);
|
|
901
|
+
let scaffoldUnitTests;
|
|
902
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await prompts_confirm({
|
|
903
|
+
message: 'Scaffold unit-tests'
|
|
904
|
+
});
|
|
905
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
906
|
+
const wiringFolder = boundedContextFolder.subitem([
|
|
907
|
+
'wiring',
|
|
908
|
+
'presentation-services'
|
|
909
|
+
]);
|
|
910
|
+
const presentationServicesFolder = boundedContextFolder.subitem([
|
|
911
|
+
'presentation',
|
|
912
|
+
'services'
|
|
913
|
+
]);
|
|
914
|
+
presentationServicesFolder.createFile(`${naming.fileName}-presentation-service.ts`, `
|
|
915
|
+
export class ${naming.ClassName}PresentationService {
|
|
916
|
+
|
|
917
|
+
}
|
|
918
|
+
`.trim());
|
|
919
|
+
if (scaffoldUnitTests) presentationServicesFolder.createFile(`${naming.fileName}-presentation-service.spec.ts`, `
|
|
920
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
921
|
+
${withWiring ? `import { type ${naming.ClassName}PresentationService } from './${naming.fileName}-presentation-service'
|
|
922
|
+
import { wire${naming.ClassName}PresentationService } from '../../wiring/presentation-services/wire-${naming.fileName}-presentation-service'` : `import { ${naming.ClassName}PresentationService } from './${naming.fileName}-presentation-service'`}
|
|
923
|
+
|
|
924
|
+
let ${naming.variableName}PresentationService: ${naming.ClassName}PresentationService
|
|
925
|
+
|
|
926
|
+
beforeEach(() => {
|
|
927
|
+
${naming.variableName}PresentationService = ${withWiring ? `wire${naming.ClassName}PresentationService()` : `new ${naming.ClassName}PresentationService()`}
|
|
928
|
+
})
|
|
929
|
+
|
|
930
|
+
describe('${naming.withSpaces}', () => {
|
|
931
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
932
|
+
expect(${naming.variableName}PresentationService).toBeDefined()
|
|
933
|
+
})
|
|
934
|
+
})
|
|
935
|
+
`.trim());
|
|
936
|
+
if (withWiring) wiringFolder.createFile(`wire-${naming.fileName}-presentation-service.ts`, `
|
|
937
|
+
import { wireClass } from '@domain-first/wire'
|
|
938
|
+
import { ${naming.ClassName}PresentationService } from '../../presentation/services/${naming.fileName}-presentation-service'
|
|
939
|
+
|
|
940
|
+
export const wire${naming.ClassName}PresentationService = wireClass(
|
|
941
|
+
${naming.ClassName}PresentationService,
|
|
942
|
+
[]
|
|
943
|
+
)
|
|
944
|
+
`.trim());
|
|
945
|
+
};
|
|
774
946
|
const scaffoldNewQuery = async (boundedContextFolder)=>{
|
|
775
947
|
const queriesFolder = boundedContextFolder.subitem([
|
|
776
948
|
'application',
|
|
@@ -945,6 +1117,11 @@ const scaffoldNewUseCase = async (boundedContextFolder)=>{
|
|
|
945
1117
|
message: 'Name: '
|
|
946
1118
|
});
|
|
947
1119
|
const naming = new UnknownFormatNaming(useCaseName);
|
|
1120
|
+
let scaffoldUnitTests;
|
|
1121
|
+
if (ConfigFile.Instance.data.testingLibrary) scaffoldUnitTests = await prompts_confirm({
|
|
1122
|
+
message: 'Scaffold unit-tests'
|
|
1123
|
+
});
|
|
1124
|
+
const withWiring = ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/wire');
|
|
948
1125
|
useCasesFolder.createFile(`${naming.fileName}-use-case.ts`, ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers') ? `
|
|
949
1126
|
import { defineHandler } from '@domain-first/handlers'
|
|
950
1127
|
|
|
@@ -967,7 +1144,24 @@ export class ${naming.ClassName}UseCase {
|
|
|
967
1144
|
|
|
968
1145
|
}
|
|
969
1146
|
`.trim());
|
|
970
|
-
if (
|
|
1147
|
+
if (scaffoldUnitTests) useCasesFolder.createFile(`${naming.fileName}-use-case.spec.ts`, `
|
|
1148
|
+
import { describe, test, expect, beforeEach } from '${ConfigFile.Instance.data.testingLibrary}'
|
|
1149
|
+
${withWiring ? `import { type ${naming.ClassName}UseCase } from './${naming.fileName}-use-case'
|
|
1150
|
+
import { wire${naming.ClassName}UseCase } from '../../wiring/use-cases/wire-${naming.fileName}-use-case'` : `import { ${naming.ClassName}UseCase } from './${naming.fileName}-use-case'`}
|
|
1151
|
+
|
|
1152
|
+
let ${naming.variableName}UseCase: ${naming.ClassName}UseCase
|
|
1153
|
+
|
|
1154
|
+
beforeEach(() => {
|
|
1155
|
+
${naming.variableName}UseCase = ${withWiring ? `wire${naming.ClassName}UseCase()` : `new ${naming.ClassName}UseCase()`}
|
|
1156
|
+
})
|
|
1157
|
+
|
|
1158
|
+
describe('${naming.withSpaces} use case', () => {
|
|
1159
|
+
test('can be ${withWiring ? 'wired' : 'created'}', () => {
|
|
1160
|
+
expect(${naming.variableName}UseCase).toBeDefined()
|
|
1161
|
+
})
|
|
1162
|
+
})
|
|
1163
|
+
`.trim());
|
|
1164
|
+
if (withWiring) boundedContextFolder.subitem([
|
|
971
1165
|
'wiring',
|
|
972
1166
|
'use-cases'
|
|
973
1167
|
]).createFile(`wire-${naming.fileName}-use-case.ts`, `
|
|
@@ -1171,13 +1365,11 @@ const main = async ()=>{
|
|
|
1171
1365
|
]);
|
|
1172
1366
|
const layer = await prompts_select({
|
|
1173
1367
|
message: 'Layer:',
|
|
1174
|
-
choices:
|
|
1368
|
+
choices: [
|
|
1175
1369
|
'Domain',
|
|
1176
1370
|
'Application',
|
|
1371
|
+
'Infrastructure',
|
|
1177
1372
|
'Presentation'
|
|
1178
|
-
] : [
|
|
1179
|
-
'Domain',
|
|
1180
|
-
'Application'
|
|
1181
1373
|
]
|
|
1182
1374
|
});
|
|
1183
1375
|
if ('Domain' === layer) {
|
|
@@ -1207,9 +1399,11 @@ const main = async ()=>{
|
|
|
1207
1399
|
'New Query',
|
|
1208
1400
|
'New Command',
|
|
1209
1401
|
'New Use Case',
|
|
1210
|
-
'New Port'
|
|
1402
|
+
'New Port',
|
|
1403
|
+
'New Service'
|
|
1211
1404
|
] : [
|
|
1212
|
-
'New Port'
|
|
1405
|
+
'New Port',
|
|
1406
|
+
'New Service'
|
|
1213
1407
|
]
|
|
1214
1408
|
});
|
|
1215
1409
|
switch(operation){
|
|
@@ -1221,20 +1415,37 @@ const main = async ()=>{
|
|
|
1221
1415
|
return void await scaffoldNewUseCase(boundedContextFolder);
|
|
1222
1416
|
case 'New Port':
|
|
1223
1417
|
return void await scaffoldNewApplicationPort(boundedContextFolder);
|
|
1418
|
+
case 'New Service':
|
|
1419
|
+
return void await scaffoldNewApplicationService(boundedContextFolder);
|
|
1420
|
+
}
|
|
1421
|
+
}
|
|
1422
|
+
if ('Infrastructure' === layer) {
|
|
1423
|
+
const operation = await prompts_select({
|
|
1424
|
+
message: 'Infrastructure layer operation:',
|
|
1425
|
+
choices: [
|
|
1426
|
+
'New Service'
|
|
1427
|
+
]
|
|
1428
|
+
});
|
|
1429
|
+
switch(operation){
|
|
1430
|
+
case 'New Service':
|
|
1431
|
+
return void await scaffoldNewInfrastructureService(boundedContextFolder);
|
|
1224
1432
|
}
|
|
1225
1433
|
}
|
|
1226
1434
|
if ('Presentation' === layer) {
|
|
1227
1435
|
const action = await prompts_select({
|
|
1228
1436
|
message: 'Presentation layer operation:',
|
|
1229
1437
|
choices: specificContext ? [
|
|
1230
|
-
ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers-rest') ? 'New Handlers-REST Endpoint' : void 0
|
|
1438
|
+
ConfigFile.Instance.data.domainFirstPackages.includes('@domain-first/handlers-rest') ? 'New Handlers-REST Endpoint' : void 0,
|
|
1439
|
+
'New Service'
|
|
1231
1440
|
].filter((x)=>x) : [
|
|
1232
|
-
''
|
|
1441
|
+
'New Service'
|
|
1233
1442
|
]
|
|
1234
1443
|
});
|
|
1235
1444
|
switch(action){
|
|
1236
1445
|
case 'New Handlers-REST Endpoint':
|
|
1237
1446
|
return void await scaffoldNewHandlersRestEndpoint(boundedContextFolder);
|
|
1447
|
+
case 'New Service':
|
|
1448
|
+
return void await scaffoldNewPresentationService(boundedContextFolder);
|
|
1238
1449
|
}
|
|
1239
1450
|
}
|
|
1240
1451
|
return;
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
export * from './initialize';
|
|
2
2
|
export * from './scaffold-new-aggregate';
|
|
3
3
|
export * from './scaffold-new-application-port';
|
|
4
|
+
export * from './scaffold-new-application-service';
|
|
4
5
|
export * from './scaffold-new-bounded-context';
|
|
5
6
|
export * from './scaffold-new-command';
|
|
6
7
|
export * from './scaffold-new-domain-service';
|
|
7
8
|
export * from './scaffold-new-error';
|
|
8
9
|
export * from './scaffold-new-handlers-rest-endpoint';
|
|
10
|
+
export * from './scaffold-new-infrastructure-service';
|
|
11
|
+
export * from './scaffold-new-presentation-service';
|
|
9
12
|
export * from './scaffold-new-query';
|
|
10
13
|
export * from './scaffold-new-use-case';
|
|
11
14
|
export * from './scaffold-react-page';
|