@nikovirtala/projen-typedoc 0.0.0

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.
@@ -0,0 +1,320 @@
1
+ # Business domain
2
+
3
+ We operate in the retail business, offering services across:
4
+
5
+ - supermarket trade
6
+ - department store trade
7
+ - specialty store trade
8
+
9
+ All code examples, domain modeling, and ubiquitous language should reflect retail business concepts (products, assortments, customers, orders, pricing, promotions, etc.).
10
+
11
+ # Design philosophy
12
+
13
+ - Favor simplicity over cleverness. Use the simplest solution that solves the core problem. Avoid premature optimization and unnecessary abstractions.
14
+ - Apply Domain-Driven Design with Hexagonal Architecture. Organize code by bounded contexts with a clear separation between domain logic (core), application services (ports), and infrastructure adapters. The domain layer must not depend on external concerns.
15
+
16
+ # Domain-Driven Design and Hexagonal Architecture
17
+
18
+ - Structure code by bounded contexts. Each context is independently deployable with its own domain model, application layer, and adapters.
19
+ - Keep domain logic pure. Domain entities, value objects, and aggregates must not import infrastructure code (AWS SDK, database clients, external APIs).
20
+ - Define ports as interfaces in the application layer. Implement adapters in the infrastructure layer (DynamoDB repositories, event publishers, Lambda function handlers).
21
+ - Use ubiquitous language from the business domain in all code, tests, and documentation. Avoid technical jargon in domain layer naming.
22
+ - Communicate between bounded contexts exclusively through domain events. Never share domain models or databases across contexts.
23
+ - Use Anti-Corruption Layer (ACL) adapters when integrating with external systems or legacy services. The ACL translates external models and protocols into your bounded context's domain language, preventing external concepts from polluting your domain model.
24
+ - Cache raw external data before ACL transformation. Store the original payload from external systems (APIs, message queues, webhooks) in DynamoDB with a TTL before processing through the ACL. This enables replay and reprocessing if bugs are discovered in the ACL or business logic, without depending on external system retention policies or availability. Use key pattern: `pk: "RAW#<source>#<id>"`, `sk: "RAW#<timestamp>"`. For financial data, set TTL to 6 years to meet regulatory retention requirements. Use DynamoDB Standard-IA storage class for raw data to optimize costs.
25
+
26
+ # Architecture
27
+
28
+ - Default to event-driven architecture. Use events for bounded context communication.
29
+ - Use AWS serverless architecture (Lambda, API Gateway, DynamoDB, EventBridge, etc.).
30
+ - Use AWS CDK (TypeScript) for infrastructure as code.
31
+ - Kafka is hosted on Confluent Cloud.
32
+
33
+ # Project structure
34
+
35
+ Each bounded context is a separate git repository organized by hexagonal layers under `src/`:
36
+
37
+ - `src/domain/` - Entities, value objects, aggregates, domain events (pure TypeScript, no external dependencies)
38
+ - `src/application/` - Use cases and port interfaces (depends only on domain)
39
+ - `src/infrastructure/` - Adapters: repositories, event publishers, Lambda function handlers, Zod schemas (implements ports)
40
+ - `src/stacks/` - CDK stack definitions
41
+ - `src/constructs/` - Reusable CDK constructs
42
+ - `src/` - CDK app entry point
43
+
44
+ # Documents
45
+
46
+ - Write all documents in Markdown format.
47
+ - Use sentence case for all document headings (capitalize only the first word and proper nouns).
48
+ - Design documents must include relevant Mermaid visualizations: logical architecture diagrams, process flowcharts, ER models, or sequence diagrams.
49
+
50
+ # Code documentation
51
+
52
+ - Use TSDoc for all code documentation. Include appropriate TSDoc tags (@param, @returns, @throws, @example, @deprecated, etc.) to fully describe the API contract.
53
+ - Document all interface properties and enum members with TSDoc comments. Every property and enum value must have a description explaining its purpose and constraints.
54
+ - Include reference links to AWS service documentation when documenting code that uses AWS services. Use @see tag with the relevant docs.aws.amazon.com URL.
55
+ - Add extensive inline documentation throughout business logic and utility code using block comments (`/* */`) to document:
56
+ - The "what": What the code is doing and why it exists
57
+ - The "why": Business rules, assumptions, constraints, and reasoning behind implementation decisions
58
+ - Focus on intent and context, not just mechanics
59
+ - Avoid single-line (`//`) comments for documentation.
60
+
61
+ # Coding standards
62
+
63
+ - Use latest TypeScript and Node.js LTS versions.
64
+ - Use TypeScript with ES modules.
65
+ - Use descriptive names; avoid single-letter variables except in loops.
66
+ - Do not expose implementation details in business-facing names. Use domain language for entities, events, use cases, AWS resources, and API endpoints. Reserve technical terms (like "bitemporal", "CQRS", "event-sourced") for infrastructure code, internal documentation, and reusable generic components.
67
+ - Do not use any type. Use specific types or unknown instead.
68
+ - Avoid type assertions (`as` keyword). Use Zod for runtime validation and type inference when handling external data. Use type guards for narrowing types when runtime checks are needed.
69
+ - Use Zod schemas in infrastructure adapters for runtime validation of external data (API requests, database records, event payloads). Keep domain types as pure TypeScript interfaces without runtime validation dependencies.
70
+ - Use JSON format for all logging output.
71
+ - Avoid `forEach()`. Use `for...of` for iteration with async/await or control flow, or use functional methods like `map()`, `filter()`, and `reduce()` for transformations.
72
+ - Use async/await syntax, including top-level await. Parallelize independent operations with `Promise.all()` or `Promise.allSettled()`.
73
+ - Prefer immutable data. Use `const` and avoid mutating objects or arrays.
74
+ - Do not use Express or other traditional web frameworks. Use AWS serverless services (API Gateway, Lambda) for HTTP APIs.
75
+ - Use nullish coalescing.
76
+ - Use satisfies for object literals.
77
+
78
+ # Data structures
79
+
80
+ - Use ISO 8601 format for timestamps in data structures to enable lexicographic sorting. Include at least millisecond precision and timezone offset (e.g., `2025-10-30T09:10:51.312+02:00`). Prefer Europe/Helsinki timezone when generating timestamps.
81
+ - Use lowerCamelCase for all data structure attribute/property names.
82
+ - Use PascalCase for classes and interfaces.
83
+ - Use UPPER_SNAKE_CASE for constants.
84
+
85
+ # DynamoDB
86
+
87
+ - Each bounded context owns a single DynamoDB table with overloaded `pk`/`sk` keys and GSIs for its access patterns. Use multi-table design within a context only when tables have fundamentally different characteristics (e.g., one table with append-only writes and another with mutable writes).
88
+ - Use lowercase for DynamoDB key attribute names. Default keys: partition key `pk` (string), sort key `sk` (string). GSI keys follow the pattern `gsi1pk`, `gsi1sk`, `gsi2pk`, `gsi2sk`, etc.
89
+ - Use DynamoDB `DynamoDBDocumentClient` from `@aws-sdk/lib-dynamodb`.
90
+ - Use DynamoDB Streams for event-driven patterns.
91
+ - Enable bisect on function error for DynamoDB Stream event sources.
92
+ - Design DynamoDB access patterns to use Query operations (avoid Scan).
93
+ - Use DynamoDB transactions (`transactWrite` and `transactGet`) for atomic operations across multiple items. Always include condition expressions to verify that source data hasn't changed since it was read.
94
+
95
+ # AWS SDK
96
+
97
+ - Use latest major version of the AWS SDK for JavaScript.
98
+
99
+ # AWS CDK project management
100
+
101
+ - Check if the project uses Projen (.projenrc.ts or .projenrc.js). If Projen is configured, use it for all project configuration and dependency management.
102
+ - Do not manually edit package.json, tsconfig.json, or other generated files in Projen projects.
103
+
104
+ # AWS CDK imports
105
+
106
+ - Import AWS service constructs from the main `aws-cdk-lib` package using named imports with underscore notation.
107
+ - Do not use namespace imports from service-specific subpaths.
108
+
109
+ **Correct:**
110
+
111
+ ```typescript
112
+ import { aws_lambda, aws_dynamodb, aws_s3 } from "aws-cdk-lib";
113
+
114
+ const fn = new aws_lambda.Function(this, "MyFunction", { ... });
115
+ const table = new aws_dynamodb.Table(this, "MyTable", { ... });
116
+ ```
117
+
118
+ **Incorrect:**
119
+
120
+ ```typescript
121
+ import * as lambda from "aws-cdk-lib/aws-lambda";
122
+ import * as dynamodb from "aws-cdk-lib/aws-dynamodb";
123
+ ```
124
+
125
+ # AWS CDK security and networking
126
+
127
+ - Follow least privilege principle for all IAM permissions.
128
+ - Prefer CDK grant methods (e.g., `bucket.grantRead()`, `table.grantWriteData()`) over creating custom IAM roles and policies.
129
+ - For network security groups, use the `connections` property (e.g., `lambda.connections.allowTo()`, `rds.connections.allowFrom()`) instead of manually managing security group rules.
130
+
131
+ # AWS Lambda
132
+
133
+ - Use latest available LTS version of Node.js runtime.
134
+ - Use ARM-based architecture.
135
+ - Use AWS CDK `NodeJsFunction` construct to bundle the handler code.
136
+ - For Lambda functions triggered by DynamoDB Streams, enable `bisectBatchOnError: true` on the event source mapping.
137
+
138
+ # Amazon CloudFront
139
+
140
+ - Use CloudFront distributions for serving static content.
141
+ - Configure S3 buckets as CloudFront origins with Origin Access Control (OAC).
142
+ - Use CloudFront VPC origins for private Application Load Balancers and other VPC resources.
143
+
144
+ # Terminology
145
+
146
+ Use these proper nouns with exact capitalization:
147
+
148
+ - Sales Flow
149
+ - Session Event
150
+ - Session Service
151
+ - Session Event Design Document
152
+ - Session Service Design Document
153
+
154
+ # Testing
155
+
156
+ - All code must have test coverage: use unit tests for business-logic and utility code, integration tests for Lambda function handlers, CDK assertions for infrastructure code.
157
+ - Test domain logic in isolation without any infrastructure dependencies or mocks.
158
+ - If no testing framework is configured, use Vitest.
159
+ - Avoid mocking in tests. Prefer dependency injection and testing pure functions with real implementations. For AWS services, use local alternatives (e.g., DynamoDB Local, LocalStack) or in-memory implementations for integration tests.
160
+
161
+ # Error handling
162
+
163
+ - Use custom error classes. Extend the base `Error` class and set the error name.
164
+ - Define all errors in `src/domain/errors.ts` (or `src/domain/errors/` for multiple files).
165
+ - Domain errors represent business rule violations (e.g., `InvalidPriceError`, `ProductNotAvailableError`).
166
+ - Application errors represent operational failures (e.g., `ProductNotFoundError`, `ConcurrencyError`).
167
+ - Infrastructure errors represent technical failures (e.g., `DatabaseConnectionError`, `ExternalServiceError`).
168
+ - Lambda handlers must catch errors and map them to appropriate HTTP status codes or event handling logic.
169
+ - Include relevant context in error messages and properties. Never expose sensitive data.
170
+ - Configure dead letter queues (DLQ) for all asynchronous Lambda invocations, DynamoDB Streams, EventBridge rules, and SNS subscriptions.
171
+
172
+ **Example:**
173
+
174
+ ```typescript
175
+ export class InvalidPriceError extends Error {
176
+ constructor(price: number) {
177
+ super(`Price must be positive, got ${price}`);
178
+ this.name = "InvalidPriceError";
179
+ }
180
+ }
181
+
182
+ export class ProductNotFoundError extends Error {
183
+ constructor(public readonly productId: string) {
184
+ super(`Product not found: ${productId}`);
185
+ this.name = "ProductNotFoundError";
186
+ }
187
+ }
188
+ ```
189
+
190
+ # Idempotency
191
+
192
+ - Design all event handlers and async operations to be idempotent.
193
+ - Use DynamoDB conditional writes with idempotency keys. Store the idempotency key with a TTL.
194
+ - For DynamoDB Stream handlers, use the event ID from the stream record as the idempotency key.
195
+ - For EventBridge events, include a unique event ID in the event payload and use it as the idempotency key.
196
+ - Store idempotency records in the same table as your domain data using key pattern: `pk: "IDEMPOTENCY#<eventId>"`, `sk: "IDEMPOTENCY"`.
197
+ - Set TTL on idempotency records (e.g., 24 hours).
198
+
199
+ **Example:**
200
+
201
+ ```typescript
202
+ async function handleProductUpdate(event: DynamoDBStreamEvent) {
203
+ for (const record of event.Records) {
204
+ const idempotencyKey = record.eventID;
205
+
206
+ try {
207
+ await dynamodb.put({
208
+ TableName: TABLE_NAME,
209
+ Item: {
210
+ pk: `IDEMPOTENCY#${idempotencyKey}`,
211
+ sk: "IDEMPOTENCY",
212
+ ttl: Math.floor(Date.now() / 1000) + 86400, // 24 hours
213
+ },
214
+ ConditionExpression: "attribute_not_exists(pk)",
215
+ });
216
+
217
+ // Process the event - this only runs if idempotency key was successfully stored
218
+ await processProductUpdate(record);
219
+ } catch (error) {
220
+ if (error.name === "ConditionalCheckFailedException") {
221
+ // Already processed, skip
222
+ continue;
223
+ }
224
+ throw error;
225
+ }
226
+ }
227
+ }
228
+ ```
229
+
230
+ # Observability
231
+
232
+ - Emit a single context-rich structured log (wide event) per service hop for each request.
233
+ - Use high-cardinality fields (user IDs, request IDs, entity IDs).
234
+ - Include high-dimensional data with many fields.
235
+ - Connect all events for a request using a correlation ID (request ID or trace ID).
236
+ - Redact sensitive data (passwords, tokens, PII) before logging.
237
+
238
+ **Required fields in all events:**
239
+
240
+ - `requestId` - Correlation ID for the entire request flow
241
+ - `timestamp` - ISO 8601 format with timezone
242
+ - `service` - Service name
243
+ - `message` - Human-readable description
244
+ - `outcome` - Result status (e.g., "ok", "error")
245
+ - `duration` - Operation duration in milliseconds
246
+
247
+ **Recommended context fields:**
248
+
249
+ - Request details: `method`, `path`, `statusCode`, `headers`
250
+ - User context: `userId`, user properties relevant to business logic
251
+ - Entity data: Full entity objects involved in the operation
252
+ - Infrastructure: `commitHash`, database queries, cache operations
253
+ - Downstream calls: Service names, durations, status codes
254
+
255
+ **Example wide event:**
256
+
257
+ ```json
258
+ {
259
+ "requestId": "8bfdf7ecdd485694",
260
+ "timestamp": "2024-09-08T06:14:05.680+03:00",
261
+ "service": "order-processing",
262
+ "message": "Order placed",
263
+ "outcome": "ok",
264
+ "duration": 268,
265
+ "commitHash": "690de31f245eb4f2160643e0dbb5304179a1cdd3",
266
+ "event": {
267
+ "type": "OrderPlaced",
268
+ "source": "order-service",
269
+ "eventId": "evt_f8d4d21c-f1fd-48b9"
270
+ },
271
+ "customer": {
272
+ "id": "cust_fdc4ddd4-8b30",
273
+ "segment": "premium",
274
+ "storeId": "store-123"
275
+ },
276
+ "order": {
277
+ "id": "ord_f8d4d21c-f1fd",
278
+ "totalAmount": 45.99,
279
+ "itemCount": 3,
280
+ "fulfillmentType": "home-delivery"
281
+ },
282
+ "dynamodb": {
283
+ "operation": "transactWrite",
284
+ "items": [
285
+ {
286
+ "table": "orders-table",
287
+ "pk": "ORDER#ord_f8d4d21c-f1fd",
288
+ "sk": "METADATA",
289
+ "operation": "put"
290
+ },
291
+ {
292
+ "table": "orders-table",
293
+ "pk": "CUSTOMER#cust_fdc4ddd4-8b30",
294
+ "sk": "ORDER#ord_f8d4d21c-f1fd",
295
+ "operation": "put"
296
+ }
297
+ ],
298
+ "duration": 45
299
+ },
300
+ "downstream": {
301
+ "eventBridge": {
302
+ "eventBus": "retail-events",
303
+ "published": true,
304
+ "duration": 12
305
+ }
306
+ }
307
+ }
308
+ ```
309
+
310
+ # Formal modeling
311
+
312
+ When designing logic for event-driven architectures, distributed systems, or asynchronous communication patterns:
313
+
314
+ - Model the system using P language (https://p-org.github.io/P/) to formally verify correctness
315
+ - Define state machines for each component/actor in the system
316
+ - Specify safety properties (what should never happen) and liveness properties (what should eventually happen)
317
+ - Model all possible event orderings and race conditions
318
+ - Use P's model checker to verify the design before implementation
319
+ - Include the P model specification alongside architecture diagrams
320
+ - Focus on modeling: message protocols, failure scenarios, concurrent interactions, and state transitions