@mediacubeco/expo-template-fsd 1.2.1 → 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.
Files changed (75) hide show
  1. package/.claude/rules/app-routing.mdc +5 -5
  2. package/.claude/rules/mediacubeco-packages.mdc +56 -0
  3. package/.claude/settings.json +11 -0
  4. package/.claude/settings.local.json +11 -0
  5. package/.claude/skills/code-style/SKILL.md +1 -0
  6. package/.claude/skills/generate-api-model/SKILL.md +193 -61
  7. package/.claude/skills/generate-api-model/references/entities/common.ts +28 -0
  8. package/.claude/skills/generate-api-model/references/entities/index.ts +4 -0
  9. package/.claude/skills/generate-api-model/references/entities/post/index.ts +2 -0
  10. package/.claude/skills/generate-api-model/references/entities/post/post-comment.ts +16 -0
  11. package/.claude/skills/generate-api-model/references/entities/post/post.ts +27 -0
  12. package/.claude/skills/generate-api-model/references/entities/user/index.ts +3 -0
  13. package/.claude/skills/generate-api-model/references/entities/user/user-settings.ts +16 -0
  14. package/.claude/skills/generate-api-model/references/entities/user/user-verification.ts +22 -0
  15. package/.claude/skills/generate-api-model/references/entities/user/user.ts +26 -0
  16. package/.claude/skills/generate-api-model/references/resources/common.ts +8 -0
  17. package/.claude/skills/generate-api-model/references/resources/index.ts +4 -0
  18. package/.claude/skills/generate-api-model/references/resources/post/index.ts +2 -0
  19. package/.claude/skills/generate-api-model/references/resources/post/post-comment.ts +8 -0
  20. package/.claude/skills/generate-api-model/references/resources/post/post.ts +14 -0
  21. package/.claude/skills/generate-api-model/references/resources/user/index.ts +3 -0
  22. package/.claude/skills/generate-api-model/references/resources/user/user-settings.ts +8 -0
  23. package/.claude/skills/generate-api-model/references/resources/user/user-verification.ts +16 -0
  24. package/.claude/skills/generate-api-model/references/resources/user/user.ts +13 -0
  25. package/.claude/skills/git-commit/SKILL.md +139 -0
  26. package/.env +0 -1
  27. package/.env.example +0 -1
  28. package/CLAUDE.md +80 -0
  29. package/env.d.ts +0 -1
  30. package/expo-env.d.ts +3 -0
  31. package/i18next.d.ts +1 -1
  32. package/package.json +42 -41
  33. package/skills-lock.json +29 -0
  34. package/src/app/_app.tsx +2 -0
  35. package/src/shared/assets/icons/like.tsx +1 -1
  36. package/src/shared/constants/environment.ts +1 -7
  37. package/src/shared/hooks/use-animated.ts +1 -1
  38. package/src/shared/hooks/use-safe-area.ts +2 -2
  39. package/src/shared/lib/api/api.provider.tsx +2 -2
  40. package/src/shared/lib/api/api.types.ts +1 -1
  41. package/src/shared/lib/api/entities/common.ts +5 -5
  42. package/src/shared/lib/api/hooks/common.ts +1 -1
  43. package/src/shared/lib/api/models/api-instance/api-instance.ts +9 -9
  44. package/src/shared/lib/api/models/api-instance/api-instance.types.ts +1 -1
  45. package/src/shared/lib/api/models/api-instance/api-instance.utils.ts +4 -4
  46. package/src/shared/lib/api/repositories/auth/auth.ts +6 -1
  47. package/src/shared/lib/api/repositories/auth/auth.types.ts +1 -1
  48. package/src/shared/lib/api/resources/common.ts +3 -3
  49. package/src/shared/lib/localization/constants/index.ts +1 -1
  50. package/src/shared/lib/localization/hooks/use-localization.ts +1 -1
  51. package/src/shared/lib/localization/localization.provider.tsx +1 -1
  52. package/src/shared/lib/localization/localization.types.ts +1 -1
  53. package/src/shared/lib/localization/utils/storage.ts +1 -1
  54. package/src/shared/lib/navigation/hooks/use-navigation-options.ts +7 -6
  55. package/src/shared/lib/navigation/navigation.provider.tsx +2 -2
  56. package/src/shared/lib/navigation/navigation.types.ts +1 -1
  57. package/src/shared/lib/theme/theme.context.ts +1 -1
  58. package/src/shared/lib/theme/theme.provider.tsx +21 -6
  59. package/src/shared/lib/theme/theme.types.ts +2 -2
  60. package/src/shared/lib/theme/utils/storage.ts +1 -1
  61. package/src/shared/ui/molecules/keyboard-area/keyboard-area.tsx +1 -1
  62. package/src/shared/ui/molecules/refresh-control/refresh-control.tsx +1 -1
  63. package/src/shared/ui/molecules/refresh-control/refresh-control.types.ts +1 -1
  64. package/src/shared/ui/templates/screen-container/screen-container-list.tsx +1 -1
  65. package/src/shared/ui/templates/screen-container/screen-container-scroll.tsx +1 -1
  66. package/src/shared/ui/templates/screen-container/screen-container-section-list.tsx +1 -1
  67. package/src/shared/ui/templates/screen-container/screen-container.hooks.tsx +3 -3
  68. package/src/shared/ui/templates/screen-container/screen-container.styles.ts +1 -1
  69. package/src/shared/ui/templates/screen-container/screen-container.types.tsx +13 -13
  70. package/svg.d.ts +1 -1
  71. package/.claude/mcp.json +0 -0
  72. package/.claude/rules/abstract-skills-agents.mdc +0 -25
  73. package/src/shared/utils/generators.ts +0 -11
  74. package/src/shared/utils/index.ts +0 -1
  75. /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 ~screens
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 `~screens/*` (or other features), not in `app`.
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 `~screens/...` (import a single root screen component).
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 `~screens` import.
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 '~screens/some-feature'
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
+ ```
@@ -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,11 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Read(//Users/alkhimenok/Public/Programing/Work/kit/**)",
5
+ "Bash(npm view *)",
6
+ "Bash(npx expo *)",
7
+ "Bash(npx expo-doctor *)",
8
+ "Bash(pnpm install *)"
9
+ ]
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 resource types and entity classes following project conventions.
5
- Use when creating new API models, entities, resources, or data types, or when
6
- the user asks to add a new API model, entity, or resource type.
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
- ## Overview
14
+ ## What this skill does
12
15
 
13
- For each **domain** object from the API, add:
16
+ Take a **backend JSON response** (or API contract) and produce two TypeScript artifacts per domain object:
14
17
 
15
- - **Resource** — a TypeScript type describing the DTO as returned on the wire (JSON shape).
16
- - **Entity** — a class that wraps that resource and exposes **camelCase** getters for the UI and domain logic.
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
- Do **not** generate resource/entity pairs for generic HTTP envelopes by default (paginated wrappers, transport-only error shapes, etc.). Add those only if the task explicitly asks for response-layer types.
21
+ ## Filesystem layout
19
22
 
20
- ## Where things live
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
- Use the project’s shared API library: sibling trees **`resources`** (DTO types) and **`entities`** (classes). In this repo they sit under the same parent folder as `models`, `repositories`, etc.; domain folders are **kebab-case** (see workspace naming rules).
39
+ Domain folder names are **kebab-case**. See `references/` for complete working examples.
23
40
 
24
- | Layer | Typical layout |
25
- | -------- | ------------------------------------------------------------------------------------------ |
26
- | Resource | `<api-lib>/resources/<domain>/<domain>.ts` plus `resources/common.ts` for shared fragments |
27
- | Entity | `<api-lib>/entities/<domain>/<domain>.ts` plus `entities/common.ts` for the base class |
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
- Each `<domain>/` folder has an `index.ts` that re-exports its modules. After adding a domain, export it from the **barrel** `resources/index.ts` and `entities/index.ts` (same pattern as existing domains).
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
- ## Naming
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
- | Item | Convention |
34
- | --------------- | ------------------------------------------------------------------------------------------------------------- |
35
- | Folder / file | kebab-case; primary domain module is `<domain>.ts`, shared cross-domain fragments use a name like `common.ts` |
36
- | Resource type | `{Domain}Resource` (and `{Domain}{Aspect}Resource` for small unions or nested DTOs owned by that file) |
37
- | Entity class | `{Domain}Entity` |
38
- | Resource fields | **Match the wire format** — same spelling and casing as the API for each property |
39
- | Entity getters | camelCase |
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
- Nested domain objects get their own `<nested-domain>/` with `*Resource` + `*Entity`, then referenced from the parent resource/entity.
68
+ Additional rules:
42
69
 
43
- ## Wire format (resources)
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
- 1. Take field names from the real JSON, OpenAPI schema, or agreed contract.
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
- ## Base types
81
+ Every property in the Entity must be **camelCase**, regardless of the wire-format key.
52
82
 
53
- ### Shared resource fragment (`resources/common.ts`)
83
+ ### Case conversion table
54
84
 
55
- Export a minimal **type** (e.g. `BaseResource`) for identifiers and audit columns **in the naming the backend uses** for those shared fields.
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
- ### Base entity (`entities/common.ts`)
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
- - Define a **generic** base class, e.g. `BaseEntity<Resource extends BaseResource>`, with a `protected` optional reference to the payload (commonly `resource`).
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
- Subclasses: `export class {Domain}Entity extends BaseEntity<{Domain}Resource>` rely on the generic parameter for typing `this.resource`; **do not** duplicate the pattern from older guides that reassign a separate typed field after `super` unless this codebase already does that elsewhere.
101
+ After converting to camelCase, apply these semantic rules:
64
102
 
65
- ## Entity subclasses
103
+ #### Dates → must end in `at`
66
104
 
67
- 1. Extend `BaseEntity<{Domain}Resource>`.
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
- Getter names are always camelCase; the **property access** inside uses the actual keys declared on `*Resource`.
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
- ## Resource module rules
115
+ #### Arrays must end in `list`
75
116
 
76
- 1. Import the shared fragment from the resources `common` module (relative path depends on depth).
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
- ## Imports between layers
119
+ Resource keeps the backend wire name; Entity renames to `*List`.
82
120
 
83
- Entities depend on resource types (import from the resources tree or its barrel). Keep dependency direction **resources → nothing in entities**; **entities → resources** only.
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
- ## Workflow
159
+ ## Step 4 — Barrel index files
86
160
 
87
- 1. **Scope** list domain DTOs only; exclude wrappers unless requested.
88
- 2. **Wire names** — copy property names from the contract into `*Resource`.
89
- 3. **Align the shared resource fragment** — smallest common optional shape; refine per domain where needed.
90
- 4. **Resources first, bottom-up** — leaf nested DTOs, then parents.
91
- 5. **Entities** — one class per resource; generic `BaseEntity<{Domain}Resource>`; nest `new …Entity(…)`.
92
- 6. **Barrels** — `index.ts` per domain folder and parent `resources/index.ts` / `entities/index.ts`.
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
- - Resource types: `export type { … }`
97
- - Entity classes: `export class …`
98
- - Index files: `export * from './<module>'`
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,4 @@
1
+ export * from './common'
2
+
3
+ export * from './post'
4
+ export * from './user'
@@ -0,0 +1,2 @@
1
+ export * from './post'
2
+ export * from './post-comment'
@@ -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,3 @@
1
+ export * from './user'
2
+ export * from './user-settings'
3
+ export * from './user-verification'
@@ -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,8 @@
1
+ type BaseResource = {
2
+ id?: string
3
+ created_at?: Date
4
+ updated_at?: Date
5
+ is_pending?: boolean
6
+ }
7
+
8
+ export type { BaseResource }
@@ -0,0 +1,4 @@
1
+ export * from './common'
2
+
3
+ export * from './user'
4
+ export * from './post'
@@ -0,0 +1,2 @@
1
+ export * from './post'
2
+ export * from './post-comment'
@@ -0,0 +1,8 @@
1
+ import { type BaseResource } from '../common'
2
+
3
+ type PostCommentResource = BaseResource & {
4
+ description: string
5
+ number_of_likes: number
6
+ }
7
+
8
+ export type { PostCommentResource }
@@ -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 }
@@ -0,0 +1,3 @@
1
+ export * from './user'
2
+ export * from './user-settings'
3
+ export * from './user-verification'
@@ -0,0 +1,8 @@
1
+ import { type BaseResource } from '../common'
2
+
3
+ type UserSettingsResource = BaseResource & {
4
+ language?: string
5
+ theme?: string
6
+ }
7
+
8
+ export type { UserSettingsResource }