@umituz/react-native-subscription 2.15.3 → 2.15.5
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/package.json +1 -1
- package/src/presentation/hooks/useSubscriptionSettingsConfig.ts +6 -2
- package/src/revenuecat/infrastructure/config/SandboxDurationConfig.ts +42 -0
- package/src/revenuecat/infrastructure/utils/ExpirationDateCalculator.ts +19 -60
- package/src/revenuecat/infrastructure/utils/SandboxDurationConverter.ts +68 -0
- package/src/domains/README.md.bak +0 -274
- package/src/domains/wallet/README.md.bak +0 -209
- package/src/presentation/README.md.bak +0 -172
- package/src/presentation/components/README.md.bak +0 -217
- package/src/presentation/hooks/useCredits.md.bak +0 -231
- package/src/presentation/hooks/useFeatureGate.md.bak +0 -284
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@umituz/react-native-subscription",
|
|
3
|
-
"version": "2.15.
|
|
3
|
+
"version": "2.15.5",
|
|
4
4
|
"description": "Complete subscription management with RevenueCat, paywall UI, and credits system for React Native apps",
|
|
5
5
|
"main": "./src/index.ts",
|
|
6
6
|
"types": "./src/index.ts",
|
|
@@ -16,6 +16,7 @@ import { useCreditsArray, getSubscriptionStatusType } from "./useSubscriptionSet
|
|
|
16
16
|
import { getCreditsConfig } from "../../infrastructure/repositories/CreditsRepositoryProvider";
|
|
17
17
|
import { detectPackageType } from "../../utils/packageTypeDetector";
|
|
18
18
|
import { getCreditAllocation } from "../../utils/creditMapper";
|
|
19
|
+
import { getExpirationDate } from "../../revenuecat/infrastructure/utils/ExpirationDateCalculator";
|
|
19
20
|
import type {
|
|
20
21
|
SubscriptionSettingsConfig,
|
|
21
22
|
SubscriptionStatusType,
|
|
@@ -75,8 +76,11 @@ export const useSubscriptionSettingsConfig = (
|
|
|
75
76
|
}, [premiumEntitlement?.productIdentifier, creditLimit]);
|
|
76
77
|
|
|
77
78
|
// Get expiration date from RevenueCat entitlement (source of truth)
|
|
78
|
-
//
|
|
79
|
-
const entitlementExpirationDate =
|
|
79
|
+
// Apply sandbox-to-production conversion for better testing UX
|
|
80
|
+
const entitlementExpirationDate = useMemo(() => {
|
|
81
|
+
if (!premiumEntitlement) return null;
|
|
82
|
+
return getExpirationDate(premiumEntitlement);
|
|
83
|
+
}, [premiumEntitlement]);
|
|
80
84
|
|
|
81
85
|
// Prefer CustomerInfo expiration (real-time) over cached status
|
|
82
86
|
const expiresAtIso = entitlementExpirationDate || (statusExpirationDate
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Duration Configuration
|
|
3
|
+
* Apple Sandbox subscription durations vs Production durations
|
|
4
|
+
*
|
|
5
|
+
* @see https://developer.apple.com/documentation/storekit/in-app_purchase/testing_in-app_purchases_with_sandbox
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { SubscriptionPackageType } from '../../../utils/packageTypeDetector';
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Apple Sandbox subscription durations in minutes
|
|
12
|
+
*/
|
|
13
|
+
export const SANDBOX_DURATIONS: Record<SubscriptionPackageType, number> = {
|
|
14
|
+
weekly: 3,
|
|
15
|
+
monthly: 5,
|
|
16
|
+
yearly: 60,
|
|
17
|
+
unknown: 5,
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Production subscription durations in days
|
|
22
|
+
*/
|
|
23
|
+
export const PRODUCTION_DURATIONS: Record<SubscriptionPackageType, number> = {
|
|
24
|
+
weekly: 7,
|
|
25
|
+
monthly: 30,
|
|
26
|
+
yearly: 365,
|
|
27
|
+
unknown: 30,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Get production duration in days for a package type
|
|
32
|
+
*/
|
|
33
|
+
export function getProductionDurationDays(packageType: SubscriptionPackageType): number {
|
|
34
|
+
return PRODUCTION_DURATIONS[packageType] ?? PRODUCTION_DURATIONS.unknown;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* Get sandbox duration in minutes for a package type
|
|
39
|
+
*/
|
|
40
|
+
export function getSandboxDurationMinutes(packageType: SubscriptionPackageType): number {
|
|
41
|
+
return SANDBOX_DURATIONS[packageType] ?? SANDBOX_DURATIONS.unknown;
|
|
42
|
+
}
|
|
@@ -1,78 +1,37 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Expiration Date Calculator
|
|
3
|
-
*
|
|
3
|
+
* Main entry point for calculating subscription expiration dates
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
import type { RevenueCatEntitlement } from '../../domain/types/RevenueCatTypes';
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
shouldConvertSandbox,
|
|
9
|
+
convertToProductionExpiration,
|
|
10
|
+
adjustPastExpiration,
|
|
11
|
+
} from './SandboxDurationConverter';
|
|
8
12
|
|
|
9
13
|
/**
|
|
10
|
-
*
|
|
11
|
-
*/
|
|
12
|
-
function addSubscriptionPeriod(date: Date, packageType: SubscriptionPackageType): Date {
|
|
13
|
-
const newDate = new Date(date);
|
|
14
|
-
|
|
15
|
-
switch (packageType) {
|
|
16
|
-
case 'weekly':
|
|
17
|
-
newDate.setDate(newDate.getDate() + 7);
|
|
18
|
-
break;
|
|
19
|
-
case 'monthly':
|
|
20
|
-
newDate.setDate(newDate.getDate() + 30);
|
|
21
|
-
break;
|
|
22
|
-
case 'yearly':
|
|
23
|
-
newDate.setDate(newDate.getDate() + 365);
|
|
24
|
-
break;
|
|
25
|
-
default:
|
|
26
|
-
// Unknown type, default to monthly
|
|
27
|
-
newDate.setDate(newDate.getDate() + 30);
|
|
28
|
-
break;
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
return newDate;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Adjust expiration date if it equals current date
|
|
36
|
-
*
|
|
37
|
-
* RevenueCat sometimes returns expiration date equal to purchase date
|
|
38
|
-
* immediately after purchase. This causes false "expired" status.
|
|
14
|
+
* Get adjusted expiration date from entitlement
|
|
39
15
|
*
|
|
40
|
-
*
|
|
41
|
-
*
|
|
16
|
+
* Handles:
|
|
17
|
+
* 1. Sandbox mode: Converts to production-equivalent dates for better UX
|
|
18
|
+
* 2. Past expiration: Adds subscription period if expiration is in the past
|
|
42
19
|
*/
|
|
43
|
-
function adjustExpirationDate(
|
|
44
|
-
expirationDate: string,
|
|
45
|
-
productIdentifier: string
|
|
46
|
-
): string {
|
|
47
|
-
const expDate = new Date(expirationDate);
|
|
48
|
-
const now = new Date();
|
|
49
|
-
|
|
50
|
-
// If expiration is in the future, no adjustment needed
|
|
51
|
-
if (expDate > now) {
|
|
52
|
-
return expirationDate;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
// If expiration is today or past, add subscription period
|
|
56
|
-
const packageType = detectPackageType(productIdentifier);
|
|
57
|
-
const adjustedDate = addSubscriptionPeriod(expDate, packageType);
|
|
58
|
-
|
|
59
|
-
return adjustedDate.toISOString();
|
|
60
|
-
}
|
|
61
|
-
|
|
62
20
|
export function getExpirationDate(
|
|
63
21
|
entitlement: RevenueCatEntitlement | null
|
|
64
22
|
): string | null {
|
|
65
|
-
if (!entitlement) {
|
|
23
|
+
if (!entitlement?.expirationDate) {
|
|
66
24
|
return null;
|
|
67
25
|
}
|
|
68
26
|
|
|
69
|
-
|
|
70
|
-
|
|
27
|
+
const { expirationDate, productIdentifier, isSandbox, latestPurchaseDate } = entitlement;
|
|
28
|
+
|
|
29
|
+
if (shouldConvertSandbox(isSandbox)) {
|
|
30
|
+
return convertToProductionExpiration(latestPurchaseDate, productIdentifier);
|
|
71
31
|
}
|
|
72
32
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
);
|
|
33
|
+
const expDate = new Date(expirationDate);
|
|
34
|
+
const adjustedDate = adjustPastExpiration(expDate, productIdentifier);
|
|
35
|
+
|
|
36
|
+
return adjustedDate.toISOString();
|
|
78
37
|
}
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Sandbox Duration Converter
|
|
3
|
+
* Converts Apple Sandbox subscription durations to production-equivalent dates
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { detectPackageType, type SubscriptionPackageType } from '../../../utils/packageTypeDetector';
|
|
7
|
+
import { getProductionDurationDays } from '../config/SandboxDurationConfig';
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Add production period to a date based on package type
|
|
11
|
+
*/
|
|
12
|
+
export function addProductionPeriod(
|
|
13
|
+
date: Date,
|
|
14
|
+
packageType: SubscriptionPackageType
|
|
15
|
+
): Date {
|
|
16
|
+
const newDate = new Date(date);
|
|
17
|
+
const daysToAdd = getProductionDurationDays(packageType);
|
|
18
|
+
newDate.setDate(newDate.getDate() + daysToAdd);
|
|
19
|
+
return newDate;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Check if sandbox conversion should be applied
|
|
24
|
+
* Only applies in DEV mode with sandbox flag
|
|
25
|
+
*/
|
|
26
|
+
export function shouldConvertSandbox(isSandbox: boolean): boolean {
|
|
27
|
+
return isSandbox && __DEV__;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Convert sandbox expiration to production-equivalent expiration
|
|
32
|
+
*
|
|
33
|
+
* In Apple Sandbox:
|
|
34
|
+
* - Weekly subscriptions expire in 3 minutes
|
|
35
|
+
* - Monthly subscriptions expire in 5 minutes
|
|
36
|
+
* - Yearly subscriptions expire in 1 hour
|
|
37
|
+
*
|
|
38
|
+
* For better testing UX, we calculate the production-equivalent expiration
|
|
39
|
+
* based on the purchase date + production duration.
|
|
40
|
+
*/
|
|
41
|
+
export function convertToProductionExpiration(
|
|
42
|
+
purchaseDate: string | null,
|
|
43
|
+
productIdentifier: string
|
|
44
|
+
): string {
|
|
45
|
+
const basePurchaseDate = purchaseDate ? new Date(purchaseDate) : new Date();
|
|
46
|
+
const packageType = detectPackageType(productIdentifier);
|
|
47
|
+
const productionExpiration = addProductionPeriod(basePurchaseDate, packageType);
|
|
48
|
+
|
|
49
|
+
return productionExpiration.toISOString();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/**
|
|
53
|
+
* Adjust expiration date if it's in the past
|
|
54
|
+
* Adds subscription period to prevent false expiration
|
|
55
|
+
*/
|
|
56
|
+
export function adjustPastExpiration(
|
|
57
|
+
expirationDate: Date,
|
|
58
|
+
productIdentifier: string
|
|
59
|
+
): Date {
|
|
60
|
+
const now = new Date();
|
|
61
|
+
|
|
62
|
+
if (expirationDate > now) {
|
|
63
|
+
return expirationDate;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const packageType = detectPackageType(productIdentifier);
|
|
67
|
+
return addProductionPeriod(expirationDate, packageType);
|
|
68
|
+
}
|
|
@@ -1,274 +0,0 @@
|
|
|
1
|
-
# Domains
|
|
2
|
-
|
|
3
|
-
Specialized domain modules implementing specific business logic and features.
|
|
4
|
-
|
|
5
|
-
## Location
|
|
6
|
-
|
|
7
|
-
**Directory**: `src/domains/`
|
|
8
|
-
|
|
9
|
-
**Type**: Domain Collection
|
|
10
|
-
|
|
11
|
-
## Strategy
|
|
12
|
-
|
|
13
|
-
### Domain-Driven Design
|
|
14
|
-
|
|
15
|
-
This directory implements Domain-Driven Design (DDD) principles:
|
|
16
|
-
|
|
17
|
-
1. **Wallet Domain**: Credit balance, transactions, and purchase flow
|
|
18
|
-
2. **Paywall Domain**: Upgrade prompts, subscription management
|
|
19
|
-
3. **Config Domain**: Feature flags, subscription configuration
|
|
20
|
-
|
|
21
|
-
Each domain is self-contained with:
|
|
22
|
-
- **Domain Layer**: Business logic, entities, value objects
|
|
23
|
-
- **Infrastructure Layer**: External integrations, repositories
|
|
24
|
-
- **Presentation Layer**: Domain-specific hooks and components
|
|
25
|
-
|
|
26
|
-
### Architecture Pattern
|
|
27
|
-
|
|
28
|
-
```
|
|
29
|
-
┌─────────────────────────────────────┐
|
|
30
|
-
│ Application Layer │
|
|
31
|
-
│ (Use Cases, Orchestration) │
|
|
32
|
-
└──────────────┬──────────────────────┘
|
|
33
|
-
│
|
|
34
|
-
┌───────┴────────┐
|
|
35
|
-
│ │
|
|
36
|
-
┌──────▼──────┐ ┌─────▼──────┐
|
|
37
|
-
│ Wallet │ │ Paywall │
|
|
38
|
-
│ Domain │ │ Domain │
|
|
39
|
-
├─────────────┤ ├────────────┤
|
|
40
|
-
│ Domain │ │ Domain │
|
|
41
|
-
│ Infra │ │ Infra │
|
|
42
|
-
│ Presentation│ │ Presentation│
|
|
43
|
-
└──────┬──────┘ └─────┬──────┘
|
|
44
|
-
│ │
|
|
45
|
-
└────────┬───────┘
|
|
46
|
-
│
|
|
47
|
-
┌────────▼──────────┐
|
|
48
|
-
│ Shared Infra │
|
|
49
|
-
│ (Firebase, etc) │
|
|
50
|
-
└───────────────────┘
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
## Domain Modules
|
|
54
|
-
|
|
55
|
-
### Wallet Domain (`wallet/`)
|
|
56
|
-
|
|
57
|
-
**Responsibility**: Credit balance and transaction management
|
|
58
|
-
|
|
59
|
-
**Key Features**:
|
|
60
|
-
- Credit balance tracking
|
|
61
|
-
- Transaction history
|
|
62
|
-
- Purchase initialization
|
|
63
|
-
- Real-time updates
|
|
64
|
-
|
|
65
|
-
**Documentation**: `wallet/README.md`
|
|
66
|
-
|
|
67
|
-
### Paywall Domain (`paywall/`)
|
|
68
|
-
|
|
69
|
-
**Responsibility**: Subscription upgrade flows and paywall UI
|
|
70
|
-
|
|
71
|
-
**Key Features**:
|
|
72
|
-
- Paywall display logic
|
|
73
|
-
- Subscription management
|
|
74
|
-
- Feature gating
|
|
75
|
-
- Upgrade prompts
|
|
76
|
-
|
|
77
|
-
**Documentation**: `paywall/README.md`
|
|
78
|
-
|
|
79
|
-
### Config Domain (`config/`)
|
|
80
|
-
|
|
81
|
-
**Responsibility**: Subscription configuration and feature flags
|
|
82
|
-
|
|
83
|
-
**Key Features**:
|
|
84
|
-
- Subscription tiers
|
|
85
|
-
- Feature configuration
|
|
86
|
-
- Pricing rules
|
|
87
|
-
- Feature flags
|
|
88
|
-
|
|
89
|
-
**Documentation**: `config/README.md`
|
|
90
|
-
|
|
91
|
-
## Restrictions
|
|
92
|
-
|
|
93
|
-
### REQUIRED
|
|
94
|
-
|
|
95
|
-
- **Domain Isolation**: Domains MUST NOT directly depend on each other
|
|
96
|
-
- **Interface Segregation**: Use well-defined interfaces between layers
|
|
97
|
-
- **Dependency Inversion**: Depend on abstractions, not concretions
|
|
98
|
-
- **Testability**: All domains MUST be testable in isolation
|
|
99
|
-
|
|
100
|
-
### PROHIBITED
|
|
101
|
-
|
|
102
|
-
- **NEVER** share domain logic between domains (use shared kernel if needed)
|
|
103
|
-
- **NEVER** create circular dependencies between domains
|
|
104
|
-
- **DO NOT** bypass domain layer from presentation
|
|
105
|
-
- **NEVER** expose infrastructure details to other domains
|
|
106
|
-
|
|
107
|
-
### CRITICAL SAFETY
|
|
108
|
-
|
|
109
|
-
- **ALWAYS** validate invariants at domain boundaries
|
|
110
|
-
- **ALWAYS** implement domain errors for business rule violations
|
|
111
|
-
- **NEVER** allow inconsistent domain state
|
|
112
|
-
- **MUST** implement proper transaction boundaries
|
|
113
|
-
- **ALWAYS** sanitize inputs from external sources
|
|
114
|
-
|
|
115
|
-
## Rules
|
|
116
|
-
|
|
117
|
-
### Domain Boundaries
|
|
118
|
-
|
|
119
|
-
```typescript
|
|
120
|
-
// CORRECT - Respecting domain boundaries
|
|
121
|
-
// Wallet domain handles credits
|
|
122
|
-
const { credits } = useCredits(); // From wallet domain
|
|
123
|
-
|
|
124
|
-
// Paywall domain handles upgrades
|
|
125
|
-
const { showPaywall } = usePaywallOperations(); // From paywall domain
|
|
126
|
-
|
|
127
|
-
// INCORRECT - Crossing domain boundaries
|
|
128
|
-
const walletRepository = new WalletRepository();
|
|
129
|
-
// Directly using wallet repo in paywall component
|
|
130
|
-
```
|
|
131
|
-
|
|
132
|
-
### Domain Errors
|
|
133
|
-
|
|
134
|
-
```typescript
|
|
135
|
-
// CORRECT - Domain-specific errors
|
|
136
|
-
class InsufficientCreditsError extends DomainError {
|
|
137
|
-
constructor(required: number, available: number) {
|
|
138
|
-
super(`Insufficient credits: need ${required}, have ${available}`);
|
|
139
|
-
}
|
|
140
|
-
}
|
|
141
|
-
|
|
142
|
-
// INCORRECT - Generic errors
|
|
143
|
-
throw new Error('Not enough credits'); // Loses domain context
|
|
144
|
-
```
|
|
145
|
-
|
|
146
|
-
### Dependency Direction
|
|
147
|
-
|
|
148
|
-
```typescript
|
|
149
|
-
// CORRECT - Dependency inversion
|
|
150
|
-
interface ICreditsRepository {
|
|
151
|
-
getBalance(userId: string): Promise<number>;
|
|
152
|
-
deductCredits(userId: string, amount: number): Promise<void>;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
// Domain depends on interface, not implementation
|
|
156
|
-
class CreditService {
|
|
157
|
-
constructor(private repo: ICreditsRepository) {}
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
// INCORRECT - Concrete dependency
|
|
161
|
-
class CreditService {
|
|
162
|
-
constructor(private repo: FirebaseCreditsRepository) {}
|
|
163
|
-
// Tightly coupled to Firebase
|
|
164
|
-
}
|
|
165
|
-
```
|
|
166
|
-
|
|
167
|
-
## AI Agent Guidelines
|
|
168
|
-
|
|
169
|
-
### When Working with Domains
|
|
170
|
-
|
|
171
|
-
1. **Always** respect domain boundaries
|
|
172
|
-
2. **Always** use dependency inversion
|
|
173
|
-
3. **Always** implement domain-specific errors
|
|
174
|
-
4. **Always** validate invariants at boundaries
|
|
175
|
-
5. **Never** create circular dependencies
|
|
176
|
-
|
|
177
|
-
### Integration Checklist
|
|
178
|
-
|
|
179
|
-
- [ ] Identify correct domain for feature
|
|
180
|
-
- [ ] Respect domain boundaries
|
|
181
|
-
- [ ] Use appropriate interfaces
|
|
182
|
-
- [ ] Handle domain errors
|
|
183
|
-
- [ ] Test domain in isolation
|
|
184
|
-
- [ ] Document domain interactions
|
|
185
|
-
- [ ] Validate invariants
|
|
186
|
-
- [ ] Implement transaction boundaries
|
|
187
|
-
- [ ] Test cross-domain scenarios
|
|
188
|
-
- [ ] Verify no circular dependencies
|
|
189
|
-
|
|
190
|
-
### Common Patterns
|
|
191
|
-
|
|
192
|
-
1. **Aggregate Root**: Single entry point for aggregate
|
|
193
|
-
2. **Value Objects**: Immutable values with no identity
|
|
194
|
-
3. **Domain Events**: Publish domain events for side effects
|
|
195
|
-
4. **Repositories**: Abstract data access
|
|
196
|
-
5. **Factories**: Complex object creation
|
|
197
|
-
6. **Domain Services**: Business logic that doesn't fit entities
|
|
198
|
-
7. **Specification**: Business rule encapsulation
|
|
199
|
-
8. **Anti-Corruption Layer**: Isolate from external systems
|
|
200
|
-
|
|
201
|
-
## Related Documentation
|
|
202
|
-
|
|
203
|
-
- **Wallet Domain**: `wallet/README.md`
|
|
204
|
-
- **Paywall Domain**: `paywall/README.md`
|
|
205
|
-
- **Config Domain**: `config/README.md`
|
|
206
|
-
- **Domain Layer**: `../domain/README.md`
|
|
207
|
-
- **Infrastructure**: `../infrastructure/README.md`
|
|
208
|
-
|
|
209
|
-
## Domain Structure
|
|
210
|
-
|
|
211
|
-
```
|
|
212
|
-
src/domains/
|
|
213
|
-
├── wallet/ # Wallet and credits domain
|
|
214
|
-
│ ├── domain/ # Business logic
|
|
215
|
-
│ ├── infrastructure/ # External integrations
|
|
216
|
-
│ └── presentation/ # UI hooks and components
|
|
217
|
-
├── paywall/ # Paywall and upgrades domain
|
|
218
|
-
│ ├── domain/
|
|
219
|
-
│ ├── infrastructure/
|
|
220
|
-
│ └── presentation/
|
|
221
|
-
└── config/ # Configuration domain
|
|
222
|
-
├── domain/
|
|
223
|
-
├── infrastructure/
|
|
224
|
-
└── presentation/
|
|
225
|
-
```
|
|
226
|
-
|
|
227
|
-
## Creating a New Domain
|
|
228
|
-
|
|
229
|
-
When creating a new domain:
|
|
230
|
-
|
|
231
|
-
1. **Define Boundaries**: What is the domain's responsibility?
|
|
232
|
-
2. **Identify Entities**: What are the core business objects?
|
|
233
|
-
3. **Define Invariants**: What rules must always be true?
|
|
234
|
-
4. **Design Interfaces**: How will other layers interact?
|
|
235
|
-
5. **Implement Repository**: Abstract data access
|
|
236
|
-
6. **Create Presentation Layer**: Hooks and components
|
|
237
|
-
7. **Write Tests**: Test domain logic in isolation
|
|
238
|
-
8. **Document**: Provide comprehensive README
|
|
239
|
-
|
|
240
|
-
Example:
|
|
241
|
-
|
|
242
|
-
```typescript
|
|
243
|
-
// Domain entity
|
|
244
|
-
export class FeatureFlag {
|
|
245
|
-
constructor(
|
|
246
|
-
public readonly id: string,
|
|
247
|
-
public readonly name: string,
|
|
248
|
-
private _isEnabled: boolean
|
|
249
|
-
) {}
|
|
250
|
-
|
|
251
|
-
get isEnabled(): boolean {
|
|
252
|
-
return this._isEnabled;
|
|
253
|
-
}
|
|
254
|
-
|
|
255
|
-
enable(): void {
|
|
256
|
-
this._isEnabled = true;
|
|
257
|
-
}
|
|
258
|
-
|
|
259
|
-
disable(): void {
|
|
260
|
-
this._isEnabled = false;
|
|
261
|
-
}
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Repository interface
|
|
265
|
-
export interface IFeatureFlagRepository {
|
|
266
|
-
findById(id: string): Promise<FeatureFlag | null>;
|
|
267
|
-
save(flag: FeatureFlag): Promise<void>;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
// Presentation hook
|
|
271
|
-
export function useFeatureFlag(featureId: string) {
|
|
272
|
-
// Hook implementation
|
|
273
|
-
}
|
|
274
|
-
```
|