runlify 0.0.794 → 0.0.796
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/build/cli.js +8 -2
- package/build/projectsGeneration/builders/generationPaths.js +44 -1
- package/build/projectsGeneration/generateProject/back/src/generateBackSrc.js +11 -2
- package/build/projectsGeneration/generateProject/fileHandlers.js +6 -2
- package/build/projectsGeneration/generateProject/front/generateFrontSrc.js +10 -1
- package/build/projectsGeneration/generateProject/front/translations/generateFrontSrcTranslations.js +33 -1
- package/build/projectsGeneration/generateProject/generateBackClickHouseBootstrap.js +1 -4
- package/build/projectsGeneration/generateProject/utils.js +2 -2
- package/build/projectsGeneration/generators/fileTemplates/back/clickhouse/jobs.js +41 -4
- package/build/projectsGeneration/generators/fileTemplates/back/roles/customPermissions.js +12 -0
- package/build/projectsGeneration/generators/fileTemplates/back/roles/customUiPermissions.js +13 -0
- package/build/projectsGeneration/generators/fileTemplates/back/roles/initPermissions.js +31 -0
- package/build/projectsGeneration/generators/fileTemplates/back/roles/uiPermissions.js +24 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/NotFoundPage.js +6 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/PermissionPage.js +6 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/environment/src/routes.js +5 -5
- package/build/projectsGeneration/generators/fileTemplates/ui/getDefaultMenu.js +5 -1
- package/build/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nIndexTmpl.js +35 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nTypesTmpl.js +33 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nValidationTmpl.js +38 -0
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityCreate/DefaultEntityCreate.js +16 -5
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityEdit/DefaultEntityEdit.js +27 -11
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityList/DefaultEntityFilter.js +13 -4
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityList/DefaultEntityList.js +14 -7
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityShow/DefaultMainTab.js +13 -4
- package/build/projectsGeneration/generators/fileTemplates/ui/pages/EntityShow/DependencyTab.js +12 -3
- package/build/projectsGeneration/generators/fileTemplates/ui/resources.js +21 -8
- package/build/projectsGeneration/generators/fileTemplates/ui/utils/permissions.js +89 -0
- package/build/projectsGeneration/generators/ui/getShowComponent.js +2 -2
- package/build/types/projectsGeneration/builders/generationPaths.d.ts +42 -0
- package/build/types/projectsGeneration/generateProject/fileHandlers.d.ts +1 -0
- package/build/types/projectsGeneration/generateProject/types.d.ts +1 -1
- package/build/types/projectsGeneration/generateProject/utils.d.ts +1 -1
- package/build/types/projectsGeneration/generators/fileTemplates/back/roles/customPermissions.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/back/roles/customUiPermissions.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/back/roles/initPermissions.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/back/roles/uiPermissions.d.ts +3 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/NotFoundPage.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/PermissionPage.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nIndexTmpl.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nTypesTmpl.d.ts +3 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/i18n/skeleton/uiI18nValidationTmpl.d.ts +2 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/pages/EntityEdit/DefaultEntityEdit.d.ts +1 -0
- package/build/types/projectsGeneration/generators/fileTemplates/ui/utils/permissions.d.ts +2 -0
- package/docs/01-overview.md +172 -0
- package/docs/02-system-meta-builder.md +405 -0
- package/docs/03-entity-types.md +471 -0
- package/docs/04-fields.md +357 -0
- package/docs/05-custom-methods.md +249 -0
- package/docs/06-storage.md +129 -0
- package/docs/07-backend-file-graph.md +231 -0
- package/docs/08-frontend-file-graph.md +260 -0
- package/docs/09-options.md +213 -0
- package/docs/10-modules.md +164 -0
- package/docs/11-permissions.md +231 -0
- package/docs/INDEX.md +70 -0
- package/package.json +1 -1
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
# Storage
|
|
2
|
+
|
|
3
|
+
> **Load this file when:** you need to choose or change the storage engine for an entity.
|
|
4
|
+
>
|
|
5
|
+
> Related: [03-entity-types.md#storage](./03-entity-types.md#storage) ·
|
|
6
|
+
> [09-options.md](./09-options.md)
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## StorageType values
|
|
11
|
+
|
|
12
|
+
Import: `import { Storage, StorageType } from 'runlify'`
|
|
13
|
+
|
|
14
|
+
| Constant | String value | What it means |
|
|
15
|
+
|----------|-------------|---------------|
|
|
16
|
+
| `Storage.POSTGRES` | `'postgres'` | PostgreSQL only. Default for all entities. |
|
|
17
|
+
| `Storage.ELASTIC` | `'elastic'` | Elasticsearch only. No Prisma model generated. |
|
|
18
|
+
| `Storage.CLICKHOUSE` | `'clickhouse'` | ClickHouse only. No Prisma model generated. |
|
|
19
|
+
| `Storage.POSTGRES_WITH_ELASTIC_SEARCH` | `'postgres_with_elastic_search'` | PostgreSQL as primary store + Elasticsearch for search queries |
|
|
20
|
+
| `Storage.POSTGRES_WITH_CLICKHOUSE_SEARCH` | `'postgres_with_clickhouse_search'` | PostgreSQL as primary store + ClickHouse for search queries |
|
|
21
|
+
|
|
22
|
+
---
|
|
23
|
+
|
|
24
|
+
## How to set storage
|
|
25
|
+
|
|
26
|
+
```ts
|
|
27
|
+
entity.setStorage(Storage.POSTGRES_WITH_ELASTIC_SEARCH)
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
The storage must be set **before** adding fields, because some storage types force
|
|
31
|
+
the `id` type to `string`.
|
|
32
|
+
|
|
33
|
+
---
|
|
34
|
+
|
|
35
|
+
## Side effects of `setStorage()`
|
|
36
|
+
|
|
37
|
+
| Storage | Forces `id` type | Creates external tracking entity | Notes |
|
|
38
|
+
|---------|-----------------|----------------------------------|-------|
|
|
39
|
+
| `POSTGRES` | No | No | Default |
|
|
40
|
+
| `ELASTIC` | `string` (cuid) | No | No Prisma schema |
|
|
41
|
+
| `CLICKHOUSE` | `string` (cuid) | No | No Prisma schema |
|
|
42
|
+
| `POSTGRES_WITH_ELASTIC_SEARCH` | `string` (cuid) | Yes: `external<Entity>SearchTrackings` | Dual-write: PG + ES |
|
|
43
|
+
| `POSTGRES_WITH_CLICKHOUSE_SEARCH` | `string` (cuid) | Yes: `external<Entity>SearchTrackings` | Dual-write: PG + CH |
|
|
44
|
+
|
|
45
|
+
The `external<Entity>SearchTrackings` catalog is auto-generated and tracks sync state
|
|
46
|
+
between Postgres and the external search engine.
|
|
47
|
+
|
|
48
|
+
---
|
|
49
|
+
|
|
50
|
+
## Infrastructure requirements
|
|
51
|
+
|
|
52
|
+
Setting a non-Postgres storage triggers bootstrap generation:
|
|
53
|
+
|
|
54
|
+
| Storage contains | Generated bootstrap |
|
|
55
|
+
|-----------------|---------------------|
|
|
56
|
+
| `elastic` or `postgres_with_elastic_search` | `generateBackElasticBootstrap` — ES client init, index creation jobs |
|
|
57
|
+
| `clickhouse` or `postgres_with_clickhouse_search` | `generateBackClickHouseBootstrap` — CH client init, table creation jobs |
|
|
58
|
+
|
|
59
|
+
These bootstraps require the corresponding config vars to be set:
|
|
60
|
+
- Elasticsearch: `es.enabled`, `es.node` (or `es.cloudId` + `es.username` + `es.password`)
|
|
61
|
+
- ClickHouse: `ch.enabled`, `ch.host`, `ch.port`, `ch.database`, `ch.username`, `ch.password`
|
|
62
|
+
|
|
63
|
+
---
|
|
64
|
+
|
|
65
|
+
## Choosing the right storage
|
|
66
|
+
|
|
67
|
+
| Scenario | Recommended storage |
|
|
68
|
+
|----------|---------------------|
|
|
69
|
+
| Standard CRUD entity | `Storage.POSTGRES` |
|
|
70
|
+
| Entity with full-text search needs, Postgres as source of truth | `Storage.POSTGRES_WITH_ELASTIC_SEARCH` |
|
|
71
|
+
| Analytical / time-series data with heavy aggregation | `Storage.POSTGRES_WITH_CLICKHOUSE_SEARCH` or `Storage.CLICKHOUSE` |
|
|
72
|
+
| Pure analytics table, no CRUD | `Storage.CLICKHOUSE` |
|
|
73
|
+
| Pure search index, no relational data | `Storage.ELASTIC` |
|
|
74
|
+
|
|
75
|
+
---
|
|
76
|
+
|
|
77
|
+
## Helper functions (exported from `runlify`)
|
|
78
|
+
|
|
79
|
+
```ts
|
|
80
|
+
isStoragePostgres(storage) // true for POSTGRES, PG+ES, PG+CH
|
|
81
|
+
isStorageElasticOnly(storage) // true only for ELASTIC
|
|
82
|
+
isStorageClickHouseOnly(storage) // true only for CLICKHOUSE
|
|
83
|
+
isStorageExternalSearch(storage) // true for PG+ES, PG+CH
|
|
84
|
+
isStorageElasticSearch(storage) // true for ELASTIC, PG+ES
|
|
85
|
+
isStorageClickHouseSearch(storage) // true for CLICKHOUSE, PG+CH
|
|
86
|
+
getSearchEngine(storage) // returns 'elastic' | 'clickhouse' | null
|
|
87
|
+
usesPrismaDelegate(storage) // false only for ELASTIC and CLICKHOUSE
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
## Anti-patterns
|
|
93
|
+
|
|
94
|
+
### Setting `id` type after setting external search storage
|
|
95
|
+
|
|
96
|
+
**Wrong:**
|
|
97
|
+
```ts
|
|
98
|
+
entity.setStorage(Storage.POSTGRES_WITH_ELASTIC_SEARCH)
|
|
99
|
+
entity.getKey().setType('int') // silently overrides forced 'string' type
|
|
100
|
+
```
|
|
101
|
+
|
|
102
|
+
**Why:** `setStorage()` with external-search storage forces `id` to `string` (cuid).
|
|
103
|
+
Overriding it to `int` or `bigint` breaks the external search sync because search engine
|
|
104
|
+
IDs must be strings.
|
|
105
|
+
|
|
106
|
+
**Correct:** do not override the key type when using external search storage.
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
### Using `ELASTIC` or `CLICKHOUSE` for entities that need relational joins
|
|
111
|
+
|
|
112
|
+
**Wrong:**
|
|
113
|
+
```ts
|
|
114
|
+
const orders = system.addDocument('orders')
|
|
115
|
+
orders.setStorage(Storage.ELASTIC) // no Prisma model, no FK support
|
|
116
|
+
orders.addLinkField('users', 'userId') // FK has nowhere to land
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Why:** `ELASTIC` and `CLICKHOUSE` storages do not generate a Prisma model, so foreign
|
|
120
|
+
keys and relational queries are not supported.
|
|
121
|
+
|
|
122
|
+
**Correct:** use `POSTGRES_WITH_ELASTIC_SEARCH` to keep Postgres as the relational
|
|
123
|
+
source of truth while gaining search capabilities.
|
|
124
|
+
|
|
125
|
+
---
|
|
126
|
+
|
|
127
|
+
> **Examples:** this file intentionally omits code examples. Add real usage examples
|
|
128
|
+
> from the consuming project here. A reference metadata file is typically at
|
|
129
|
+
> `src/meta/metadata.ts` in the meta project.
|
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
# Backend File Graph (`<prefix>-back`)
|
|
2
|
+
|
|
3
|
+
> **Load this file when:** you need to find a specific file in the generated backend,
|
|
4
|
+
> understand which files are safe to edit, or know what gets regenerated.
|
|
5
|
+
>
|
|
6
|
+
> Legend: `[gen]` = regenerated every `regen` (DO NOT EDIT) · `[yours]` = created once,
|
|
7
|
+
> yours to implement · `[once]` = created once by Runlify, rarely needs changes
|
|
8
|
+
>
|
|
9
|
+
> Related: [01-overview.md](./01-overview.md) · [08-frontend-file-graph.md](./08-frontend-file-graph.md)
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Full tree
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
<prefix>-back/
|
|
17
|
+
│
|
|
18
|
+
├── src/
|
|
19
|
+
│ │
|
|
20
|
+
│ ├── config/
|
|
21
|
+
│ │ └── config.ts [gen] Runtime config loader (reads env JSON)
|
|
22
|
+
│ │
|
|
23
|
+
│ ├── adm/
|
|
24
|
+
│ │ │
|
|
25
|
+
│ │ ├── graph/
|
|
26
|
+
│ │ │ ├── permissionsToGraphql.ts [gen] Permission → GraphQL mapping
|
|
27
|
+
│ │ │ ├── additionalResolvers.ts [gen] Wires additional service resolvers
|
|
28
|
+
│ │ │ ├── additionalTypes.ts [gen] Additional GraphQL type definitions
|
|
29
|
+
│ │ │ │
|
|
30
|
+
│ │ │ └── <EntityName>/ [gen] Per-entity GraphQL folder
|
|
31
|
+
│ │ │ ├── resolvers.ts [gen] CRUD resolvers
|
|
32
|
+
│ │ │ ├── types.ts [gen] GraphQL type definitions
|
|
33
|
+
│ │ │ └── permissionsToGraphql.ts [gen] Entity permission mappings
|
|
34
|
+
│ │ │
|
|
35
|
+
│ │ └── services/
|
|
36
|
+
│ │ └── <EntityName>Service/ One folder per entity
|
|
37
|
+
│ │ │
|
|
38
|
+
│ │ ├── <EntityName>Service.ts [gen] Base service class — DO NOT EDIT
|
|
39
|
+
│ │ ├── Additional<Entity>Service.ts [yours] Your custom methods + overrides
|
|
40
|
+
│ │ ├── config.ts [gen] Service configuration
|
|
41
|
+
│ │ ├── initBuiltInHooks.ts [gen] Wires built-in framework hooks
|
|
42
|
+
│ │ ├── initUserHooks.ts [yours] Wire your custom hooks here
|
|
43
|
+
│ │ │
|
|
44
|
+
│ │ └── hooks/
|
|
45
|
+
│ │ ├── beforeCreate.ts [yours] Runs before INSERT
|
|
46
|
+
│ │ ├── afterCreate.ts [yours] Runs after INSERT
|
|
47
|
+
│ │ ├── beforeUpdate.ts [yours] Runs before UPDATE
|
|
48
|
+
│ │ ├── afterUpdate.ts [yours] Runs after UPDATE
|
|
49
|
+
│ │ ├── beforeDelete.ts [yours] Runs before DELETE
|
|
50
|
+
│ │ ├── afterDelete.ts [yours] Runs after DELETE
|
|
51
|
+
│ │ ├── beforeUpsert.ts [yours] Runs before UPSERT
|
|
52
|
+
│ │ ├── additionalOperationsOnCreate.ts [yours] Side-effects after create
|
|
53
|
+
│ │ ├── additionalOperationsOnUpdate.ts [yours] Side-effects after update
|
|
54
|
+
│ │ ├── additionalOperationsOnDelete.ts [yours] Side-effects after delete
|
|
55
|
+
│ │ ├── changeListFilter.ts [yours] Mutate the list query filter at runtime
|
|
56
|
+
│ │ └── tenantIdRequiredHooks.ts [yours] Tenant isolation (only if multitenancy enabled)
|
|
57
|
+
│ │
|
|
58
|
+
│ ├── clients/
|
|
59
|
+
│ │ ├── createPgPrismaClient.ts [gen] Prisma client factory
|
|
60
|
+
│ │ └── getPrisma.ts [gen] Prisma client getter (per DB)
|
|
61
|
+
│ │
|
|
62
|
+
│ ├── rest/
|
|
63
|
+
│ │ └── restRouter.ts [once] Custom REST endpoints (yours)
|
|
64
|
+
│ │
|
|
65
|
+
│ ├── init/
|
|
66
|
+
│ │ └── roles/
|
|
67
|
+
│ │ ├── generated/
|
|
68
|
+
│ │ │ ├── initPermissions.ts [gen] Collects and seeds all permissions
|
|
69
|
+
│ │ │ └── uiPermissions.ts [gen] 'ui.<entity>.list' per entity + system pages
|
|
70
|
+
│ │ ├── customPermissions.ts [once] Your hand-written data-space permissions
|
|
71
|
+
│ │ └── customUiPermissions.ts [once] Your hand-written UI-space permissions
|
|
72
|
+
│ │
|
|
73
|
+
│ ├── enums/
|
|
74
|
+
│ │ ├── enums.ts [gen] All entity name enums
|
|
75
|
+
│ │ ├── devEnums.ts [gen] Dev-only enums
|
|
76
|
+
│ │ └── initEntities.ts [gen] Entity registry initialisation
|
|
77
|
+
│ │
|
|
78
|
+
│ └── index.ts [gen] Application entry point
|
|
79
|
+
│
|
|
80
|
+
├── prisma/
|
|
81
|
+
│ └── schema.<dbName>.prisma [gen] Prisma schema (one per database)
|
|
82
|
+
│
|
|
83
|
+
├── config/
|
|
84
|
+
│ ├── dev.json [gen] Dev environment config (values from meta)
|
|
85
|
+
│ └── prod.json [gen] Prod environment config (values from meta)
|
|
86
|
+
│
|
|
87
|
+
├── docs/
|
|
88
|
+
│ ├── spec.md [gen] Auto-generated project specification
|
|
89
|
+
│ ├── configuration.md [gen] Config vars documentation
|
|
90
|
+
│ └── entities/
|
|
91
|
+
│ └── <EntityName>.md [gen] Per-entity documentation
|
|
92
|
+
│
|
|
93
|
+
├── Dockerfile [gen] Backend Docker image
|
|
94
|
+
├── .gitlab-ci.yml [gen] GitLab CI pipeline
|
|
95
|
+
│
|
|
96
|
+
└── chart/ Helm chart for Kubernetes
|
|
97
|
+
├── Chart.yaml [gen]
|
|
98
|
+
├── values.yaml [gen]
|
|
99
|
+
└── templates/
|
|
100
|
+
├── back.yaml [gen] Deployment manifest
|
|
101
|
+
└── ingress.yaml [gen] Ingress manifest
|
|
102
|
+
```
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Per-entity service: what to edit and what not to
|
|
107
|
+
|
|
108
|
+
### `<EntityName>Service.ts` — DO NOT EDIT {#entity-service-generated}
|
|
109
|
+
|
|
110
|
+
Generated on every `regen`. Contains:
|
|
111
|
+
- Standard CRUD operations (`getAll`, `getById`, `create`, `update`, `delete`)
|
|
112
|
+
- Prisma query building, filter application, sorting, pagination
|
|
113
|
+
- Hook invocation calls (calls into `initUserHooks.ts`)
|
|
114
|
+
|
|
115
|
+
### `Additional<EntityName>Service.ts` — yours {#additional-service}
|
|
116
|
+
|
|
117
|
+
Created once. Never overwritten. This is where you add:
|
|
118
|
+
- Custom methods that are too complex for the standard service
|
|
119
|
+
- Business logic that wraps or extends generated CRUD
|
|
120
|
+
- Helper methods used by hooks
|
|
121
|
+
|
|
122
|
+
### `hooks/` — yours {#hooks}
|
|
123
|
+
|
|
124
|
+
All hook files are created once and never overwritten.
|
|
125
|
+
|
|
126
|
+
| Hook file | When called | Common uses |
|
|
127
|
+
|-----------|------------|-------------|
|
|
128
|
+
| `beforeCreate.ts` | Before DB INSERT | Validate, enrich input, set computed fields |
|
|
129
|
+
| `afterCreate.ts` | After DB INSERT | Send notifications, trigger side effects |
|
|
130
|
+
| `beforeUpdate.ts` | Before DB UPDATE | Validate new state, check transitions |
|
|
131
|
+
| `afterUpdate.ts` | After DB UPDATE | Sync caches, publish events |
|
|
132
|
+
| `beforeDelete.ts` | Before DB DELETE | Check dependencies, soft-delete fallback |
|
|
133
|
+
| `afterDelete.ts` | After DB DELETE | Clean up related data |
|
|
134
|
+
| `beforeUpsert.ts` | Before UPSERT | |
|
|
135
|
+
| `additionalOperationsOnCreate.ts` | After create, separate tx context | Audit log, async tasks |
|
|
136
|
+
| `additionalOperationsOnUpdate.ts` | After update, separate tx context | |
|
|
137
|
+
| `additionalOperationsOnDelete.ts` | After delete, separate tx context | |
|
|
138
|
+
| `changeListFilter.ts` | On every list query | Inject runtime filters (tenant, user scope) |
|
|
139
|
+
| `tenantIdRequiredHooks.ts` | On create (multitenancy only) | Ensure `tenantId` is set from user context |
|
|
140
|
+
|
|
141
|
+
### `initUserHooks.ts` — yours {#init-user-hooks}
|
|
142
|
+
|
|
143
|
+
Registers your hook implementations with the framework. Created once.
|
|
144
|
+
When you implement a hook, register it here.
|
|
145
|
+
|
|
146
|
+
### `initBuiltInHooks.ts` — DO NOT EDIT {#init-built-in-hooks}
|
|
147
|
+
|
|
148
|
+
Generated. Registers framework-level hooks (audit logging, search index sync, etc.).
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Prisma schema
|
|
153
|
+
|
|
154
|
+
One `.prisma` file per registered database:
|
|
155
|
+
- `schema.main.prisma` — always generated
|
|
156
|
+
- `schema.<name>.prisma` — generated for each additional database registered via
|
|
157
|
+
`system.addDatabase(name)`
|
|
158
|
+
|
|
159
|
+
The schema is fully generated. **Do not edit it** — all model definitions come from
|
|
160
|
+
the meta. Schema changes go in the meta, not in the `.prisma` file.
|
|
161
|
+
|
|
162
|
+
**Anti-pattern — editing the Prisma schema directly:**
|
|
163
|
+
|
|
164
|
+
```
|
|
165
|
+
// WRONG: you add a column directly to schema.main.prisma
|
|
166
|
+
model Order {
|
|
167
|
+
id Int @id @default(autoincrement())
|
|
168
|
+
myField String // <-- added manually
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
After the next `regen`, `myField` is gone.
|
|
173
|
+
|
|
174
|
+
**Correct:** add the field in `metadata.ts` via `entity.addField('myField')`, then regen.
|
|
175
|
+
|
|
176
|
+
---
|
|
177
|
+
|
|
178
|
+
## Config files (`config/`)
|
|
179
|
+
|
|
180
|
+
`dev.json` and `prod.json` are generated from the config vars defined in `metadata.ts`.
|
|
181
|
+
They contain placeholder / default values.
|
|
182
|
+
|
|
183
|
+
**Do not commit secrets** to these files. They are intended as value templates.
|
|
184
|
+
Actual secret values should be injected from CI/CD environment variables, not from these
|
|
185
|
+
files.
|
|
186
|
+
|
|
187
|
+
---
|
|
188
|
+
|
|
189
|
+
## Permissions (`init/roles/`)
|
|
190
|
+
|
|
191
|
+
`permissionsToGraphql.ts` and its per-entity counterparts map permission strings to
|
|
192
|
+
GraphQL methods. How permissions are named, structured, and seeded into the database
|
|
193
|
+
(`init/roles/initPermissions.ts` and friends) is covered in full in
|
|
194
|
+
[11-permissions.md](./11-permissions.md).
|
|
195
|
+
|
|
196
|
+
---
|
|
197
|
+
|
|
198
|
+
## Anti-patterns
|
|
199
|
+
|
|
200
|
+
### Adding custom logic to `<EntityName>Service.ts`
|
|
201
|
+
|
|
202
|
+
**Wrong:** editing the generated service file directly.
|
|
203
|
+
|
|
204
|
+
**Why:** overwritten on every `regen`. Your changes are lost.
|
|
205
|
+
|
|
206
|
+
**Correct:** use `Additional<EntityName>Service.ts` for custom logic or `hooks/` for
|
|
207
|
+
lifecycle interception.
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
### Creating custom hooks outside the `hooks/` directory
|
|
212
|
+
|
|
213
|
+
**Wrong:** adding a `customHook.ts` file inside `<EntityName>Service/` and calling it
|
|
214
|
+
directly from `Additional<EntityName>Service.ts`.
|
|
215
|
+
|
|
216
|
+
**Why:** works, but bypasses the hook registration system. Framework hooks (audit,
|
|
217
|
+
search) won't compose correctly with custom hooks unless they go through `initUserHooks`.
|
|
218
|
+
|
|
219
|
+
**Correct:** implement hooks in the designated `hooks/` files and register them in
|
|
220
|
+
`initUserHooks.ts`.
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
### Modifying `initBuiltInHooks.ts`
|
|
225
|
+
|
|
226
|
+
**Wrong:** editing the generated file to remove audit logging or search sync hooks.
|
|
227
|
+
|
|
228
|
+
**Why:** the file is regenerated. Your changes are lost.
|
|
229
|
+
|
|
230
|
+
**Correct:** if you need to suppress a built-in hook, do it via the meta (e.g.
|
|
231
|
+
`entity.setAuditable(false)` to disable audit logging).
|
|
@@ -0,0 +1,260 @@
|
|
|
1
|
+
# Frontend File Graph (`<prefix>-ui`)
|
|
2
|
+
|
|
3
|
+
> **Load this file when:** you need to find a specific file in the generated frontend,
|
|
4
|
+
> understand which files are safe to edit, or know what gets regenerated.
|
|
5
|
+
>
|
|
6
|
+
> Legend: `[gen]` = regenerated every `regen` (DO NOT EDIT) · `[yours]` = created once,
|
|
7
|
+
> yours to implement · `[once]` = created once, rarely needs changes
|
|
8
|
+
>
|
|
9
|
+
> Related: [01-overview.md](./01-overview.md) · [07-backend-file-graph.md](./07-backend-file-graph.md) ·
|
|
10
|
+
> [11-permissions.md](./11-permissions.md)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Full tree
|
|
15
|
+
|
|
16
|
+
```
|
|
17
|
+
<prefix>-ui/
|
|
18
|
+
│
|
|
19
|
+
└── src/
|
|
20
|
+
│
|
|
21
|
+
├── adm/
|
|
22
|
+
│ │
|
|
23
|
+
│ ├── pages/
|
|
24
|
+
│ │ └── <EntityName>/ One folder per entity
|
|
25
|
+
│ │ │
|
|
26
|
+
│ │ ├── <EntityName>List/
|
|
27
|
+
│ │ │ ├── index.tsx [gen] Entry point — re-exports Default or custom
|
|
28
|
+
│ │ │ ├── Default<Entity>List.tsx [gen] Generated list component
|
|
29
|
+
│ │ │ ├── <Entity>Filter.tsx [yours] Your custom filter (created once)
|
|
30
|
+
│ │ │ ├── Default<Entity>Filter.tsx [gen] Generated default filter
|
|
31
|
+
│ │ │ └── <Entity>ListBreadcrumbs.tsx [gen] Breadcrumb component
|
|
32
|
+
│ │ │
|
|
33
|
+
│ │ ├── <EntityName>Show/
|
|
34
|
+
│ │ │ ├── index.tsx [gen] Entry point
|
|
35
|
+
│ │ │ ├── Default<Entity>Show.tsx [gen] Generated show component
|
|
36
|
+
│ │ │ ├── MainTab.tsx [yours] Your custom main tab content
|
|
37
|
+
│ │ │ ├── DefaultMainTab.tsx [gen] Generated main tab
|
|
38
|
+
│ │ │ ├── DefaultActions.tsx [gen] Generated action buttons
|
|
39
|
+
│ │ │ ├── additionalTabs.tsx [yours] Your extra tabs in show view
|
|
40
|
+
│ │ │ └── tabs/
|
|
41
|
+
│ │ │ └── <RelatedEntity>Tab.tsx [gen] Auto-generated dependency tabs
|
|
42
|
+
│ │ │
|
|
43
|
+
│ │ ├── <EntityName>Create/
|
|
44
|
+
│ │ │ ├── index.tsx [gen] Entry point
|
|
45
|
+
│ │ │ └── Default<Entity>Create.tsx [gen] Generated create form
|
|
46
|
+
│ │ │
|
|
47
|
+
│ │ └── <EntityName>Edit/
|
|
48
|
+
│ │ ├── index.tsx [gen] Entry point
|
|
49
|
+
│ │ └── Default<Entity>Edit.tsx [gen] Generated edit form
|
|
50
|
+
│ │
|
|
51
|
+
│ ├── widgets/
|
|
52
|
+
│ │ └── <EntityName>/
|
|
53
|
+
│ │ ├── <Entity>CountWidget.tsx [gen] Count widget for dashboard
|
|
54
|
+
│ │ └── <Entity>ListWidget.tsx [gen] List widget for dashboard
|
|
55
|
+
│ │
|
|
56
|
+
│ ├── resources.tsx [gen] react-admin resource registry
|
|
57
|
+
│ ├── resourcesChunk0.tsx [gen] Chunked resource imports (code splitting)
|
|
58
|
+
│ ├── resourcesChunk1.tsx [gen] Chunked resource imports
|
|
59
|
+
│ ├── ResourcesPage.tsx [gen] /resources debug page
|
|
60
|
+
│ ├── MetaPage.tsx [gen] /meta debug page
|
|
61
|
+
│ ├── entityMapping.ts [gen] Entity name → component mapping
|
|
62
|
+
│ ├── routes.tsx [gen] All react-admin routes
|
|
63
|
+
│ ├── additionalRoutes.tsx [yours] Your custom routes/pages
|
|
64
|
+
│ ├── Dashboard.tsx [yours] Home page content
|
|
65
|
+
│ ├── PermissionPage.tsx [once] Shown when withPermission denies access
|
|
66
|
+
│ ├── NotFoundPage.tsx [once] Shown when an action doesn't exist on the entity
|
|
67
|
+
│ ├── getDefaultMenu.ts [gen] Auto-generated sidebar menu
|
|
68
|
+
│ ├── getAdditionalMenu.ts [yours] Your extra menu items
|
|
69
|
+
│ │
|
|
70
|
+
│ └── functions/
|
|
71
|
+
│ └── Functions.tsx [gen] System functions page
|
|
72
|
+
│
|
|
73
|
+
├── i18n/
|
|
74
|
+
│ ├── types.ts [once] ValidationMessages type (one per project)
|
|
75
|
+
│ └── <lang>/
|
|
76
|
+
│ ├── <lang>Catalogs.ts [gen] Catalog translations for this language
|
|
77
|
+
│ ├── <lang>Docs.ts [gen] Document translations (exports `<lang>Documents`)
|
|
78
|
+
│ ├── <lang>InfoRegistries.ts [gen]
|
|
79
|
+
│ ├── <lang>SumRegistries.ts [gen]
|
|
80
|
+
│ ├── <lang>Reports.ts [gen]
|
|
81
|
+
│ ├── <lang>Validation.ts [once] Validation messages (one per language)
|
|
82
|
+
│ └── index.ts [once] Assembles the above + custom keys, per language
|
|
83
|
+
│
|
|
84
|
+
├── utils/
|
|
85
|
+
│ └── permissions.ts [gen] hasPermission / hasAnyPermission / hasAllPermissions / withPermission
|
|
86
|
+
│
|
|
87
|
+
└── environment/
|
|
88
|
+
├── src/
|
|
89
|
+
│ ├── App.tsx [gen] Root react-admin App component
|
|
90
|
+
│ ├── dataProvider/
|
|
91
|
+
│ │ ├── index.ts [gen] GraphQL data provider
|
|
92
|
+
│ │ └── getAdditionalMethods.ts [once] Hook for custom data provider methods
|
|
93
|
+
│ ├── i18nProvider/
|
|
94
|
+
│ │ └── index.ts [gen] react-admin i18n provider — merges src/i18n/<lang> per language
|
|
95
|
+
│ ├── layout/
|
|
96
|
+
│ │ ├── AppBar.tsx [gen] Top application bar
|
|
97
|
+
│ │ └── Menu.tsx [gen] Sidebar menu component
|
|
98
|
+
│ ├── routes.ts [gen] Route registration
|
|
99
|
+
│ └── contexts/
|
|
100
|
+
│ └── SpacesContext.ts [gen] Multi-space / tenant context
|
|
101
|
+
│
|
|
102
|
+
├── .gitlab-ci.yml [gen] GitLab CI pipeline for UI
|
|
103
|
+
├── Dockerfile [gen] nginx-based frontend Docker image
|
|
104
|
+
└── chart/ Helm chart for Kubernetes
|
|
105
|
+
├── Chart.yaml [gen]
|
|
106
|
+
├── values.yaml [gen]
|
|
107
|
+
└── templates/
|
|
108
|
+
├── front.yaml [gen]
|
|
109
|
+
└── ingress.yaml [gen]
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
---
|
|
113
|
+
|
|
114
|
+
## Per-entity: what to edit and what not to
|
|
115
|
+
|
|
116
|
+
### List page
|
|
117
|
+
|
|
118
|
+
| File | Owner | When to edit |
|
|
119
|
+
|------|-------|-------------|
|
|
120
|
+
| `index.tsx` | `[gen]` | Never — re-exports Default or your override |
|
|
121
|
+
| `Default<Entity>List.tsx` | `[gen]` | Never |
|
|
122
|
+
| `Default<Entity>Filter.tsx` | `[gen]` | Never |
|
|
123
|
+
| `<Entity>Filter.tsx` | `[yours]` | Customise filter fields, add new filters |
|
|
124
|
+
| `<Entity>ListBreadcrumbs.tsx` | `[gen]` | Never |
|
|
125
|
+
|
|
126
|
+
### Show page
|
|
127
|
+
|
|
128
|
+
| File | Owner | When to edit |
|
|
129
|
+
|------|-------|-------------|
|
|
130
|
+
| `index.tsx` | `[gen]` | Never |
|
|
131
|
+
| `Default<Entity>Show.tsx` | `[gen]` | Never |
|
|
132
|
+
| `DefaultMainTab.tsx` | `[gen]` | Never |
|
|
133
|
+
| `MainTab.tsx` | `[yours]` | Override the main tab layout |
|
|
134
|
+
| `DefaultActions.tsx` | `[gen]` | Never |
|
|
135
|
+
| `additionalTabs.tsx` | `[yours]` | Add extra tabs to the show view |
|
|
136
|
+
| `tabs/<Related>Tab.tsx` | `[gen]` | Never — auto-generated dependency lists |
|
|
137
|
+
|
|
138
|
+
### Create / Edit pages
|
|
139
|
+
|
|
140
|
+
All files under `Create/` and `Edit/` are `[gen]`. Form fields and their order are
|
|
141
|
+
controlled from the meta via `entity.getForms()`. See [03-entity-types.md](./03-entity-types.md).
|
|
142
|
+
|
|
143
|
+
---
|
|
144
|
+
|
|
145
|
+
## System-level files
|
|
146
|
+
|
|
147
|
+
### `Dashboard.tsx` — yours {#dashboard}
|
|
148
|
+
|
|
149
|
+
The home page of the admin UI. Empty by default. Add widgets, stats, charts here.
|
|
150
|
+
Widget components are generated per-entity in `widgets/<EntityName>/`.
|
|
151
|
+
|
|
152
|
+
### `additionalRoutes.tsx` — yours {#additional-routes}
|
|
153
|
+
|
|
154
|
+
Register custom react-admin `<Route>` components here. Used for non-entity pages like
|
|
155
|
+
reports, custom dashboards, or wizard flows.
|
|
156
|
+
|
|
157
|
+
### `getAdditionalMenu.ts` — yours {#additional-menu}
|
|
158
|
+
|
|
159
|
+
Return extra menu items from this function. Appended to the auto-generated menu.
|
|
160
|
+
Menu items added via `system.addGroupMenuItem` / `addInternalMenuItem` etc. in the meta
|
|
161
|
+
are reflected in `getDefaultMenu.ts` — do not edit that file.
|
|
162
|
+
|
|
163
|
+
### `PermissionPage.tsx` / `NotFoundPage.tsx` — once {#permission-fallbacks}
|
|
164
|
+
|
|
165
|
+
Fallback components rendered instead of a page's real content. Both re-export a
|
|
166
|
+
react-admin built-in by default and are safe to replace. See
|
|
167
|
+
[11-permissions.md](./11-permissions.md#fallback-components) for when each one is used.
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## How the menu is built
|
|
172
|
+
|
|
173
|
+
```
|
|
174
|
+
getDefaultMenu.ts [gen] ← from system.addGroupMenuItem / addInternalMenuItem in meta
|
|
175
|
+
getAdditionalMenu.ts [yours] ← your custom extra items
|
|
176
|
+
|
|
177
|
+
Menu.tsx [gen] ← combines both
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Anti-pattern — editing `getDefaultMenu.ts` to add menu items:**
|
|
181
|
+
|
|
182
|
+
```ts
|
|
183
|
+
// WRONG: overwritten on next regen
|
|
184
|
+
export const getDefaultMenu = () => [
|
|
185
|
+
...generatedItems,
|
|
186
|
+
{ label: 'My Custom Page', path: '/custom' } // lost on regen
|
|
187
|
+
]
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
**Correct:** add menu items in `getAdditionalMenu.ts` (for frontend-only custom items)
|
|
191
|
+
or via `system.addInternalMenuItem` / `addGroupMenuItem` in the meta (for items
|
|
192
|
+
backed by a page registered in the system).
|
|
193
|
+
|
|
194
|
+
---
|
|
195
|
+
|
|
196
|
+
## i18n {#i18n}
|
|
197
|
+
|
|
198
|
+
Entity/field-derived translation files (`<lang>Catalogs.ts`, `<lang>Docs.ts`,
|
|
199
|
+
`<lang>InfoRegistries.ts`, `<lang>SumRegistries.ts`, `<lang>Reports.ts`) are `[gen]` —
|
|
200
|
+
fully generated from entity titles and field titles defined in the meta. To change one
|
|
201
|
+
of these translations:
|
|
202
|
+
|
|
203
|
+
1. Update the title in `metadata.ts`: `entity.setTitle({ singular: 'Order', plural: 'Orders' }, 'en')`
|
|
204
|
+
2. Run `regen`
|
|
205
|
+
|
|
206
|
+
Three other files exist per project/language and are `[once]` — created only if
|
|
207
|
+
missing, then yours to edit freely:
|
|
208
|
+
|
|
209
|
+
| File | Scope | Purpose |
|
|
210
|
+
|------|-------|---------|
|
|
211
|
+
| `i18n/types.ts` | One per project | The `ValidationMessages` type — add fields here as you add validation messages |
|
|
212
|
+
| `i18n/<lang>/<lang>Validation.ts` | One per language | Validation message strings, typed by `ValidationMessages` |
|
|
213
|
+
| `i18n/<lang>/index.ts` | One per language | Assembles all of the above into the object `i18nProvider` imports; the place to add any translation key that isn't derived from an entity/field (UI labels, error messages, etc.) |
|
|
214
|
+
|
|
215
|
+
`i18n/<lang>/index.ts` is what `environment/src/i18nProvider/index.ts` (`[gen]`)
|
|
216
|
+
actually imports — it merges the entity-derived generated files, `<lang>Validation.ts`,
|
|
217
|
+
and falls back to `ra-language-english` for anything not overridden. Since it's created
|
|
218
|
+
once, it's safe to add project-specific keys directly into it.
|
|
219
|
+
|
|
220
|
+
**Anti-pattern — editing an entity/field-derived translation file directly:**
|
|
221
|
+
|
|
222
|
+
Editing `<lang>Catalogs.ts`, `<lang>Docs.ts`, or any other `[gen]` lang file is
|
|
223
|
+
pointless — changes are overwritten on regen. Add custom keys to `i18n/<lang>/index.ts`
|
|
224
|
+
instead.
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Data provider
|
|
229
|
+
|
|
230
|
+
`dataProvider/index.ts` is generated. It wires the react-admin data provider to the
|
|
231
|
+
GraphQL backend.
|
|
232
|
+
|
|
233
|
+
`dataProvider/getAdditionalMethods.ts` is created once. Use it to add custom data
|
|
234
|
+
provider methods for non-standard GraphQL queries.
|
|
235
|
+
|
|
236
|
+
---
|
|
237
|
+
|
|
238
|
+
## Anti-patterns
|
|
239
|
+
|
|
240
|
+
### Editing `Default<Entity>*.tsx` components
|
|
241
|
+
|
|
242
|
+
**Wrong:** modifying generated list/show/create/edit components directly.
|
|
243
|
+
|
|
244
|
+
**Why:** overwritten on every `regen`. Your changes vanish.
|
|
245
|
+
|
|
246
|
+
**Correct:**
|
|
247
|
+
- For list: customise `<Entity>Filter.tsx`
|
|
248
|
+
- For show: implement `MainTab.tsx` or add tabs in `additionalTabs.tsx`
|
|
249
|
+
- For create/edit: adjust field visibility/order in the meta via `entity.getForms()`
|
|
250
|
+
or `field.setShowInCreate(false)` / `setShowInEdit(false)`
|
|
251
|
+
|
|
252
|
+
---
|
|
253
|
+
|
|
254
|
+
### Adding custom routes directly to `routes.tsx`
|
|
255
|
+
|
|
256
|
+
**Wrong:** editing the generated `routes.tsx` to add a new route.
|
|
257
|
+
|
|
258
|
+
**Why:** overwritten on regen.
|
|
259
|
+
|
|
260
|
+
**Correct:** add custom routes in `additionalRoutes.tsx`.
|