@mediacubeco/expo-template-fsd 1.2.0 → 2.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.
- package/.claude/rules/app-routing.mdc +5 -5
- package/.claude/rules/mediacubeco-packages.mdc +56 -0
- package/.claude/settings.json +11 -0
- package/.claude/settings.local.json +11 -0
- package/.claude/skills/code-style/SKILL.md +1 -0
- package/.claude/skills/generate-api-model/SKILL.md +193 -61
- package/.claude/skills/generate-api-model/references/entities/common.ts +28 -0
- package/.claude/skills/generate-api-model/references/entities/index.ts +4 -0
- package/.claude/skills/generate-api-model/references/entities/post/index.ts +2 -0
- package/.claude/skills/generate-api-model/references/entities/post/post-comment.ts +16 -0
- package/.claude/skills/generate-api-model/references/entities/post/post.ts +27 -0
- package/.claude/skills/generate-api-model/references/entities/user/index.ts +3 -0
- package/.claude/skills/generate-api-model/references/entities/user/user-settings.ts +16 -0
- package/.claude/skills/generate-api-model/references/entities/user/user-verification.ts +22 -0
- package/.claude/skills/generate-api-model/references/entities/user/user.ts +26 -0
- package/.claude/skills/generate-api-model/references/resources/common.ts +8 -0
- package/.claude/skills/generate-api-model/references/resources/index.ts +4 -0
- package/.claude/skills/generate-api-model/references/resources/post/index.ts +2 -0
- package/.claude/skills/generate-api-model/references/resources/post/post-comment.ts +8 -0
- package/.claude/skills/generate-api-model/references/resources/post/post.ts +14 -0
- package/.claude/skills/generate-api-model/references/resources/user/index.ts +3 -0
- package/.claude/skills/generate-api-model/references/resources/user/user-settings.ts +8 -0
- package/.claude/skills/generate-api-model/references/resources/user/user-verification.ts +16 -0
- package/.claude/skills/generate-api-model/references/resources/user/user.ts +13 -0
- package/.claude/skills/git-commit/SKILL.md +139 -0
- package/.env +0 -1
- package/.env.example +0 -1
- package/CLAUDE.md +80 -0
- package/env.d.ts +0 -1
- package/expo-env.d.ts +3 -0
- package/i18next.d.ts +1 -1
- package/package.json +44 -43
- package/skills-lock.json +29 -0
- package/src/app/_app.tsx +2 -0
- package/src/shared/assets/icons/like.tsx +1 -1
- package/src/shared/constants/environment.ts +1 -7
- package/src/shared/hooks/use-animated.ts +1 -1
- package/src/shared/hooks/use-safe-area.ts +2 -2
- package/src/shared/lib/api/api.provider.tsx +2 -2
- package/src/shared/lib/api/api.types.ts +1 -1
- package/src/shared/lib/api/entities/common.ts +5 -5
- package/src/shared/lib/api/hooks/common.ts +1 -1
- package/src/shared/lib/api/models/api-instance/api-instance.ts +9 -9
- package/src/shared/lib/api/models/api-instance/api-instance.types.ts +1 -1
- package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +4 -4
- package/src/shared/lib/api/repositories/auth/auth.ts +6 -1
- package/src/shared/lib/api/repositories/auth/auth.types.ts +1 -1
- package/src/shared/lib/api/resources/common.ts +3 -3
- package/src/shared/lib/localization/constants/index.ts +1 -1
- package/src/shared/lib/localization/hooks/use-localization.ts +1 -1
- package/src/shared/lib/localization/localization.provider.tsx +1 -1
- package/src/shared/lib/localization/localization.types.ts +1 -1
- package/src/shared/lib/localization/utils/storage.ts +1 -1
- package/src/shared/lib/navigation/hooks/use-navigation-options.ts +7 -6
- package/src/shared/lib/navigation/navigation.provider.tsx +2 -2
- package/src/shared/lib/navigation/navigation.types.ts +1 -1
- package/src/shared/lib/theme/theme.context.ts +1 -1
- package/src/shared/lib/theme/theme.provider.tsx +21 -6
- package/src/shared/lib/theme/theme.types.ts +2 -2
- package/src/shared/lib/theme/utils/storage.ts +1 -1
- package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +1 -1
- package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +1 -1
- package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
- package/src/shared/ui/templates/screen-container/screen-container-list.tsx +1 -1
- package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +1 -1
- package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +1 -1
- package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +3 -3
- package/src/shared/ui/templates/screen-container/screen-container.styles.ts +1 -1
- package/src/shared/ui/templates/screen-container/screen-container.types.tsx +13 -13
- package/svg.d.ts +1 -1
- package/.claude/mcp.json +0 -0
- package/.claude/rules/abstract-skills-agents.mdc +0 -25
- package/src/shared/utils/generators.ts +0 -11
- package/src/shared/utils/index.ts +0 -1
- /package/src/{screens → pages}/.gitkeep +0 -0
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
---
|
|
2
|
-
description: Thin Expo Router layer in src/app — layout/screen and delegation to ~
|
|
2
|
+
description: Thin Expo Router layer in src/app — layout/screen and delegation to ~pages
|
|
3
3
|
globs: src/app/**/*.tsx
|
|
4
4
|
alwaysApply: false
|
|
5
5
|
---
|
|
6
6
|
|
|
7
7
|
# `src/app` layer (Expo Router)
|
|
8
8
|
|
|
9
|
-
Files under `src/app` only wire routes to implementation. Screen UI and business logic live in `~
|
|
9
|
+
Files under `src/app` only wire routes to implementation. Screen UI and business logic live in `~pages/*` (or other features), not in `app`.
|
|
10
10
|
|
|
11
11
|
## `_layout.tsx`
|
|
12
12
|
|
|
@@ -16,9 +16,9 @@ Files under `src/app` only wire routes to implementation. Screen UI and business
|
|
|
16
16
|
|
|
17
17
|
## Route screen files (`index.tsx`, `[param].tsx`, named routes, etc.)
|
|
18
18
|
|
|
19
|
-
- The main component is a function named **`screen`**; it only renders the root screen from `~
|
|
19
|
+
- The main component is a function named **`screen`**; it only renders the root screen from `~pages/...` (import a single root screen component).
|
|
20
20
|
- **Default**-export it (`export default screen` at the end of the file, same export rules).
|
|
21
|
-
- Do not duplicate screen markup in the route file — only a thin wrapper around the `~
|
|
21
|
+
- Do not duplicate screen markup in the route file — only a thin wrapper around the `~pages` import.
|
|
22
22
|
|
|
23
23
|
## Exceptions
|
|
24
24
|
|
|
@@ -37,7 +37,7 @@ export default Layout
|
|
|
37
37
|
|
|
38
38
|
```tsx
|
|
39
39
|
// route screen
|
|
40
|
-
import { SomeFeatureScreen } from '~
|
|
40
|
+
import { SomeFeatureScreen } from '~pages/some-feature'
|
|
41
41
|
|
|
42
42
|
function Screen() {
|
|
43
43
|
return <SomeFeatureScreen />
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Enforce use of @mediacubeco/* corporate packages over custom re-implementations
|
|
3
|
+
alwaysApply: true
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Use @mediacubeco/\* corporate packages
|
|
7
|
+
|
|
8
|
+
All utilities, helpers, hooks, and configs that exist in `@mediacubeco/*` packages **must** be imported from there. Do not duplicate or re-implement what is already provided.
|
|
9
|
+
|
|
10
|
+
## Available packages
|
|
11
|
+
|
|
12
|
+
To discover what each package exports, read its DTS index:
|
|
13
|
+
|
|
14
|
+
- `@mediacubeco/base` — `dist/index.d.ts`
|
|
15
|
+
- subpaths: `/helpers`, `/services`, `/types`, `/utils`
|
|
16
|
+
- `@mediacubeco/base-react-native` — `dist/index.d.ts`
|
|
17
|
+
- subpaths: `/helpers`, `/hooks`, `/utils`
|
|
18
|
+
|
|
19
|
+
## Rules
|
|
20
|
+
|
|
21
|
+
- Before writing any utility, helper, hook, or type — read the relevant package's DTS index and check if it already exists.
|
|
22
|
+
- Always import via subpath, never from the package root:
|
|
23
|
+
- `@mediacubeco/base/utils` ✅ — `@mediacubeco/base` ❌
|
|
24
|
+
- `@mediacubeco/base-react-native/hooks` ✅ — `@mediacubeco/base-react-native` ❌
|
|
25
|
+
- Never copy source from a `@mediacubeco/*` package into the app.
|
|
26
|
+
- ESLint, Prettier, and TypeScript configs must extend the corresponding `@mediacubeco/*` config package — do not inline equivalent rules.
|
|
27
|
+
- If something is missing from a `@mediacubeco/*` package, add it there first, then import it in the app.
|
|
28
|
+
|
|
29
|
+
## Examples
|
|
30
|
+
|
|
31
|
+
```typescript
|
|
32
|
+
// ✅ GOOD — subpath import
|
|
33
|
+
import { isNull } from '@mediacubeco/base/utils'
|
|
34
|
+
|
|
35
|
+
// ❌ BAD — inline
|
|
36
|
+
value === null
|
|
37
|
+
|
|
38
|
+
// ❌ BAD — root import
|
|
39
|
+
import { isNull } from '@mediacubeco/base'
|
|
40
|
+
|
|
41
|
+
// ❌ BAD — reimplementing what already exists
|
|
42
|
+
function isNull(value: unknown): value is null {
|
|
43
|
+
return value === null
|
|
44
|
+
}
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
```typescript
|
|
48
|
+
// ✅ GOOD — subpath import
|
|
49
|
+
import { useKeyboard } from '@mediacubeco/base-react-native/hooks'
|
|
50
|
+
|
|
51
|
+
// ❌ BAD — root import
|
|
52
|
+
import { useKeyboard } from '@mediacubeco/base-react-native'
|
|
53
|
+
|
|
54
|
+
// ❌ BAD — custom re-implementation
|
|
55
|
+
function useKeyboard() { /* ... */ }
|
|
56
|
+
```
|
package/.claude/settings.json
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
{
|
|
2
|
+
"enabledPlugins": {
|
|
3
|
+
"gitlab@claude-plugins-official": true,
|
|
4
|
+
|
|
5
|
+
"superpowers@claude-plugins-official": true,
|
|
6
|
+
|
|
7
|
+
"expo@claude-plugins-official": true,
|
|
8
|
+
"code-review@claude-plugins-official": true,
|
|
9
|
+
"code-simplifier@claude-plugins-official": true
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
TODO: fill
|
|
@@ -1,98 +1,230 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: generate-api-model
|
|
3
3
|
description: >-
|
|
4
|
-
Generates API
|
|
5
|
-
Use
|
|
6
|
-
|
|
4
|
+
Generates API Resource types and Entity classes from a backend JSON response.
|
|
5
|
+
Use this skill whenever the user provides a backend JSON object or API schema
|
|
6
|
+
and wants TypeScript types generated from it, asks to "add a new API model",
|
|
7
|
+
"create an entity for...", "generate resource and entity", or wants to model
|
|
8
|
+
an API response. Also use when adding new domain types to the shared API
|
|
9
|
+
library (resources/ + entities/ folders).
|
|
7
10
|
---
|
|
8
11
|
|
|
9
12
|
# Generate API Model
|
|
10
13
|
|
|
11
|
-
##
|
|
14
|
+
## What this skill does
|
|
12
15
|
|
|
13
|
-
|
|
16
|
+
Take a **backend JSON response** (or API contract) and produce two TypeScript artifacts per domain object:
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
18
|
+
1. **Resource** (`*Resource`) — a `type` that mirrors the **exact wire format** of the backend: field names are copied verbatim, including any snake_case, camelCase, kebab-case, UPPER_SNAKE_CASE, or PascalCase the backend uses.
|
|
19
|
+
2. **Entity** (`*Entity`) — a `class` that maps every field to strict **camelCase** and applies project naming conventions for dates, arrays, and booleans.
|
|
17
20
|
|
|
18
|
-
|
|
21
|
+
## Filesystem layout
|
|
19
22
|
|
|
20
|
-
|
|
23
|
+
```
|
|
24
|
+
<api-lib>/
|
|
25
|
+
├── resources/
|
|
26
|
+
│ ├── common.ts ← BaseResource shared type (see references/)
|
|
27
|
+
│ ├── index.ts ← barrel re-exporting all domains
|
|
28
|
+
│ └── <domain>/
|
|
29
|
+
│ ├── index.ts ← barrel for this domain
|
|
30
|
+
│ └── <domain>.ts ← Resource type(s) for this domain
|
|
31
|
+
└── entities/
|
|
32
|
+
├── common.ts ← BaseEntity<T> base class (see references/)
|
|
33
|
+
├── index.ts ← barrel re-exporting all domains
|
|
34
|
+
└── <domain>/
|
|
35
|
+
├── index.ts ← barrel for this domain
|
|
36
|
+
└── <domain>.ts ← Entity class for this domain
|
|
37
|
+
```
|
|
21
38
|
|
|
22
|
-
|
|
39
|
+
Domain folder names are **kebab-case**. See `references/` for complete working examples.
|
|
23
40
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## Step 1 — Identify domains
|
|
44
|
+
|
|
45
|
+
From the backend JSON, identify independent domain objects. Exclude generic HTTP envelopes (pagination wrappers, status/error shapes) unless the user explicitly requests them.
|
|
46
|
+
|
|
47
|
+
**If the user did not specify a domain name for the root object — ask before generating anything.** Never invent a domain name on your own. The root domain name determines all type names, filenames, and folder names, so it must be confirmed by the user.
|
|
48
|
+
|
|
49
|
+
Every distinct nested object that carries its own data becomes its own `*Resource` + `*Entity` pair.
|
|
28
50
|
|
|
29
|
-
|
|
51
|
+
**Where to place nested domain files:** if a nested object only appears inside one parent domain, put its files **in the parent's folder** (e.g., `brand/brand-translation.ts`, `post/post-comment.ts`, `user/user-settings.ts`). Create a separate top-level folder only when the object appears across multiple domains or is clearly a standalone concept (e.g., `stored-file` that could be attached to many entities).
|
|
30
52
|
|
|
31
|
-
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Step 2 — Resource types (wire format)
|
|
56
|
+
|
|
57
|
+
**Rule: Resource field names must exactly match the backend.** Copy them verbatim — do not normalize, rename, or change casing.
|
|
32
58
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
59
|
+
```typescript
|
|
60
|
+
// Backend sends: { "first_name": "John", "UserAge": 30, "is-active": true }
|
|
61
|
+
type UserResource = BaseResource & {
|
|
62
|
+
'first_name'?: string // snake_case preserved as-is
|
|
63
|
+
'UserAge'?: number // PascalCase preserved as-is
|
|
64
|
+
'is-active'?: boolean // hyphenated keys must be quoted
|
|
65
|
+
}
|
|
66
|
+
```
|
|
40
67
|
|
|
41
|
-
|
|
68
|
+
Additional rules:
|
|
42
69
|
|
|
43
|
-
|
|
70
|
+
- All fields are **optional** (`?`) by default, unless the contract guarantees the field is always present.
|
|
71
|
+
- **Do not add `id`, `createdAt` / `updatedAt`, or `isPending`** to domain Resource types — these are already declared in `BaseResource` and inherited automatically.
|
|
72
|
+
- Nested objects → their own `*Resource` type in a sibling file, referenced here.
|
|
73
|
+
- Arrays of objects → `ChildResource[]`.
|
|
74
|
+
- String enums → `export enum` in the same file as the resource that owns the field.
|
|
75
|
+
- Import `BaseResource` from `../common` (or the resources barrel).
|
|
76
|
+
|
|
77
|
+
---
|
|
44
78
|
|
|
45
|
-
|
|
46
|
-
2. Property names on `*Resource` must **match the API** for each field (whether snake_case, camelCase, or mixed).
|
|
47
|
-
3. Do **not** normalize keys to a different convention “by habit” if the contract says otherwise.
|
|
48
|
-
4. `BaseResource` (or equivalent shared type) should include only fields that are **actually shared** across many DTOs, usually as **optional** members. If some responses omit `id` or timestamps, keep those optional or omit them from the base and declare per-resource.
|
|
49
|
-
5. A concrete `*Resource` may **intersect** with the base and add or refine fields (including refining `id` if the backend uses different scalar types per resource). Avoid types that claim keys the response never sends.
|
|
79
|
+
## Step 3 — Entity classes (always camelCase)
|
|
50
80
|
|
|
51
|
-
|
|
81
|
+
Every property in the Entity must be **camelCase**, regardless of the wire-format key.
|
|
52
82
|
|
|
53
|
-
###
|
|
83
|
+
### Case conversion table
|
|
54
84
|
|
|
55
|
-
|
|
85
|
+
| Wire format | Wire key example | Entity field |
|
|
86
|
+
| ---------------- | ----------------- | --------------- |
|
|
87
|
+
| snake_case | `created_at` | `createdAt` |
|
|
88
|
+
| snake_case | `first_name` | `firstName` |
|
|
89
|
+
| snake_case | `number_of_likes` | `numberOfLikes` |
|
|
90
|
+
| kebab-case | `user-name` | `userName` |
|
|
91
|
+
| kebab-case | `post-id` | `postId` |
|
|
92
|
+
| UPPER_SNAKE_CASE | `USER_NAME` | `userName` |
|
|
93
|
+
| UPPER_SNAKE_CASE | `POST_ID` | `postId` |
|
|
94
|
+
| camelCase | `firstName` | `firstName` |
|
|
95
|
+
| PascalCase | `FirstName` | `firstName` |
|
|
56
96
|
|
|
57
|
-
|
|
97
|
+
**Algorithm:** split the wire key on `_`, `-`, spaces, or case-boundary transitions (uppercase after lowercase) → lowercase the first segment, capitalize the first letter of every subsequent segment → join.
|
|
58
98
|
|
|
59
|
-
|
|
60
|
-
- Constructor accepts that optional `Resource`.
|
|
61
|
-
- Shared getters map from **the keys present on the shared resource fragment** to camelCase (e.g. wire `created_at` → getter `createdAt`), using optional chaining and sensible defaults only where the product expects them (e.g. dates, counts).
|
|
99
|
+
### Entity naming conventions
|
|
62
100
|
|
|
63
|
-
|
|
101
|
+
After converting to camelCase, apply these semantic rules:
|
|
64
102
|
|
|
65
|
-
|
|
103
|
+
#### Dates → must end in `at`
|
|
66
104
|
|
|
67
|
-
|
|
68
|
-
2. Add getters only for fields that exist on that resource type (plus inherited shared getters).
|
|
69
|
-
3. **Nested entities:** return `new ChildEntity(this.resource?.someKey)` (or map arrays with the same idea).
|
|
70
|
-
4. **Primitives:** optional chaining; use `??` with defaults where the UI needs a non-optional value (counts → `0`, flags → `false`, strings → `''` when appropriate).
|
|
105
|
+
Any field that represents a point in time must end with `at`.
|
|
71
106
|
|
|
72
|
-
|
|
107
|
+
```
|
|
108
|
+
created_at → createdAt ✓
|
|
109
|
+
updated_at → updatedAt ✓
|
|
110
|
+
publish_date → publishedAt ✓ (rename: "date" → "at")
|
|
111
|
+
expiry → expiredAt ✓ (rename: add "at" suffix)
|
|
112
|
+
birth_date → birthAt ✓
|
|
113
|
+
```
|
|
73
114
|
|
|
74
|
-
|
|
115
|
+
#### Arrays → must end in `list`
|
|
75
116
|
|
|
76
|
-
|
|
77
|
-
2. Export `export type {Domain}Resource = BaseResource & { ... }`.
|
|
78
|
-
3. Nested object → separate `*Resource` in its own module; arrays → `ChildResource[]`.
|
|
79
|
-
4. String unions / enums → `export type` aliases in the same file as the resource that owns the field.
|
|
117
|
+
All array properties in Entity end in `list`. Strip a trailing plural `s` (when it makes the base form a clean word) before appending `List`.
|
|
80
118
|
|
|
81
|
-
|
|
119
|
+
Resource keeps the backend wire name; Entity renames to `*List`.
|
|
82
120
|
|
|
83
|
-
|
|
121
|
+
```
|
|
122
|
+
// Resource field (wire name) Entity field (camelCase + list)
|
|
123
|
+
comments: PostCommentResource[] → commentList: PostCommentEntity[]
|
|
124
|
+
tags: string[] → tagList: string[]
|
|
125
|
+
user_posts: PostResource[] → userPostList: PostEntity[]
|
|
126
|
+
items: ItemResource[] → itemList: ItemEntity[]
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
If the backend already uses a `list`-style name (e.g., `comment_list`), the Entity field is simply the camelCase conversion: `commentList`.
|
|
130
|
+
|
|
131
|
+
#### Booleans → should start with `is`, `has`, `did`, `can`, `should`, `was`, `will`
|
|
132
|
+
|
|
133
|
+
```
|
|
134
|
+
is_email_verified → isEmailVerified ✓
|
|
135
|
+
verified → isVerified ✓ (add prefix when the boolean nature is clear)
|
|
136
|
+
email_confirmed → didConfirmEmail ✓
|
|
137
|
+
can_edit → canEdit ✓
|
|
138
|
+
has_access → hasAccess ✓
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Entity constructor pattern
|
|
142
|
+
|
|
143
|
+
**Do not re-declare or set `id`, `createdAt`, `updatedAt` in domain entities** — `BaseEntity` already handles them via `super(resource, context)`. Only add fields that are specific to this domain.
|
|
144
|
+
|
|
145
|
+
See `references/entities/common.ts` for the full `BaseEntity<T>` implementation and `references/entities/post/post.ts` for a complete domain example (with nested entity, array `→ List`, and cross-domain reference).
|
|
146
|
+
|
|
147
|
+
Default values by type:
|
|
148
|
+
| Type | Default |
|
|
149
|
+
|------------------|-----------------------|
|
|
150
|
+
| `string` | `''` |
|
|
151
|
+
| `number` | `0` |
|
|
152
|
+
| `boolean` | `false` |
|
|
153
|
+
| `Date` | `new Date()` |
|
|
154
|
+
| nested Entity | `new ChildEntity(resource?.field, this)` |
|
|
155
|
+
| array | `[]` |
|
|
156
|
+
|
|
157
|
+
---
|
|
84
158
|
|
|
85
|
-
##
|
|
159
|
+
## Step 4 — Barrel index files
|
|
86
160
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
161
|
+
Each `<domain>/` folder must have an `index.ts` re-exporting its modules. After adding a new domain, update the parent barrels too.
|
|
162
|
+
|
|
163
|
+
See `references/resources/post/index.ts`, `references/entities/post/index.ts`, and `references/resources/index.ts` for examples.
|
|
164
|
+
|
|
165
|
+
---
|
|
166
|
+
|
|
167
|
+
## Import conventions
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
// Inside a Resource file:
|
|
171
|
+
import { BaseResource } from '../common'
|
|
172
|
+
import { ChildResource } from './child-domain'
|
|
173
|
+
|
|
174
|
+
// Inside an Entity file:
|
|
175
|
+
import { BaseResource, DomainResource } from '../../resources'
|
|
176
|
+
import { BaseEntity } from '../common'
|
|
177
|
+
import { ChildEntity } from '../child-domain'
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Dependency direction is **entities → resources only**. Resources never import from entities.
|
|
181
|
+
|
|
182
|
+
---
|
|
93
183
|
|
|
94
184
|
## Export style
|
|
95
185
|
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
186
|
+
```typescript
|
|
187
|
+
// Resource files:
|
|
188
|
+
export type { DomainResource }
|
|
189
|
+
export { DomainStatusEnum } // enums use a regular (value) export
|
|
190
|
+
|
|
191
|
+
// Entity files:
|
|
192
|
+
export { DomainEntity }
|
|
193
|
+
|
|
194
|
+
// Index files:
|
|
195
|
+
export * from './domain'
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
---
|
|
199
|
+
|
|
200
|
+
## Checklist before finishing
|
|
201
|
+
|
|
202
|
+
- [ ] Every Resource field name matches the backend wire format exactly (no renaming)
|
|
203
|
+
- [ ] Every Entity property is camelCase
|
|
204
|
+
- [ ] All date fields in Entity end in `at`
|
|
205
|
+
- [ ] All array fields in Entity end in `list`
|
|
206
|
+
- [ ] All boolean fields in Entity start with `is` / `has` / `did` / `can` / `should` / `was` / `will`
|
|
207
|
+
- [ ] Nested objects have their own `*Resource` + `*Entity` files
|
|
208
|
+
- [ ] Each domain folder has an `index.ts` barrel
|
|
209
|
+
- [ ] Parent `resources/index.ts` and `entities/index.ts` updated
|
|
210
|
+
|
|
211
|
+
---
|
|
212
|
+
|
|
213
|
+
## Reference examples
|
|
214
|
+
|
|
215
|
+
Read these files to understand the complete, working implementation:
|
|
216
|
+
|
|
217
|
+
| File | What it shows |
|
|
218
|
+
| -------------------------------------------- | ----------------------------------------------------------------------------- |
|
|
219
|
+
| `references/resources/common.ts` | `BaseResource` type — `id`, `created_at`, `updated_at`, `is_pending` |
|
|
220
|
+
| `references/entities/common.ts` | `BaseEntity<T>` — `generateIdFactory`, `isPending` getter, `super()` contract |
|
|
221
|
+
| `references/resources/post/post.ts` | Resource with snake_case fields, nested types, cross-domain ref |
|
|
222
|
+
| `references/entities/post/post.ts` | Entity with camelCase, `commentList`, `numberOfLikes`, nested `UserEntity` |
|
|
223
|
+
| `references/resources/post/post-comment.ts` | Minimal nested Resource inside a parent domain folder |
|
|
224
|
+
| `references/entities/post/post-comment.ts` | Minimal nested Entity inside a parent domain folder |
|
|
225
|
+
| `references/resources/user/user.ts` | Multi-file domain Resource with two nested sub-resources |
|
|
226
|
+
| `references/entities/user/user.ts` | Multi-file domain Entity with two nested sub-entities |
|
|
227
|
+
| `references/resources/user/user-settings.ts` | Leaf Resource (no further nesting) |
|
|
228
|
+
| `references/entities/user/user-settings.ts` | Leaf Entity (no further nesting) |
|
|
229
|
+
| `references/resources/post/index.ts` | Domain barrel — re-exports all files in the folder |
|
|
230
|
+
| `references/resources/index.ts` | Root barrel — re-exports all domains + common |
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
import { generateIdFactory } from '@mediacontent/base/utils'
|
|
2
|
+
import { type BaseResource } from '../resources'
|
|
3
|
+
|
|
4
|
+
const generateId = generateIdFactory()
|
|
5
|
+
|
|
6
|
+
class BaseEntity<Resource extends BaseResource> {
|
|
7
|
+
protected readonly resource?: Resource
|
|
8
|
+
protected readonly context?: BaseResource
|
|
9
|
+
|
|
10
|
+
id: string
|
|
11
|
+
createdAt: Date
|
|
12
|
+
updatedAt: Date
|
|
13
|
+
|
|
14
|
+
constructor(resource?: Resource, context?: BaseResource) {
|
|
15
|
+
this.resource = resource
|
|
16
|
+
this.context = context
|
|
17
|
+
|
|
18
|
+
this.id = resource?.id ?? generateId()
|
|
19
|
+
this.createdAt = resource?.created_at ?? new Date()
|
|
20
|
+
this.updatedAt = resource?.updated_at ?? new Date()
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
get isPending() {
|
|
24
|
+
return this.resource?.is_pending ?? this.context?.is_pending ?? false
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export { BaseEntity }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BaseResource, type PostCommentResource } from '../../resources'
|
|
2
|
+
import { BaseEntity } from '../common'
|
|
3
|
+
|
|
4
|
+
class PostCommentEntity extends BaseEntity<PostCommentResource> {
|
|
5
|
+
description: string
|
|
6
|
+
numberOfLikes: number
|
|
7
|
+
|
|
8
|
+
constructor(resource?: PostCommentResource, context?: BaseResource) {
|
|
9
|
+
super(resource, context)
|
|
10
|
+
|
|
11
|
+
this.description = resource?.description ?? '-'
|
|
12
|
+
this.numberOfLikes = resource?.number_of_likes ?? 0
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { PostCommentEntity }
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { type BaseResource, type PostResource } from '../../resources'
|
|
2
|
+
import { BaseEntity } from '../common'
|
|
3
|
+
import { UserEntity } from '../user'
|
|
4
|
+
import { PostCommentEntity } from './post-comment'
|
|
5
|
+
|
|
6
|
+
class PostEntity extends BaseEntity<PostResource> {
|
|
7
|
+
title: string
|
|
8
|
+
description: string
|
|
9
|
+
numberOfLikes: number
|
|
10
|
+
numberOfComments: number
|
|
11
|
+
user: UserEntity
|
|
12
|
+
commentList: PostCommentEntity[]
|
|
13
|
+
|
|
14
|
+
constructor(resource?: PostResource, context?: BaseResource) {
|
|
15
|
+
super(resource, context)
|
|
16
|
+
|
|
17
|
+
this.title = resource?.title ?? ''
|
|
18
|
+
this.description = resource?.description ?? ''
|
|
19
|
+
this.numberOfLikes = resource?.number_of_likes ?? 0
|
|
20
|
+
this.numberOfComments = resource?.number_of_comments ?? 0
|
|
21
|
+
this.user = new UserEntity(resource?.user, this)
|
|
22
|
+
this.commentList =
|
|
23
|
+
resource?.comments?.map((comment) => new PostCommentEntity(comment, this)) ?? []
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export { PostEntity }
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type BaseResource, type UserSettingsResource } from '../../resources'
|
|
2
|
+
import { BaseEntity } from '../common'
|
|
3
|
+
|
|
4
|
+
class UserSettingsEntity extends BaseEntity<UserSettingsResource> {
|
|
5
|
+
language: string
|
|
6
|
+
theme: string
|
|
7
|
+
|
|
8
|
+
constructor(resource?: UserSettingsResource, context?: BaseResource) {
|
|
9
|
+
super(resource, context)
|
|
10
|
+
|
|
11
|
+
this.language = resource?.language ?? 'en'
|
|
12
|
+
this.theme = resource?.theme ?? 'system'
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export { UserSettingsEntity }
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type BaseResource,
|
|
3
|
+
type UserVerificationResource,
|
|
4
|
+
UserVerificationStatus,
|
|
5
|
+
} from '../../resources'
|
|
6
|
+
import { BaseEntity } from '../common'
|
|
7
|
+
|
|
8
|
+
class UserVerificationEntity extends BaseEntity<UserVerificationResource> {
|
|
9
|
+
isEmailVerified: boolean
|
|
10
|
+
isPhoneVerified: boolean
|
|
11
|
+
status: UserVerificationStatus
|
|
12
|
+
|
|
13
|
+
constructor(resource?: UserVerificationResource, context?: BaseResource) {
|
|
14
|
+
super(resource, context)
|
|
15
|
+
|
|
16
|
+
this.isEmailVerified = resource?.is_email_verified ?? false
|
|
17
|
+
this.isPhoneVerified = resource?.is_phone_verified ?? false
|
|
18
|
+
this.status = resource?.status ?? UserVerificationStatus.Pending
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export { UserVerificationEntity }
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { type BaseResource, type UserResource } from '../../resources'
|
|
2
|
+
import { BaseEntity } from '../common'
|
|
3
|
+
import { UserSettingsEntity } from './user-settings'
|
|
4
|
+
import { UserVerificationEntity } from './user-verification'
|
|
5
|
+
|
|
6
|
+
class UserEntity extends BaseEntity<UserResource> {
|
|
7
|
+
firstName: string
|
|
8
|
+
lastName: string
|
|
9
|
+
fullName: string
|
|
10
|
+
avatar: string
|
|
11
|
+
verification: UserVerificationEntity
|
|
12
|
+
settings: UserSettingsEntity
|
|
13
|
+
|
|
14
|
+
constructor(resource?: UserResource, context?: BaseResource) {
|
|
15
|
+
super(resource, context)
|
|
16
|
+
|
|
17
|
+
this.firstName = resource?.first_name ?? 'UNKNOWN'
|
|
18
|
+
this.lastName = resource?.last_name ?? 'UNKNOWN'
|
|
19
|
+
this.fullName = `${resource?.first_name} ${resource?.last_name}`.trim() ?? 'UNKNOWN'
|
|
20
|
+
this.avatar = resource?.avatar ?? ''
|
|
21
|
+
this.verification = new UserVerificationEntity(resource?.verification, this)
|
|
22
|
+
this.settings = new UserSettingsEntity(resource?.settings, this)
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export { UserEntity }
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { type BaseResource } from '../common'
|
|
2
|
+
import { type UserResource } from '../user'
|
|
3
|
+
import { type PostCommentResource } from './post-comment'
|
|
4
|
+
|
|
5
|
+
type PostResource = BaseResource & {
|
|
6
|
+
title?: string
|
|
7
|
+
description?: string
|
|
8
|
+
number_of_likes?: number
|
|
9
|
+
number_of_comments?: number
|
|
10
|
+
user?: UserResource
|
|
11
|
+
comments?: PostCommentResource[]
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export type { PostResource }
|