runlify 0.0.794 → 0.0.797
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/layout/Menu.js +1 -1
- 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,357 @@
|
|
|
1
|
+
# Fields
|
|
2
|
+
|
|
3
|
+
> **Load this file when:** you need to add or configure fields on an entity — scalar
|
|
4
|
+
> fields, link fields, file/image fields, view-link fields, or the id field. Also covers
|
|
5
|
+
> filters, display visibility, required/optional semantics, and indexes.
|
|
6
|
+
>
|
|
7
|
+
> Related: [03-entity-types.md](./03-entity-types.md) ·
|
|
8
|
+
> [02-system-meta-builder.md](./02-system-meta-builder.md)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Field types overview
|
|
13
|
+
|
|
14
|
+
| Builder class | Created by | Used for |
|
|
15
|
+
|--------------|-----------|---------|
|
|
16
|
+
| `ScalarFieldBuilder` | `entity.addField()` | Primitive values: string, int, float, bool, datetime, date |
|
|
17
|
+
| `IdFieldBuilder` | auto (always `id`) | Primary key of an entity |
|
|
18
|
+
| `LinkFieldBuilder` | `entity.addLinkField()`, `addFileField()`, `addImageField()` | FK reference to another entity |
|
|
19
|
+
| `ViewLinkFieldBuilder` | `entity.addViewLinkField()` | Read-only join, not a real FK column |
|
|
20
|
+
|
|
21
|
+
---
|
|
22
|
+
|
|
23
|
+
## Scalar types (`FieldType`)
|
|
24
|
+
|
|
25
|
+
| Type | TypeScript | Prisma | Notes |
|
|
26
|
+
|------|-----------|--------|-------|
|
|
27
|
+
| `'string'` | `string` | `String` | Default. Use `setStringType()` to specialise |
|
|
28
|
+
| `'int'` | `number` | `Int` | |
|
|
29
|
+
| `'bigint'` | `bigint` | `BigInt` | |
|
|
30
|
+
| `'float'` | `number` | `Float` | |
|
|
31
|
+
| `'bool'` | `boolean` | `Boolean` | `searchable` set to `false` automatically |
|
|
32
|
+
| `'datetime'` | `Date` | `DateTime` | |
|
|
33
|
+
| `'date'` | `Date` | `DateTime` | Date only (no time component in UI) |
|
|
34
|
+
|
|
35
|
+
Default type for new fields created via `addField()` is `'int'`. Always call
|
|
36
|
+
`setType()` explicitly.
|
|
37
|
+
|
|
38
|
+
---
|
|
39
|
+
|
|
40
|
+
## String sub-types (`StringType`)
|
|
41
|
+
|
|
42
|
+
Set via `field.setStringType(StringType.X)`. Only valid when `type === 'string'`.
|
|
43
|
+
|
|
44
|
+
| Value | Effect | Notes |
|
|
45
|
+
|-------|--------|-------|
|
|
46
|
+
| `StringType.Plain` | Single-line text input | Default |
|
|
47
|
+
| `StringType.Number` | Number-formatted string | Displayed as number but stored as string |
|
|
48
|
+
| `StringType.Multiline` | Multi-line textarea | |
|
|
49
|
+
| `StringType.RichEdit` | WYSIWYG rich text editor | |
|
|
50
|
+
| `StringType.Markdown` | Markdown editor | Auto-sets `showInList=false`, `showInFilter=false` |
|
|
51
|
+
| `StringType.Json` | JSON editor | Auto-sets `showInList=false`, `showInFilter=false` |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Number sub-types (`NumberType`)
|
|
56
|
+
|
|
57
|
+
Set via `field.setNumberType(NumberType.X)`. Only valid for `int`, `bigint`, `float`.
|
|
58
|
+
|
|
59
|
+
| Value | Effect |
|
|
60
|
+
|-------|--------|
|
|
61
|
+
| `NumberType.Base` | Plain number (default) |
|
|
62
|
+
| `NumberType.Money` | Formatted as monetary value in UI |
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## BaseFieldBuilder — all methods
|
|
67
|
+
|
|
68
|
+
Available on every field type.
|
|
69
|
+
|
|
70
|
+
### Type & subtype
|
|
71
|
+
|
|
72
|
+
#### `setType(type: FieldType): this` {#setType}
|
|
73
|
+
|
|
74
|
+
Sets the scalar type. Validates that existing filters are compatible with the new type.
|
|
75
|
+
When set to `'bool'`, automatically sets `searchable = false`.
|
|
76
|
+
|
|
77
|
+
#### `setStringType(stringType: StringType): this` {#setStringType}
|
|
78
|
+
|
|
79
|
+
Only for `type === 'string'`. Throws otherwise.
|
|
80
|
+
|
|
81
|
+
#### `setNumberType(numberType: NumberType): this` {#setNumberType}
|
|
82
|
+
|
|
83
|
+
Only for `type === 'int' | 'bigint' | 'float'`. Throws otherwise.
|
|
84
|
+
|
|
85
|
+
---
|
|
86
|
+
|
|
87
|
+
### Required / optional semantics {#required}
|
|
88
|
+
|
|
89
|
+
#### `setRequired(value?: boolean): this`
|
|
90
|
+
|
|
91
|
+
Marks the field as required in the DB (`NOT NULL`) and in GraphQL input types.
|
|
92
|
+
Also sets `requiredOnInput = true` if it has not been set explicitly before.
|
|
93
|
+
|
|
94
|
+
#### `setNotRequired(): this`
|
|
95
|
+
|
|
96
|
+
Marks the field as optional (`NULL` allowed). Equivalent to `setRequired(false)`.
|
|
97
|
+
|
|
98
|
+
#### `setRequiredOnInput(value: boolean, defaultValueExpression?: string): this`
|
|
99
|
+
|
|
100
|
+
Controls whether the field is required in GraphQL **input types** independently of
|
|
101
|
+
whether it is required in the DB. Use when the field is required in the DB but has a
|
|
102
|
+
server-side default, so the client doesn't need to send it.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
### Updatability {#updatability}
|
|
107
|
+
|
|
108
|
+
#### `setNotUpdatableByUser(defaultValueExpression?, defaultBackendValueExpression?): this` {#setNotUpdatableByUser}
|
|
109
|
+
|
|
110
|
+
Marks the field as **not updatable via user-facing GraphQL mutations**.
|
|
111
|
+
Internally calls `setRequiredOnInput(false, defaultValueExpression)`.
|
|
112
|
+
|
|
113
|
+
| Parameter | Description |
|
|
114
|
+
|-----------|-------------|
|
|
115
|
+
| `defaultValueExpression` | Frontend expression used as default value in create forms |
|
|
116
|
+
| `defaultBackendValueExpression` | Server-side expression evaluated on create/update |
|
|
117
|
+
|
|
118
|
+
**Anti-pattern — using `setNotUpdatableByUser` to make a field immutable when it is
|
|
119
|
+
also `setRequired()`:**
|
|
120
|
+
|
|
121
|
+
```ts
|
|
122
|
+
// WRONG: setNotUpdatableByUser sets requiredOnInput=false
|
|
123
|
+
// This means the field is optional in update input even though it is required in DB.
|
|
124
|
+
// For a create-only field that the server fills, this is CORRECT.
|
|
125
|
+
// For a field that the user MUST provide on create, use setRequiredOnInput explicitly:
|
|
126
|
+
field
|
|
127
|
+
.setRequired() // required in DB
|
|
128
|
+
.setNotUpdatableByUser() // NOT in update mutations
|
|
129
|
+
.setRequiredOnInput(true) // required in CREATE mutation
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
**Anti-pattern — using `setNotUpdatableByUser` on every field to hide entity from UI:**
|
|
133
|
+
|
|
134
|
+
```ts
|
|
135
|
+
// WRONG: intended to make entity read-only, but breaks required/type semantics
|
|
136
|
+
entity.addField('name').setRequired().setNotUpdatableByUser()
|
|
137
|
+
entity.addField('code').setRequired().setNotUpdatableByUser()
|
|
138
|
+
|
|
139
|
+
// CORRECT: hide at entity level
|
|
140
|
+
entity.setCreatableByUser(false).setUpdatableByUser(false).setRemovableByUser(false)
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
See [03-entity-types.md#access-control](./03-entity-types.md#access-control).
|
|
144
|
+
|
|
145
|
+
#### `setUpdatableByUser(): this`
|
|
146
|
+
|
|
147
|
+
Reverses `setNotUpdatableByUser`. Marks field as editable by user again.
|
|
148
|
+
|
|
149
|
+
#### `setUpdatable(value?: boolean): this`
|
|
150
|
+
|
|
151
|
+
Controls whether the field appears in backend update operations at all (not just user-
|
|
152
|
+
facing). Lower-level than `setUpdatableByUser`.
|
|
153
|
+
|
|
154
|
+
---
|
|
155
|
+
|
|
156
|
+
### Visibility {#visibility}
|
|
157
|
+
|
|
158
|
+
All default to `true` except where noted.
|
|
159
|
+
|
|
160
|
+
| Method | Controls |
|
|
161
|
+
|--------|---------|
|
|
162
|
+
| `setShowInList(value?)` | Shown in entity list table |
|
|
163
|
+
| `setShowInFilter(value?)` | Shown in list filter panel |
|
|
164
|
+
| `setShowInCreate(value?)` | Shown in create form |
|
|
165
|
+
| `setShowInEdit(value?)` | Shown in edit form |
|
|
166
|
+
| `setShowInShow(value?)` | Shown in record detail view |
|
|
167
|
+
| `setHidden(value?)` | Hidden everywhere (UI + generated TS types) |
|
|
168
|
+
| `setSearchable(value?)` | Included in full-text search indexing |
|
|
169
|
+
|
|
170
|
+
`Markdown` and `Json` string types auto-set `showInList=false` and `showInFilter=false`.
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
### Default values {#defaults}
|
|
175
|
+
|
|
176
|
+
#### `setDefaultDbValue(value: string | boolean | undefined): this`
|
|
177
|
+
|
|
178
|
+
Sets the Prisma `@default(...)` value in the schema. This is a DB-level default.
|
|
179
|
+
|
|
180
|
+
#### `setDefaultValueExpression(value: string): this`
|
|
181
|
+
|
|
182
|
+
Sets the expression used on the **frontend** (create forms) and as the backend default
|
|
183
|
+
expression. Cannot be `'null'` or `'undefined'` — throws.
|
|
184
|
+
|
|
185
|
+
Also automatically calls `setDefaultBackendValueExpression(value)`.
|
|
186
|
+
|
|
187
|
+
#### `setDefaultBackendValueExpression(value: string): this`
|
|
188
|
+
|
|
189
|
+
Sets only the backend expression (TypeScript code evaluated during create/update).
|
|
190
|
+
Cannot be `'null'` or `'undefined'` — throws.
|
|
191
|
+
|
|
192
|
+
---
|
|
193
|
+
|
|
194
|
+
### Filters {#filters}
|
|
195
|
+
|
|
196
|
+
Default filter for all fields: `['equal']`.
|
|
197
|
+
|
|
198
|
+
#### `setFilters(filters: Filter[]): this`
|
|
199
|
+
|
|
200
|
+
Replaces the filter list entirely. Validates compatibility with the field type.
|
|
201
|
+
|
|
202
|
+
#### `addFilter(filter: Filter): this` / `addFilters(filters: Filter[]): this`
|
|
203
|
+
|
|
204
|
+
Adds one or more filters.
|
|
205
|
+
|
|
206
|
+
#### `delFilter(filter: Filter): this`
|
|
207
|
+
|
|
208
|
+
Removes a filter.
|
|
209
|
+
|
|
210
|
+
**Allowed filters per type:**
|
|
211
|
+
|
|
212
|
+
| Type | Allowed filters |
|
|
213
|
+
|------|----------------|
|
|
214
|
+
| `string` | `equal`, `defined`, `not_defined`, `in`, `not_in` |
|
|
215
|
+
| `int`, `bigint`, `float` | `equal`, `defined`, `not_defined`, `in`, `not_in`, `lte`, `gte`, `lt`, `gt` |
|
|
216
|
+
| `date`, `datetime` | `equal`, `defined`, `not_defined`, `lte`, `gte`, `lt`, `gt` |
|
|
217
|
+
| `bool` | `equal`, `defined`, `not_defined` |
|
|
218
|
+
|
|
219
|
+
**Anti-pattern — `in` / `not_in` on a scalar field:**
|
|
220
|
+
|
|
221
|
+
```ts
|
|
222
|
+
// WRONG: throws at build() time — 'in'/'not_in' only allowed on LinkFields
|
|
223
|
+
entity.addField('statusCode').setType('string').addFilter('in')
|
|
224
|
+
|
|
225
|
+
// CORRECT: 'in'/'not_in' is only valid on link fields
|
|
226
|
+
entity.addLinkField('statuses', 'statusId').addFilter('in')
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
---
|
|
230
|
+
|
|
231
|
+
### Other
|
|
232
|
+
|
|
233
|
+
#### `setTitle(title: string, language?: string): this`
|
|
234
|
+
|
|
235
|
+
Sets human-readable title for a language. Defaults to the system's default language.
|
|
236
|
+
|
|
237
|
+
#### `setTitles(titles: Record<string, string>): this`
|
|
238
|
+
|
|
239
|
+
Sets titles for multiple languages at once: `{ en: 'Name', ru: 'Имя' }`.
|
|
240
|
+
|
|
241
|
+
#### `setNeedFor(text: string): this`
|
|
242
|
+
|
|
243
|
+
Documents what this field is used for. Included in generated docs.
|
|
244
|
+
|
|
245
|
+
#### `setSharded(value?: boolean): this`
|
|
246
|
+
|
|
247
|
+
Marks this field as a sharding key. The field becomes part of the composite unique
|
|
248
|
+
constraint that groups records in a shard.
|
|
249
|
+
|
|
250
|
+
#### `setArray(value?: boolean): this`
|
|
251
|
+
|
|
252
|
+
Marks the field as an array type in GraphQL and TypeScript output types.
|
|
253
|
+
|
|
254
|
+
#### `setMeaning(meaning: 'img'): this`
|
|
255
|
+
|
|
256
|
+
Marks the field with a semantic meaning. Currently only `'img'` is supported — hints
|
|
257
|
+
the UI to render this string field as an image URL.
|
|
258
|
+
|
|
259
|
+
---
|
|
260
|
+
|
|
261
|
+
## IdFieldBuilder {#id-field}
|
|
262
|
+
|
|
263
|
+
Accessed via `entity.getKey()`. Auto-created as `id`.
|
|
264
|
+
|
|
265
|
+
#### `setType(type: TKeyFieldType): this`
|
|
266
|
+
|
|
267
|
+
| Value | Prisma | Auto-generation |
|
|
268
|
+
|-------|--------|----------------|
|
|
269
|
+
| `'int'` | `Int` | `@default(autoincrement())` — default |
|
|
270
|
+
| `'bigint'` | `BigInt` | `@default(autoincrement())` |
|
|
271
|
+
| `'string'` | `String` | `@default(cuid())` |
|
|
272
|
+
|
|
273
|
+
---
|
|
274
|
+
|
|
275
|
+
## LinkFieldBuilder {#link-field}
|
|
276
|
+
|
|
277
|
+
Created via `entity.addLinkField(entityNameOrBuilder, name, title?)`.
|
|
278
|
+
|
|
279
|
+
**Name MUST end in `Id`** — validated on construction. Throws immediately if violated.
|
|
280
|
+
|
|
281
|
+
```ts
|
|
282
|
+
entity.addLinkField('statuses', 'statusId') // correct
|
|
283
|
+
entity.addLinkField('statuses', 'status') // throws: must end in 'Id'
|
|
284
|
+
```
|
|
285
|
+
|
|
286
|
+
#### `setType(type: TKeyFieldType): this`
|
|
287
|
+
|
|
288
|
+
Sets the FK column type: `'int'` (default), `'bigint'`, or `'string'`.
|
|
289
|
+
When passing a `CatalogBuilder` instance to `addLinkField`, the type is inferred
|
|
290
|
+
automatically from the target entity's key type.
|
|
291
|
+
|
|
292
|
+
#### `setPredefinedLinkedEntity(value: 'none' | 'file'): this`
|
|
293
|
+
|
|
294
|
+
`'file'` — marks this field as linking to the built-in `files` catalog with special
|
|
295
|
+
file-upload UI treatment. Set automatically by `addFileField()` and `addImageField()`.
|
|
296
|
+
|
|
297
|
+
#### `setFileType(type: 'plain' | 'image'): this`
|
|
298
|
+
|
|
299
|
+
Only valid when `predefinedLinkedEntity === 'file'`. Throws otherwise.
|
|
300
|
+
Set automatically by `addImageField()` to `'image'`.
|
|
301
|
+
|
|
302
|
+
**Anti-pattern — using `addLinkField` for file/image fields:**
|
|
303
|
+
|
|
304
|
+
```ts
|
|
305
|
+
// WRONG: works but misses file-upload UI and correct type semantics
|
|
306
|
+
entity.addLinkField('files', 'avatarId')
|
|
307
|
+
|
|
308
|
+
// CORRECT: use the dedicated helpers
|
|
309
|
+
entity.addFileField('documentId') // file upload UI
|
|
310
|
+
entity.addImageField('avatarId') // image upload UI with preview
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
---
|
|
314
|
+
|
|
315
|
+
## ViewLinkFieldBuilder {#view-link-field}
|
|
316
|
+
|
|
317
|
+
Created via `entity.addViewLinkField(entityNameOrBuilder, name, title?)`.
|
|
318
|
+
|
|
319
|
+
A **read-only join** — does not create a real FK column in the database. Used to display
|
|
320
|
+
data from a related entity without storing a FK. The name must still end in `Id`.
|
|
321
|
+
|
|
322
|
+
Use for: displaying computed or external references that should not be part of the
|
|
323
|
+
entity's own schema.
|
|
324
|
+
|
|
325
|
+
---
|
|
326
|
+
|
|
327
|
+
## Convenience helpers on entity
|
|
328
|
+
|
|
329
|
+
```ts
|
|
330
|
+
entity.addFileField(name, title?) // LinkField to 'files', predefined='file', fileType='plain'
|
|
331
|
+
entity.addImageField(name, title?) // LinkField to 'files', predefined='file', fileType='image'
|
|
332
|
+
```
|
|
333
|
+
|
|
334
|
+
---
|
|
335
|
+
|
|
336
|
+
## Field name rules
|
|
337
|
+
|
|
338
|
+
Field names must match `/^[a-zA-Z0-9]+$/`. No underscores, hyphens, or special chars.
|
|
339
|
+
Validated on construction — throws immediately.
|
|
340
|
+
|
|
341
|
+
**Anti-pattern — underscores or special chars in field names:**
|
|
342
|
+
|
|
343
|
+
```ts
|
|
344
|
+
// WRONG: throws immediately
|
|
345
|
+
entity.addField('first_name')
|
|
346
|
+
entity.addField('first-name')
|
|
347
|
+
entity.addField('firstName!')
|
|
348
|
+
|
|
349
|
+
// CORRECT
|
|
350
|
+
entity.addField('firstName')
|
|
351
|
+
```
|
|
352
|
+
|
|
353
|
+
---
|
|
354
|
+
|
|
355
|
+
> **Examples:** this file intentionally omits code examples. Add real usage examples
|
|
356
|
+
> from the consuming project here. A reference metadata file is typically at
|
|
357
|
+
> `src/meta/metadata.ts` in the meta project.
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
# Custom Methods
|
|
2
|
+
|
|
3
|
+
> **Load this file when:** you need to add custom GraphQL operations (Query or Mutation)
|
|
4
|
+
> beyond the standard CRUD — either at the system level or on a specific entity.
|
|
5
|
+
> Also covers input/output models, args, return types, workers, and cron schedules.
|
|
6
|
+
>
|
|
7
|
+
> Related: [02-system-meta-builder.md#addMethod](./02-system-meta-builder.md#addMethod) ·
|
|
8
|
+
> [03-entity-types.md#entity-methods](./03-entity-types.md#entity-methods)
|
|
9
|
+
|
|
10
|
+
---
|
|
11
|
+
|
|
12
|
+
## Where to add methods
|
|
13
|
+
|
|
14
|
+
Methods can live on:
|
|
15
|
+
|
|
16
|
+
| Scope | How to create | Generated location |
|
|
17
|
+
|-------|--------------|-------------------|
|
|
18
|
+
| **System-level** | `system.addMethod(name, type)` | Top-level GraphQL schema + `AdditionalService` |
|
|
19
|
+
| **Entity-level** | `entity.addMethod(name, type)` | Inside entity's `<Entity>Service` |
|
|
20
|
+
|
|
21
|
+
Standard CRUD methods (`all`, `create`, `update`, `delete`) are auto-generated on every
|
|
22
|
+
savable entity. Do not add them manually.
|
|
23
|
+
|
|
24
|
+
---
|
|
25
|
+
|
|
26
|
+
## `addMethod(name, methodType, title?): MethodBuilder` {#addMethod}
|
|
27
|
+
|
|
28
|
+
| Parameter | Type | Values |
|
|
29
|
+
|-----------|------|--------|
|
|
30
|
+
| `name` | `string` | Method name, `camelCase`, unique within the owner |
|
|
31
|
+
| `methodType` | `MethodType` | `MethodType.Query` — read operation; `MethodType.Mutation` — write operation |
|
|
32
|
+
| `title` | `string` | Optional human-readable title |
|
|
33
|
+
|
|
34
|
+
Returns a `MethodBuilder`.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## MethodBuilder API
|
|
39
|
+
|
|
40
|
+
### `setExportedToApi(value?: boolean): this` {#setExportedToApi}
|
|
41
|
+
|
|
42
|
+
Exposes this method in the generated GraphQL schema. Default `false` for custom methods
|
|
43
|
+
(standard CRUD methods are exported automatically via their own mechanism).
|
|
44
|
+
|
|
45
|
+
Call `.setExportedToApi()` on any custom method that should be callable from the
|
|
46
|
+
frontend.
|
|
47
|
+
|
|
48
|
+
**Anti-pattern — forgetting `setExportedToApi`:**
|
|
49
|
+
|
|
50
|
+
```ts
|
|
51
|
+
// WRONG: method is generated in the service but NOT in the GraphQL schema
|
|
52
|
+
entity.addMethod('recalculate', MethodType.Mutation)
|
|
53
|
+
|
|
54
|
+
// CORRECT
|
|
55
|
+
entity.addMethod('recalculate', MethodType.Mutation).setExportedToApi()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
### `setMethodType(type: MethodType): this`
|
|
61
|
+
|
|
62
|
+
Changes the method type after creation.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
### `setAsync(value?: boolean): this`
|
|
67
|
+
|
|
68
|
+
Marks the method as `async`. Affects the generated service method signature.
|
|
69
|
+
|
|
70
|
+
---
|
|
71
|
+
|
|
72
|
+
### `setWorker(workerName: string): this` / `resetWorker(): this`
|
|
73
|
+
|
|
74
|
+
Associates this method with a named worker. The worker must be registered via
|
|
75
|
+
`system.addWorker(name)`. When a worker is set, the method runs in the worker process
|
|
76
|
+
rather than the main backend.
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
### `addRunSchedule(cronExpression: string): this` / `resetRunSchedule(): this`
|
|
81
|
+
|
|
82
|
+
Adds a cron expression to automatically trigger this method on a schedule.
|
|
83
|
+
Multiple schedules can be added. Duplicate cron expressions throw.
|
|
84
|
+
|
|
85
|
+
```ts
|
|
86
|
+
method.addRunSchedule('0 * * * *') // every hour
|
|
87
|
+
method.addRunSchedule('0 0 * * *') // every day at midnight
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
---
|
|
91
|
+
|
|
92
|
+
### `getArgsModel(): ArgsModelBuilder` {#argsModel}
|
|
93
|
+
|
|
94
|
+
Returns the **args model** — the input type for this method. Add fields to it to define
|
|
95
|
+
what the caller must pass.
|
|
96
|
+
|
|
97
|
+
`ArgsModelBuilder` extends `BaseModelBuilder` — it has the same `addField()` and
|
|
98
|
+
`addModelField()` API (see [Models section](#models) below).
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
### Return type
|
|
103
|
+
|
|
104
|
+
A method's return type is one of three variants. Set exactly one.
|
|
105
|
+
|
|
106
|
+
#### `setReturnObjectModel(name: string): ReturnObjectBuilder` {#setReturnObjectModel}
|
|
107
|
+
|
|
108
|
+
Sets return type to a custom object type. Returns a `ReturnObjectBuilder` which extends
|
|
109
|
+
`BaseModelBuilder` — add fields to define the returned shape.
|
|
110
|
+
|
|
111
|
+
The returned model name is auto-namespaced: `${methodName}${PascalCase(name)}`.
|
|
112
|
+
|
|
113
|
+
`ReturnObjectBuilder` additionally supports:
|
|
114
|
+
```ts
|
|
115
|
+
returnModel.setArray(true) // returns an array of objects
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
#### `setReturnScalarModel(): ReturnScalarBuilder` {#setReturnScalarModel}
|
|
119
|
+
|
|
120
|
+
Sets return type to a scalar. Returns a `ReturnScalarBuilder` which extends
|
|
121
|
+
`ScalarFieldBuilder` — call `setType()` to set the scalar type, `setArray()` to return
|
|
122
|
+
an array of scalars.
|
|
123
|
+
|
|
124
|
+
```ts
|
|
125
|
+
const ret = method.setReturnScalarModel()
|
|
126
|
+
ret.setType('string')
|
|
127
|
+
ret.setArray() // returns string[]
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
#### `setReturnVoidModel(name?: string): ReturnVoidBuilder` {#setReturnVoidModel}
|
|
131
|
+
|
|
132
|
+
Sets return type to `void` (no return value). This is the **default** — methods start
|
|
133
|
+
as void.
|
|
134
|
+
|
|
135
|
+
---
|
|
136
|
+
|
|
137
|
+
## Models — input and output {#models}
|
|
138
|
+
|
|
139
|
+
Models are named TypeScript / GraphQL types used as method arguments or return values.
|
|
140
|
+
They are defined on the owner (system or entity) and referenced by methods.
|
|
141
|
+
|
|
142
|
+
### Creating models
|
|
143
|
+
|
|
144
|
+
| Method | Type | Use as |
|
|
145
|
+
|--------|------|--------|
|
|
146
|
+
| `createGeneralModel(name, title?)` | `BaseModelBuilder` | Input or output |
|
|
147
|
+
| `createInputModel(name, title?)` | `BaseModelBuilder` | Input only (GraphQL `input` type) |
|
|
148
|
+
| `createOutputModel(name, title?)` | `BaseModelBuilder` | Output only (GraphQL `type`) |
|
|
149
|
+
|
|
150
|
+
### `BaseModelBuilder` API
|
|
151
|
+
|
|
152
|
+
#### `addField(name, title?): ScalarFieldBuilder`
|
|
153
|
+
|
|
154
|
+
Adds a scalar field to the model. Returns a `ScalarFieldBuilder` with the full field API
|
|
155
|
+
from [04-fields.md](./04-fields.md).
|
|
156
|
+
|
|
157
|
+
#### `addModelField(model, name, title?): ModelFieldBuilder`
|
|
158
|
+
|
|
159
|
+
Adds a reference to another model defined on the same owner. The referenced model must
|
|
160
|
+
exist at the time of this call — throws if not found.
|
|
161
|
+
|
|
162
|
+
```ts
|
|
163
|
+
const address = system.createGeneralModel('address')
|
|
164
|
+
address.addField('street').setType('string').setRequired()
|
|
165
|
+
address.addField('city').setType('string').setRequired()
|
|
166
|
+
|
|
167
|
+
const output = method.setReturnObjectModel('result')
|
|
168
|
+
output.addModelField('address', 'shippingAddress')
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
#### `delField(name): this`
|
|
172
|
+
|
|
173
|
+
Removes a field from the model.
|
|
174
|
+
|
|
175
|
+
---
|
|
176
|
+
|
|
177
|
+
## Full wiring example pattern
|
|
178
|
+
|
|
179
|
+
```
|
|
180
|
+
system
|
|
181
|
+
.addMethod('calculateDiscount', MethodType.Query)
|
|
182
|
+
.setExportedToApi()
|
|
183
|
+
.setAsync()
|
|
184
|
+
┌─ .getArgsModel()
|
|
185
|
+
│ .addField('orderId').setType('int').setRequired()
|
|
186
|
+
│ .addField('promoCode').setType('string')
|
|
187
|
+
│
|
|
188
|
+
└─ .setReturnObjectModel('result')
|
|
189
|
+
.addField('discountPercent').setType('float').setRequired()
|
|
190
|
+
.addField('finalPrice').setType('float').setRequired()
|
|
191
|
+
```
|
|
192
|
+
|
|
193
|
+
The method name, args model, and return model combine to generate:
|
|
194
|
+
- A GraphQL schema entry (`Query.calculateDiscount(orderId: Int!, promoCode: String): CalculateDiscountResult`)
|
|
195
|
+
- A service method stub in the `Additional*Service.ts` file (yours to implement)
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## Anti-patterns
|
|
200
|
+
|
|
201
|
+
### Defining return shape via separate `createOutputModel` without linking it to the method
|
|
202
|
+
|
|
203
|
+
**Wrong:**
|
|
204
|
+
```ts
|
|
205
|
+
system.createOutputModel('discountResult') // created but never linked
|
|
206
|
+
|
|
207
|
+
const method = system.addMethod('getDiscount', MethodType.Query)
|
|
208
|
+
// forgot to call setReturnObjectModel()
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
**Why:** the model is generated but the method returns `void`. The GraphQL schema
|
|
212
|
+
doesn't reference the model.
|
|
213
|
+
|
|
214
|
+
**Correct:** always call one of `setReturnObjectModel`, `setReturnScalarModel`, or
|
|
215
|
+
`setReturnVoidModel` explicitly. For object returns, the model is created and registered
|
|
216
|
+
by `setReturnObjectModel` automatically — no need to pre-create it separately.
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
### Adding standard CRUD method names manually
|
|
221
|
+
|
|
222
|
+
**Wrong:**
|
|
223
|
+
```ts
|
|
224
|
+
entity.addMethod('all', MethodType.Query) // throws: 'all' already exists
|
|
225
|
+
entity.addMethod('create', MethodType.Mutation)
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
**Why:** `all`, `create`, `update`, `delete` are auto-added by `BaseSavableEntityBuilder`
|
|
229
|
+
constructor. Adding them again throws a duplicate name error.
|
|
230
|
+
|
|
231
|
+
**Correct:** use only unique custom names.
|
|
232
|
+
|
|
233
|
+
---
|
|
234
|
+
|
|
235
|
+
### Duplicate cron expressions on the same method
|
|
236
|
+
|
|
237
|
+
**Wrong:**
|
|
238
|
+
```ts
|
|
239
|
+
method.addRunSchedule('0 * * * *')
|
|
240
|
+
method.addRunSchedule('0 * * * *') // throws: duplicate cron
|
|
241
|
+
```
|
|
242
|
+
|
|
243
|
+
**Correct:** each cron expression must be unique per method.
|
|
244
|
+
|
|
245
|
+
---
|
|
246
|
+
|
|
247
|
+
> **Examples:** this file intentionally omits code examples. Add real usage examples
|
|
248
|
+
> from the consuming project here. A reference metadata file is typically at
|
|
249
|
+
> `src/meta/metadata.ts` in the meta project.
|