@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7
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/.env.example +2 -2
- package/.env.production.example +9 -1
- package/README.md +14 -38
- package/dist/config.js +36 -3
- package/dist/knowledge/backendConstraints.js +982 -0
- package/dist/knowledge/componentCatalog.js +549 -92
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +500 -16
- package/dist/knowledge/outlines.js +167 -12
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +961 -44
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +107 -6
- package/dist/platform/http.js +34 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +25 -6
- package/dist/platform/screenshot.js +544 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +3 -3
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +4 -3
- package/dist/resources/guides/component.md +81 -8
- package/dist/resources/guides/entity.md +712 -20
- package/dist/resources/guides/expressions.md +47 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +82 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +287 -15
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +402 -49
- package/dist/resources/guides/translation.md +150 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +199 -28
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +6 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +34 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
- package/dist/server.js +17 -1
- package/dist/tools/access.js +3 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +76 -34
- package/dist/tools/common.js +55 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/environments.js +52 -0
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +57 -42
- package/dist/tools/flows.js +205 -13
- package/dist/tools/folders.js +7 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +270 -40
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +421 -104
- package/dist/tools/platformInfo.js +155 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +484 -50
- package/dist/tools/screenWrite.js +68 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/templates.js +32 -5
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +56 -21
- package/package.json +7 -2
|
@@ -4,6 +4,12 @@ Buradaki kurallar gerçek projelerde (qm, hr, sms, klinik) **deneyerek doğrulan
|
|
|
4
4
|
|
|
5
5
|
## Slug seçimi — isim uzayı platformla paylaşımlıdır
|
|
6
6
|
|
|
7
|
+
> **Yanıttaki `silentlyDropped`'ı oku.** Gateway her yazma çağrısında gönderdiğin gövdeyi
|
|
8
|
+
> kaydedilenle karşılaştırır ve kayda geçmeyen alanları bu listede sayar — istek 200 döner, alan
|
|
9
|
+
> yok sayılmıştır. Bu, hata listesinde hiç görünmeyen tek sinyaldir; yazmadan sonra yanıtın o
|
|
10
|
+
> alanına bakmayı alışkanlık yap. Ayrıntılı örnek: `platform_guide('microservice')`.
|
|
11
|
+
|
|
12
|
+
|
|
7
13
|
Her environment şeması ~75 platform tablosu taşır ve entity slug'ları bu tablolarla aynı isim
|
|
8
14
|
uzayındadır. Çakışan slug `Migration failed … relation "X" already exists` verir ve **entity
|
|
9
15
|
kaydı hiç oluşmaz**. Rezerve adlardan bilinenler: `branches`, `applications`, `caches`,
|
|
@@ -48,18 +54,24 @@ doğrudan kullan).
|
|
|
48
54
|
silinemez. Buna karşılık **`entity_patch` ile alan EKLENEBİLİR** — telefon, departman, avatar,
|
|
49
55
|
`musteri_tipi` gibi profil alanları `users`'a eklenebilir.
|
|
50
56
|
- **Eklediğin alan `nullable: true` olmalı.** Platform bu tabloya EF üzerinden de yazıyor
|
|
51
|
-
(
|
|
52
|
-
|
|
57
|
+
(bakım yolları dâhil) ve EF senin kolonunu bilmiyor; NOT NULL + varsayılansız bir kolon o yolu
|
|
58
|
+
kırar.
|
|
53
59
|
- **`password` alanı `Hasher.Hash`'lidir.** Satır yazarken hash'i ifadeyle ürettir
|
|
54
60
|
(`crypto.SHA256.encode(...)` — gerçek algoritma için `platform_guide('expressions')`); düz metin
|
|
55
61
|
yazarsan giriş hiçbir zaman eşleşmez.
|
|
56
62
|
- **`isAuditTrailActive`'i AÇMA.** `users` tablosunun audit kolonları EF tarafında `Ignore`
|
|
57
63
|
edilmiş; `sys_*` eklemek EF'in bilmediği kolonlar yaratır.
|
|
58
|
-
-
|
|
59
|
-
|
|
60
|
-
|
|
64
|
+
- **Tablo hem platform operatörünü hem uygulama kullanıcısını taşıyabilir** ve roller
|
|
65
|
+
`page.roleIds` yetkilendirmesiyle ortaktır. Ama **yeni projede boş gelir**: Studio operatörü
|
|
66
|
+
kendiliğinden uygulama kullanıcısı olmaz. İlk kullanıcıyı açmanın yolu
|
|
67
|
+
`platform_guide('getting-started')` §"İlk kullanıcıyı kim açar?".
|
|
61
68
|
- **İlişki aynı datasource içinde kurulur.** `users` built-in datasource'tadır; entity'lerin dış
|
|
62
69
|
bir DB'deyse `users`'a ilişki kuramazsın.
|
|
70
|
+
- **Sonuç: kimlik gerektiren her uygulama built-in datasource'ta kurulur.** Entity'lerini dış bir
|
|
71
|
+
DB'ye açarsan giriş, `user.id` kapsaması ve roller kullanılamaz hâle gelir — bunlar `users`'a
|
|
72
|
+
ilişki üzerinden bağlıdır. Built-in her projeyle birlikte doğar (`datasource_list` çıktısında
|
|
73
|
+
`isBuiltIn: true`); `datasource_create` yalnız **kullanıcı kendi veritabanını verdiğinde**
|
|
74
|
+
çağrılır ve o kurulum bu bedeli kabul etmiş olur.
|
|
63
75
|
|
|
64
76
|
### Karar: `users`'ı genişlet mi, ayrı entity mi?
|
|
65
77
|
|
|
@@ -76,6 +88,50 @@ kurmak. O noktada iki ayrı kullanıcı tablosu, iki ayrı hash düzeni ve `midd
|
|
|
76
88
|
ile hiç konuşmayan bir yetkilendirme olur. Gateway bu şekli ENT-009 ile hatırlatır (warn — bilerek
|
|
77
89
|
yapıyorsan devam et).
|
|
78
90
|
|
|
91
|
+
### Rol yönetimi — `roles` da sıradan bir entity
|
|
92
|
+
|
|
93
|
+
**`role_create` diye bir tool yoktur ve olmayacak.** Rol tablosu built-in datasource'ta yaşar;
|
|
94
|
+
uygulamanın rol açması/düzenlemesi gerekiyorsa yol her domain tablosuyla aynıdır: entity + kendi
|
|
95
|
+
microservice ucun + sayfa. `role_list` yalnız okur.
|
|
96
|
+
|
|
97
|
+
`roles` entity'sinin açtığı alanlar — fiziksel tabloda ayrıca `template_*` kolonları var, hepsi
|
|
98
|
+
nullable, gateway'den görünmez:
|
|
99
|
+
|
|
100
|
+
| alan | not |
|
|
101
|
+
|---|---|
|
|
102
|
+
| `id` | uuid PK; `page.roleIds` ve `microservice.roles` BUNU ister |
|
|
103
|
+
| `name` | **UNIQUE** (`ix_roles_name`) — aynı adı ikinci kez yazan insert veritabanı hatasıyla düşer |
|
|
104
|
+
| `slug` | **yetkilendirmenin karşılaştırdığı alan**; `role_list` bunu DÖNDÜRMEZ |
|
|
105
|
+
|
|
106
|
+
**İki farklı anahtar, karıştırma:**
|
|
107
|
+
|
|
108
|
+
- **id** → `page_create/page_update` ve `microservice_create` `roleIds` alanı (gövdede `roles` diye bir
|
|
109
|
+
yazma alanı YOKTUR; `microservice.roles` middleware'de RUNTIME şeklidir, `{ id, name: '<slug>' }`).
|
|
110
|
+
`role_list`ten okunur.
|
|
111
|
+
- **slug** → `Authorized(user.roles)`, middleware'in kurduğu `user.roles` / `Page.user.roles`.
|
|
112
|
+
Platformun get-user sorgusu `roles`'u `user_roles` üzerinden **slug dizisi** olarak üretir
|
|
113
|
+
(`SELECT … ARRAY(SELECT r.slug FROM roles r INNER JOIN user_roles ur …)`), auth middleware
|
|
114
|
+
tarifi de aynı şeyi yapar — `platform_guide('middleware')` §Rol karşılaştırması SLUG üzerinden.
|
|
115
|
+
|
|
116
|
+
Bu yüzden **slug'sız yazılan rol sessizdir:** insert 200 döner, satır `role_list`te görünür,
|
|
117
|
+
`page.roleIds` ile bağlanır — ama `Authorized(user.roles)` o rol için hiçbir zaman tutmaz. Gateway
|
|
118
|
+
bunu `ROL-002` ile uyarır.
|
|
119
|
+
|
|
120
|
+
Rol açma reçetesi:
|
|
121
|
+
|
|
122
|
+
1. `entity_get('01917ede-88a2-72ea-8397-2619e5d5dca0')` ile alan id'lerini al.
|
|
123
|
+
2. Yazma ucunu kur: `microservice_create` `source:'table'`, `entityId` = roles, `operation:'create'`,
|
|
124
|
+
`fields` = `name` + `slug` (slug'ı ifadeyle ada göre üretmek serbest; **`super-admin` slug'ını
|
|
125
|
+
verme** — platform süper yöneticiyi `slug == 'super-admin'` ile arar).
|
|
126
|
+
3. Rolü kullanıcıya bağlamak ayrı bir satırdır: `user_roles` (`01917ede-be1e-7d62-9a7c-d0ce4e4daaaf`)
|
|
127
|
+
`user_id` + `role_id`; çift **(user_id, role_id) UNIQUE**'tir, aynı bağı iki kez yazma.
|
|
128
|
+
4. Yeni rol, kullanıcının bir sonraki isteğinde geçerlidir — middleware rolleri her çağrıda
|
|
129
|
+
tablodan okur; jeton tazelemeye gerek yoktur (rolleri jetona kendi gömen bir middleware
|
|
130
|
+
yazdıysan orada geçerli değil).
|
|
131
|
+
|
|
132
|
+
`permissions` / `role_permissions` **Studio'nun yetki matrisidir**, uygulama sayfasının yetkisi
|
|
133
|
+
değil; uygulama tarafında yetkiyi `page.roleIds` ve middleware ifadesi kurar.
|
|
134
|
+
|
|
79
135
|
## Alan tipleri (dbType + commonType İKİSİ birden verilir)
|
|
80
136
|
|
|
81
137
|
| dbType (BÜYÜK HARF zorunlu) | commonType |
|
|
@@ -101,6 +157,11 @@ yapıyorsan devam et).
|
|
|
101
157
|
`description`'a yaz: para → `INT8` kuruş (`*_minor`), süre → `INT4` dakika, yüzde →
|
|
102
158
|
`INT4` ×100, puan → `INT4` ×10.
|
|
103
159
|
- `commonType`'sız `UUID`/`BOOL` "isn't a supported database type" hatasıyla reddedilir.
|
|
160
|
+
- **Katalogu okumak gerekiyorsa** (bu datasource'ta hangi `dbType` var, hangisi varsayılan, tip
|
|
161
|
+
hangi seçenekleri kabul ediyor, hangi tipe dönüştürülebilir) `datasource_configuration_options`
|
|
162
|
+
çağır: varsayılan yanıt ~5 KB'lık **özettir**; 816 kayıtlık collation DEĞER listesi yalnız
|
|
163
|
+
`includeCollations: true` ile gelir (tam yanıt ~45 KB). Katalogda görünen bir tipin
|
|
164
|
+
`entity_create` tarafından kabul edileceğini varsayma — yukarıdaki ondalık maddesi bunun kanıtı.
|
|
104
165
|
|
|
105
166
|
## Alan tipine karar verme (önce buna bak, sonra yaz)
|
|
106
167
|
|
|
@@ -138,13 +199,35 @@ olduğu için serbest metindir; `course_assignments.semester` (Güz/Bahar/Yaz) e
|
|
|
138
199
|
"relationType": "none", "nullable": false,
|
|
139
200
|
"isEnum": true,
|
|
140
201
|
"valueOptions": [
|
|
141
|
-
{ "value": "Aktif", "icon": "#47ad10" },
|
|
142
|
-
{ "value": "Bırakıldı", "icon": "#888888" },
|
|
143
|
-
{ "value": "Tamamlandı", "icon": "#0052CC" }
|
|
202
|
+
{ "value": "Aktif", "icon": "#47ad10", "order": 1 },
|
|
203
|
+
{ "value": "Bırakıldı", "icon": "#888888", "order": 2 },
|
|
204
|
+
{ "value": "Tamamlandı", "icon": "#0052CC", "order": 3 }
|
|
144
205
|
] }
|
|
145
206
|
```
|
|
146
207
|
|
|
147
208
|
- `valueOptions[].icon` bir **hex renktir**, ikon adı değil.
|
|
209
|
+
- **`valueOptions[].order` seçeneğin İŞ SIRASIDIR (2026-09-05).** Huni, aşama, öncelik gibi
|
|
210
|
+
sıralı raporlarda sırayı sorguya statik `VALUES` listesi olarak gömmek gerekmez — sıra alan
|
|
211
|
+
tanımında durur ve `entity_get` ile geri gelir. Verilmezse `0`'dır; sıralı bir enum tanımlarken
|
|
212
|
+
doldurun, çünkü etiket değişince sorguya gömülmüş liste sessizce yanlış sıralar.
|
|
213
|
+
- **Sıra SQL'den de okunur.** `value_options` tenant veritabanında `fields` tablosunda `jsonb`
|
|
214
|
+
kolondur ve anahtarları küçük harftir (`value` · `icon` · `order`). Sıralı bir rapor sırayı
|
|
215
|
+
buradan JOIN'ler:
|
|
216
|
+
|
|
217
|
+
```sql
|
|
218
|
+
WITH ord AS (
|
|
219
|
+
SELECT opt->>'value' AS status, (opt->>'order')::int AS pos
|
|
220
|
+
FROM fields f CROSS JOIN LATERAL jsonb_array_elements(f.value_options) AS opt
|
|
221
|
+
WHERE f.entity_id = '<entity id>' AND f.slug = 'status')
|
|
222
|
+
SELECT o.status, COALESCE(c.total, 0) AS candidates
|
|
223
|
+
FROM ord o
|
|
224
|
+
LEFT JOIN (SELECT status, COUNT(*) AS total FROM candidates GROUP BY status) c
|
|
225
|
+
ON c.status = o.status
|
|
226
|
+
ORDER BY o.pos
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Ölçüldü 2026-09-05 (`query_preview`): dört durumluk bir enum bu sorguyla doğru sırada döndü,
|
|
230
|
+
sorguda tek bir sabit değer yok.
|
|
148
231
|
- `isEnum` Studio tarafında `valueOptions.length > 0` ile türetilir; yine de ikisini birlikte ver.
|
|
149
232
|
- **Enum PostgreSQL'de CHECK constraint üretir.** Var olan bir metin alanını enum'a çevirirken
|
|
150
233
|
tablodaki değerler `valueOptions` içinde yoksa migration `check constraint ... is violated` ile
|
|
@@ -181,6 +264,11 @@ Girdi sınırları (`IncrementalCodeInput.TryParse`; ihlal edilirse nesne geçer
|
|
|
181
264
|
(`EntityIncrementSequenceService.ResolveStartingCounterAsync`), yani elle `TLP-0005` yazdıktan
|
|
182
265
|
sonra nesne biçimiyle yazılan ilk satır `TLP-0006` olur. `Counter: 1` vermek "1'den başla"
|
|
183
266
|
demek değil, "taban 1" demektir.
|
|
267
|
+
- **Kodu kaydetmeden görmek:** `row_next_code` aynı nesneyi alır (`prefix` · `separator` ·
|
|
268
|
+
`counter` · `digitCount`) ve sıradaki kodu sayacı ilerletmeden döner (`persist: false`).
|
|
269
|
+
`counter` ile `digitCount` platform tarafında **zorunludur** (`GetNextIncrementalCodeQuery`
|
|
270
|
+
validator'ı `NotEmpty` der, yani 0 kabul edilmez); eksik gönderirsen uç 400 verir — gateway de
|
|
271
|
+
bu ikisini zorunlu tutar (ölçüm: 09-02 D-2).
|
|
184
272
|
- `properties.isCaseSensitive` büyük/küçük harf duyarlılığını belirler (`Field.IsCaseSensitiveIncremental`).
|
|
185
273
|
- Sayfa tarafındaki karşılığı `incrementalinput` komponentidir.
|
|
186
274
|
- `datasource_list.dataTypes` çıktısında bu tipi ayrı bir grup olarak görürsün; listede
|
|
@@ -194,6 +282,15 @@ parametre YOKTUR). Kısıt (`FieldDtoValidator`): `isLocalized: true` iken `prim
|
|
|
194
282
|
· `unique` `false` olmalı ve `relationType: 'none'` olmalı — bunlardan biri doluysa platform
|
|
195
283
|
reddeder.
|
|
196
284
|
|
|
285
|
+
**`display: true` alanı `isLocalized` OLABİLİR** (ürün kararı 2026-09-08). Alan companion'a
|
|
286
|
+
taşınır ama okuyucular onu çözer: `entity_get` companion alanını ana listeye katar, `row_list`
|
|
287
|
+
bire-çok ilişkinin etiketini istek dilinde döndürür (`kategori_id: { id, ad }`).
|
|
288
|
+
|
|
289
|
+
> **İlişkinin hedefinde de çalışır.** Çoka-çok listesi `[{ id, <display> }]` olarak istek dilinde
|
|
290
|
+
> gelir, join'li (bire-çok) tablonun çok dilli kolonu mikroservis alan seçicisinde görünür ve
|
|
291
|
+
> `returnScheme`'e girer. Çevirisi olmayan satır varsayılan dile düşer, listeden düşmez.
|
|
292
|
+
> Ölçüldü 2026-09-08 (`dil-iliski2`: `etiketler[].ad` ve `kategori_id.ad` iki dilde).
|
|
293
|
+
|
|
197
294
|
**Platform bunun için ayrı bir uç açmaz.** Bir entity'de `isLocalized` alanı olduğunda (create ya
|
|
198
295
|
da update'te) platform otomatik olarak `{entity_slug}_localization` adlı **companion bir entity**
|
|
199
296
|
kurar/genişletir:
|
|
@@ -208,6 +305,29 @@ language Text
|
|
|
208
305
|
artı `(relation, language)` üzerinde unique index. Yani bir satırın Türkçe/İngilizce değerleri
|
|
209
306
|
**ayrı satırlardır**, ana tabloda değil bu companion entity'de yaşar.
|
|
210
307
|
|
|
308
|
+
**Satır araçları companion'ı senin yerine konuşur — normalde companion'a hiç dokunmazsın:**
|
|
309
|
+
|
|
310
|
+
| İstediğin | Çağrı |
|
|
311
|
+
|---|---|
|
|
312
|
+
| Satırı bir dilde yaz | `row_create(row, localizations: { tr: {…}, en: {…} })` |
|
|
313
|
+
| Toplu yaz | `row_create(rows: [{ …, localizations: { tr: {…} } }])` — `localizations` SATIRIN İÇİNDE |
|
|
314
|
+
| Yalnız çeviriyi güncelle | `row_update(row: { <pk>: … }, localizations: { en: {…} })` — ana satır değişmese de olur |
|
|
315
|
+
| Satırı bir dilde oku | `row_list(language: 'en')` / `row_get(language: 'en')` — alan düz gelir |
|
|
316
|
+
| Bütün dilleri gör | `row_list(includeTranslations: true)` → satırda `localizations.<dil>.<alan>` |
|
|
317
|
+
| Çok dilli alana filtre/sıralama | `row_list(language, filters)` — alan slug'ı düz yazılır |
|
|
318
|
+
|
|
319
|
+
`language` verilmezse platform `Accept-Language`'a, o da yoksa projenin varsayılan diline düşer;
|
|
320
|
+
bölgeli kod ('tr-TR') kabul edilir ve gateway 'tr'ye indirger. **Okumada çevirisi olmayan alan
|
|
321
|
+
varsayılan dile düşer ama filtre ve sıralama fallback GÖRMEZ** — o satır filtrede eşleşmez ve
|
|
322
|
+
sıralamada `NULL` sayılır.
|
|
323
|
+
|
|
324
|
+
Dil satırlarını platform yazar: `row_delete` companion satırlarını ana satırdan önce kendisi siler,
|
|
325
|
+
desteklenmeyen bir dil kodu 400 döner ve ana satır da yazılmaz. Yanıtta
|
|
326
|
+
`gatewayNormalized: ["platform 'localizations' alanını tanımıyor …"]` görürsen platform bu
|
|
327
|
+
sözleşmeden eski bir sürümdedir; gateway o zaman companion'a kendisi yazar ve `localizations.count`
|
|
328
|
+
ile kaç dil satırı yazdığını söyler — `localizationsFailed` ise ana satır yazılmış, dil satırları
|
|
329
|
+
yazılmamıştır. Aşağıdaki elle tarif yalnız o eski yol ve `rows` ile toplu yazma için gerekir.
|
|
330
|
+
|
|
211
331
|
**Kullanım (tamamı mevcut genel araçlarla, yeni tool GEREKMEZ):**
|
|
212
332
|
|
|
213
333
|
```
|
|
@@ -219,6 +339,14 @@ artı `(relation, language)` üzerinde unique index. Yani bir satırın Türkçe
|
|
|
219
339
|
|
|
220
340
|
`{fk_slug}` companion entity'nin `entity_get`'inde görünen ilişki alanının slug'ıdır (ana entity
|
|
221
341
|
adından türetilir); satırı elle kurmadan önce mutlaka `entity_get` ile doğrula, tahmin etme.
|
|
342
|
+
Companion'ı adından değil **ana entity'nin `localizationEntityId` alanından** bul — ad kalıbı
|
|
343
|
+
(`{slug}_localization`) bir kolaylıktır, bağ ise `entity_get`'in döndürdüğü bu id'dir.
|
|
344
|
+
|
|
345
|
+
> **Toplu uçta `localizations` SATIRIN İÇİNDEDİR** (2026-09-08): `rows: [{ kod: 'A1',
|
|
346
|
+
> localizations: { tr: { ad: 'Kalem' }, en: { ad: 'Pen' } } }]`. Üst düzeye koymak toplu uçta
|
|
347
|
+
> hangi satıra ait olduğunu söyleyemeyeceği için gateway tarafından reddedilir. `localizations`
|
|
348
|
+
> bu yüzden hem okuma hem yazma tarafında **ayrılmış bir anahtardır**: `includeTranslations: true`
|
|
349
|
+
> ile okunan satırda da aynı adla gelir, yani bu adda gerçek bir kolonun olamaz.
|
|
222
350
|
|
|
223
351
|
```jsonc
|
|
224
352
|
// GUID anahtar
|
|
@@ -249,6 +377,10 @@ adından türetilir); satırı elle kurmadan önce mutlaka `entity_get` ile doğ
|
|
|
249
377
|
|
|
250
378
|
Gateway bu iki kuralı ENT-006 (PK identity/tip uyumu) ve ENT-007 (enum şekli) olarak denetler.
|
|
251
379
|
|
|
380
|
+
> **Çok dilli bir entity TEK adımda silinir.** `entity_delete` ana entity'ye uygulanır, companion
|
|
381
|
+
> da onunla düşer. (Aynı gün daha erken bir ölçümdeki "önce companion'ı sil" adımı artık geçersiz;
|
|
382
|
+
> EF izleme hatası giderildi. Ölçüldü 2026-09-08, `dil-iliski`.)
|
|
383
|
+
|
|
252
384
|
## Application bağı — ne yapar, ne yapmaz
|
|
253
385
|
|
|
254
386
|
`applicationIds` **create'te platformca bağlanır** (`CreateEntityCommand` → `EntityService.CreateAsync`
|
|
@@ -273,8 +405,9 @@ Karar: **Studio'dan yönetilecek entity'lerde `applicationIds` ver; yalnız API'
|
|
|
273
405
|
boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zorunlu kılmaz).
|
|
274
406
|
|
|
275
407
|
> `entity_patch` uyarısı: `EntityDto` çıktıda `applications` (nesne listesi) döner ama
|
|
276
|
-
> `UpdateEntityCommand` girdide `applicationIds` (Guid listesi) okur.
|
|
277
|
-
>
|
|
408
|
+
> `UpdateEntityCommand` girdide `applicationIds` (Guid listesi) okur. `PATCH /entities/{id}`
|
|
409
|
+
> yolunda bu çeviriyi **platform** yapar; ham PUT atarken **siz yapmazsanız her güncelleme bağı
|
|
410
|
+
> sessizce siler.**
|
|
278
411
|
|
|
279
412
|
## İlişkiler (doğru şekil — 57 alanla doğrulandı)
|
|
280
413
|
|
|
@@ -300,6 +433,122 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
|
|
|
300
433
|
- İki entity'yi önce ilişkisiz oluşturup ilişki alanını sonra `entity_patch` ile eklemek en
|
|
301
434
|
güvenli sıra (id'ler ancak oluşunca belli olur).
|
|
302
435
|
- `display: true` entity başına tek alanda olabilir; ilişkilerde görüntülenen değer bu alandır.
|
|
436
|
+
- **Boş ilişki `null` döner** (2026-09-09'dan beri). Öncesinde platform her üyesi `null` olan bir
|
|
437
|
+
nesne (`{"name": null, "id": null}`) döndürüyordu; gateway `prune` bunu `{}`'ye indirdiği için
|
|
438
|
+
ajan tarafında `{}` görünüyordu ve JS'te truthy olduğu için `if (row.manager_id)` "yöneticisi
|
|
439
|
+
var" diyordu (İK koşusu D-8). Şekil `row_list`'in okuma yolunda düzeltildi. **Gateway `null`
|
|
440
|
+
alanları tümüyle atar**, yani `row_list` çıktısında boş ilişki alanını **hiç görmezsin** —
|
|
441
|
+
alanın yokluğu "boş" demektir.
|
|
442
|
+
|
|
443
|
+
## Index'ler
|
|
444
|
+
|
|
445
|
+
`indexes` alanı `entity_create` ve `entity_update` gövdesinde geçer.
|
|
446
|
+
**Studio'da index ekranı YOKTUR** — bu alan platformun tek index yönetim yüzeyidir, yanlış
|
|
447
|
+
yazdığın bir index'i arayüzden görüp düzeltemezsin. Tam şema:
|
|
448
|
+
`ngapps://schemas/CreateEntityCommand` (`EntityIndexDto`).
|
|
449
|
+
|
|
450
|
+
```jsonc
|
|
451
|
+
{ "indexes": [
|
|
452
|
+
{ "id": "<guid, opsiyonel — verilmezse platform üretir>",
|
|
453
|
+
"name": "candidate_company_status",
|
|
454
|
+
"unique": true,
|
|
455
|
+
"fieldIds": ["<field id>", "<field id>"] }
|
|
456
|
+
] }
|
|
457
|
+
```
|
|
458
|
+
|
|
459
|
+
Alan adları 422 ile kendini söyler; **sessiz olan kurallar** şunlar:
|
|
460
|
+
|
|
461
|
+
- **`unique` — `isUnique` DEĞİL.** Yanlış anahtar hata vermez, platform onu yok sayar ve index
|
|
462
|
+
UNIQUE OLMADAN kurulur. Aynı tuzak `fields` için de var, ama o 422 döner
|
|
463
|
+
(`Indexes[0].FieldIds: 'Field Ids' must not be empty`); doğru ad **`fieldIds`**.
|
|
464
|
+
- **`fieldIds` sırası bileşik index'in KOLON SIRASIDIR.** Dizideki konum `Order` olarak
|
|
465
|
+
yazılır, DDL kolonları o sırayla kurar. `(company_id, status)` ile `(status, company_id)`
|
|
466
|
+
farklı index'lerdir; öndeki kolon önek sorgularını hızlandıran kolondur.
|
|
467
|
+
- **`entity_update` gövdesinde `indexes` YOKSA entity'nin BÜTÜN index'leri silinir** — kayıt da
|
|
468
|
+
düşer, `DROP INDEX` de koşar. Gateway `entity_update` çağrısında alan verilmemişse mevcut
|
|
469
|
+
index'leri gövdeye taşır ve bunu `gatewayNormalized` ile bildirir; silmek istiyorsan
|
|
470
|
+
`indexes: []` yazarak **açıkça** iste.
|
|
471
|
+
- **Var olan bir index'te `name` / `unique` / `fieldIds` değiştirmek İŞE YARAMAZ:** platform farkı
|
|
472
|
+
yalnız `id` üzerinden alır, aynı `id` ile yapılan değişiklik UYGULANMAZ ve hata da vermez.
|
|
473
|
+
Değiştirmenin yolu index'i silip **yeni bir `id`** ile yeniden yazmaktır.
|
|
474
|
+
|
|
475
|
+
**Sıra:** `fieldIds` alan **id'si** ister, slug değil — ve alan id'lerini platform ÜRETİR
|
|
476
|
+
(`FieldDto.Id` gövdede yoksa sunucuda atanır). İki geçerli yol var:
|
|
477
|
+
|
|
478
|
+
1. Entity'yi index'siz kur, `entity_get view:'outline'` ile alan id'lerini oku, index'i
|
|
479
|
+
`entity_patch` ile ekle. **Varsayılan yol budur.**
|
|
480
|
+
2. Tek çağrıda kuracaksan `fields[].id`'leri kendin ver ve aynı guid'leri `fieldIds`'e yaz.
|
|
481
|
+
(Ölçüldü 2026-09-07: üç alanı ve bileşik unique index'i olan bir entity tek `entity_create`
|
|
482
|
+
çağrısıyla kuruldu.)
|
|
483
|
+
|
|
484
|
+
Var olmayan bir alan id'si reddedilir ama **hata mesajı yanıltıcıdır**: platform
|
|
485
|
+
`This record is in use. Related table: 'Fields', column: 'Field Id'. Please remove its relations
|
|
486
|
+
first.` der — sanki bir bağımlılık varmış gibi. Gerçek sebep bunun tersidir: o id'de alan YOKTUR.
|
|
487
|
+
Bu mesajı alınca bağımlılık aramaya gitme, `fieldIds`'i alan listesiyle karşılaştır.
|
|
488
|
+
|
|
489
|
+
Doğrulama kuralları (hepsi 422 döner):
|
|
490
|
+
|
|
491
|
+
- `fieldIds` boş olamaz ve aynı alanı iki kez taşıyamaz.
|
|
492
|
+
- `name` zorunlu; **önek hariç** en fazla 32 karakter.
|
|
493
|
+
- Index adları entity içinde benzersizdir (büyük/küçük harf duyarsız).
|
|
494
|
+
|
|
495
|
+
Ada dokunma: platform ada `IX_<entity id'sinin 5 hanesi>_` öneki ekler ve okuma yolu **önekli**
|
|
496
|
+
adı döner. Gidiş-dönüş kararlıdır — okuduğun adı geri yazabilirsin, önek ikinci kez eklenmez.
|
|
497
|
+
|
|
498
|
+
Kurulabilen tek şekil **artan (ASC), çok kolonlu, opsiyonel UNIQUE** b-tree'dir. `DESC`, kısmi
|
|
499
|
+
index (`WHERE`), ifade index'i, `INCLUDE` ve GIN/GiST platform yüzeyinde yoktur; bunlardan birine
|
|
500
|
+
ihtiyacın varsa `platform_gap_report` ile bildir.
|
|
501
|
+
|
|
502
|
+
Platformun kendi ürettiği örnek, bileşik unique için okunabilir bir kalıptır: çoklu dilli alan
|
|
503
|
+
kurulunca yan entity'ye `(relation, language)` üzerinde unique bir index açılır — yani "bir
|
|
504
|
+
satırın her dilde en fazla bir çevirisi olur" kuralı index'le zorlanır.
|
|
505
|
+
|
|
506
|
+
Yazdığın index'i `entity_get view:'outline'` çıktısındaki `indexes` bloğundan doğrula; DDL'in
|
|
507
|
+
gerçekten kurulduğunu görmek istiyorsan `query_preview` ile
|
|
508
|
+
`select indexname, indexdef from pg_indexes where tablename = '<slug>'` sorgusunu koştur —
|
|
509
|
+
`entity_get` yalnız metadata'yı kanıtlar, `CREATE UNIQUE INDEX` satırını değil.
|
|
510
|
+
|
|
511
|
+
## Backend doğrulama sınırları (ENT-015 · ENT-016 · ENT-017 · ENT-018 · ENT-004 · ENT-013)
|
|
512
|
+
|
|
513
|
+
Bu tablo `CreateEntityCommandValidator` / `UpdateEntityCommandValidator` / `FieldDtoValidator`'ın
|
|
514
|
+
aynasıdır. Gateway aynı kuralı **istek gitmeden** `block` olarak uygular ve mesaj platformun 422
|
|
515
|
+
cümlesidir — 2026-09-08 koşusunda bu kurallar ancak platforma gidip öğrenildi (index adı 33
|
|
516
|
+
karakter, primaryKey yok). Tam katalog, kaynak referanslı JSON: `ngapps://rules/backend-constraints`.
|
|
517
|
+
|
|
518
|
+
| Alan | Kural | ID |
|
|
519
|
+
|---|---|---|
|
|
520
|
+
| `name` | zorunlu, en fazla 255 karakter | ENT-015 |
|
|
521
|
+
| `description` | en fazla 1024 | ENT-015 |
|
|
522
|
+
| `slug` | zorunlu (create) | ENT-015 |
|
|
523
|
+
| `fields[].name` | en fazla 50 | ENT-015 |
|
|
524
|
+
| `fields[].description` | en fazla 512 | ENT-015 |
|
|
525
|
+
| `fields[].slug`, `fields[].dbType` | zorunlu; dbType en fazla 50 | ENT-015 |
|
|
526
|
+
| `fields[]` | **tam 1** alan `primaryKey: true` — "Please select exactly one primary key." | ENT-016 |
|
|
527
|
+
| `fields[]` | adlar tekil (büyük/küçük harf duyarsız): create `name`, update `slug` üzerinden | ENT-016 |
|
|
528
|
+
| `fields[].display` | en fazla 1 alan | ENT-004 |
|
|
529
|
+
| `primaryKey` / `identity` | `nullable: false`; PK ayrıca `hidden: false` | ENT-017 |
|
|
530
|
+
| `isLocalized` | `primaryKey`/`identity`/`unique` yok, `relationType: 'none'` | ENT-017 |
|
|
531
|
+
| `folderId` | yalnız `commonType: 'file'` alanında | ENT-017 |
|
|
532
|
+
| `properties.generationType` | yalnız `commonType` `guid` ya da `number` | ENT-017 |
|
|
533
|
+
| `commonType` / `defaultValueType` / `relationType` | sözlükten; bilinmeyen değer 400 `InvalidEnumTokenException` (`multiFile` diye bir tip YOK) | ENT-017 |
|
|
534
|
+
| `recordTransferMode: 'specific'` | `transferRowIds` dolu, tekil, boşsuz | ENT-018 |
|
|
535
|
+
| `indexes[]` | ad önek hariç en fazla 32, `fieldIds` dolu ve tekil, adlar tekil | ENT-013 |
|
|
536
|
+
|
|
537
|
+
**Birincil anahtar kalıbı** (bu rehberde yoktu; 2026-09-08 koşusunda iki hatalı çağrı + bir
|
|
538
|
+
keşif turuna mal oldu):
|
|
539
|
+
|
|
540
|
+
```jsonc
|
|
541
|
+
{ "slug": "id", "name": "Id", "dbType": "UUID", "commonType": "guid",
|
|
542
|
+
"primaryKey": true, "unique": true, "identity": true, "nullable": false,
|
|
543
|
+
"properties": { "generationType": 0 } }
|
|
544
|
+
```
|
|
545
|
+
|
|
546
|
+
**Create tuzağı:** create yolu alan adlarını `name` üzerinden tekil arar. `name` yazılmayan iki alan
|
|
547
|
+
aynı (boş) gruba düşer ve platform "Duplicate value: 'Slug'" der — mesaj slug'ı suçlar, sebep ad
|
|
548
|
+
eksikliğidir. Her alana `name` yaz; update yolu aynı denetimi `slug` üzerinden yapar.
|
|
549
|
+
|
|
550
|
+
Satır yazma da aynı katalogdan denetlenir (ROW-002, `row_create` / `row_update`): satır sözlüğü
|
|
551
|
+
boş olamaz, boş anahtar taşıyamaz, bütün değerleri null olamaz; toplu yolda her satır ayrı bakılır.
|
|
303
552
|
|
|
304
553
|
## TEXT kolonunda JSON: yazarken string, okurken NESNE döner
|
|
305
554
|
|
|
@@ -328,8 +577,58 @@ bilmezsen "kayıt bozulmuş" sanırsın.
|
|
|
328
577
|
|
|
329
578
|
## Satır işlemleri
|
|
330
579
|
|
|
580
|
+
- `row_*` tool'ları `entityId` olarak slug kabul eder (`employees`, çakışmada
|
|
581
|
+
`BuiltIn/employees`); gateway id'ye çevirir ve yanıtta `gatewayResolved` ile bildirir.
|
|
331
582
|
- `row_create/row_update` anahtarları **field slug'larıdır** (name değil): `{"code": "IK"}`.
|
|
332
|
-
-
|
|
583
|
+
- **Satırlar bir DOSYADAN geliyorsa gövdeye hiç koyma: `path`.** `row_create { entityId, path:
|
|
584
|
+
"seed.csv" }` gateway'e dosyayı okutur, 1000'lik partilere böler ve ardışık bulk çağırır;
|
|
585
|
+
bağlama tek bir `{ written, batches }` döner. Ölçüm: 170 KB'lık tohum verisi `rows[]` ile üç
|
|
586
|
+
kez ajan bağlamından geçti (2026-09-03 koşusu P3-3). `.json` satır dizisi ya da `.csv` (UTF-8,
|
|
587
|
+
ayraç ilk satırdan; `;` sayısı `,` sayısını geçerse `;`) okur, değerler string gider ve tipe
|
|
588
|
+
backend çevirir. Sınır 50 MB / 100.000 satır — üstü içe aktarma zinciridir.
|
|
589
|
+
- **Partiler arası transaction YOKTUR.** Her parti kendi içinde tek transaction'dır: düşen parti
|
|
590
|
+
kendi satırlarını geri alır, **önceki partiler yazılmış kalır**. Gateway ilk düşen partide durur
|
|
591
|
+
ve `stopped.fileLine` ile hangi DOSYA SATIRINDAN devam edileceğini söyler. Hepsi-ya-hiç
|
|
592
|
+
gerekiyorsa `path` değil `import_run` kullanın.
|
|
593
|
+
- **İlişki alanına id yerine ad yazmak: `relationsByName: true`.** `departman: "Satış"` hedef
|
|
594
|
+
tablonun display ya da benzersiz alanında aranır ve id'ye çevrilir; yalnız toplu yolda
|
|
595
|
+
(`rows` / `path`) çalışır. Eşleşmeyen değer 422 döner ve hangi satırın hangi alanı olduğunu
|
|
596
|
+
söyler — sessizce boş bırakmaz. Öncesinde m2m ve tekil ilişki için `row_list` ile display → id
|
|
597
|
+
sözlüğü elle kuruluyordu.
|
|
598
|
+
- **Toplu yazımda (`rows[]`) yanıt `data: [{ "<pk slug>": değer }, …]` döner** — istek sırasında,
|
|
599
|
+
her satır için bir kayıt. `row_create` bunu 2026-09-04'ten beri, `row_update` **2026-09-09'dan
|
|
600
|
+
beri** yapar; o tarihe kadar toplu update yalnız `{success:true}` dönüyordu ve hangi satırın
|
|
601
|
+
yazıldığını öğrenmek için fazladan bir `row_list` turu gerekiyordu.
|
|
602
|
+
- Yazdıktan sonra `row_list` ile geri oku; ilişki ve dosya alanları `{ id, <displayAlanı> }`
|
|
603
|
+
nesnesi olarak döner.
|
|
604
|
+
- **Okunan şekli olduğu gibi geri yazabilirsin (2026-09-09'dan beri).** İlişki, dosya ve m2m
|
|
605
|
+
alanlarında yazma yolu hem id string'ini hem `{ id, … }` nesnesini kabul eder; nesne verilirse
|
|
606
|
+
`id` okunur. Öncesinde bu **sessizce veri kaybettiriyordu**: dosya alanında `500
|
|
607
|
+
InvalidCastException`, tekil ilişkide `200` + FK'nın `NULL`'a düşmesi, m2m'de `200` + bağın
|
|
608
|
+
silinmesi (2026-09-09 koşusu S-01). İd string'i hâlâ en kısa ve en güvenli şekildir.
|
|
609
|
+
- **Boş to-one ilişki `null`, boş many-to-many alan `[]` döner** (2026-09-09'dan beri). m2m
|
|
610
|
+
öncesinde JSON metni olarak `""` dönüyordu — `Array.isArray` `false`, `forEach` çökerdi (S-02).
|
|
611
|
+
Gateway `null` alanları attığı için boş to-one ilişki yanıtta **hiç görünmez**.
|
|
612
|
+
|
|
613
|
+
### Alan tipi × yazarken × okurken
|
|
614
|
+
|
|
615
|
+
Tek tabloda tüm şekiller. "Yazarken" sütunundaki **her iki** biçim de kabul edilir; id string'i en
|
|
616
|
+
kısa ve en güvenli olandır.
|
|
617
|
+
|
|
618
|
+
| Alan tipi | Yazarken | Okurken (`row_list`) | Boşken okurken |
|
|
619
|
+
|---|---|---|---|
|
|
620
|
+
| tekil ilişki (`relation`, one-to-many) | `"<id>"` · `{ "id": "<id>", … }` | `{ id, <displayAlanı> }` | `null` (gateway alanı hiç göstermez) |
|
|
621
|
+
| tekil dosya (`file`) | `"<fileId>"` · `{ "id": "<fileId>", … }` | `{ id, display_name }` | `null` (gateway alanı hiç göstermez) |
|
|
622
|
+
| çoklu ilişki (m2m) | `["<id>", …]` · `[{ "id": "<id>", … }, …]` | `[{ id, <displayAlanı> }, …]` | `[]` |
|
|
623
|
+
| çoklu dosya (FILE m2m) | `["<fileId>", …]` · `[{ "id": "<fileId>", … }, …]` | `[{ id, display_name }, …]` | `[]` |
|
|
624
|
+
| `text` kolonda JSON | `JSON.stringify(...)` (string) | **ayrıştırılmış** nesne/dizi | — |
|
|
625
|
+
| enum (`valueOptions`) | değerin kendisi (`"active"`) | aynı string | `null` |
|
|
626
|
+
| ilişki, `relationsByName: true` ile | görünen ad (`"Satış"`) — display ya da benzersiz alan | `{ id, <displayAlanı> }` | `null` |
|
|
627
|
+
|
|
628
|
+
Üç tuzak aynı tablodan okunur: (1) yazma ve okuma şekli **farklıdır**, ama 2026-09-09'dan beri
|
|
629
|
+
okunan şekil geri yazılabilir; (2) `text` kolonda JSON'u okurken **ikinci kez** `JSON.parse` etme;
|
|
630
|
+
(3) `row_get` ilişki alanlarını `row_list`'ten farklı olarak **düz FK** döner — nesne bekleyen kod
|
|
631
|
+
orada `undefined` alır.
|
|
333
632
|
- **`isAuditTrailActive: true` ile KURULAN entity'ye satır yazılamaz.** `sys_*` kolonları create
|
|
334
633
|
yolunda oluşmaz, yalnız ilk **etkin** `entity_update`'inde eklenir; o ana kadar **her**
|
|
335
634
|
`row_create` şu hatayla 500 verir:
|
|
@@ -363,18 +662,411 @@ bilmezsen "kayıt bozulmuş" sanırsın.
|
|
|
363
662
|
> da yan kayıt oluşmaz; `true` + `basic/detailed` iken "anonim" dediğin satırın yanında IP
|
|
364
663
|
> durur. Bu, ürünün gizlilik vaadini sessizce bozan tek ayardır.
|
|
365
664
|
|
|
366
|
-
-
|
|
367
|
-
|
|
368
|
-
"varsayılanı
|
|
369
|
-
|
|
370
|
-
`
|
|
371
|
-
`
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
665
|
+
- **Varsayılan alanın adı `default-value`, `static` DEĞİL.** `defaultValueType` üç değer alır:
|
|
666
|
+
`none` · `default-value` (sabit değer, `defaultValue` ile birlikte) · `sql-expression`.
|
|
667
|
+
`defaultValueType: "default-value"` **gerçekten DB varsayılanı kurar** — `ALTER TABLE … ADD
|
|
668
|
+
COLUMN … NOT NULL DEFAULT …` üretilir, dolu tabloda mevcut satırlar da o değerle dolar (ölçüm
|
|
669
|
+
2026-09-09: 3 satırlık tabloya tek çağrıda `NOT NULL DEFAULT 0` eklendi, üç satır da `0` oldu).
|
|
670
|
+
Eski "`static` sessizce `none`'a düşer" notu bir **yanlış anlaşılmaydı**: `static` diye bir değer
|
|
671
|
+
hiç olmadı, geçersiz belirteç sessizce `none`'a düşüyordu. 2026-09-09'dan beri geçersiz belirteç
|
|
672
|
+
**istek anında reddedilir** ve kabul edilen değerleri sayar:
|
|
673
|
+
`Please use a valid value for 'DefaultValueType': none, default-value, sql-expression. Received: 'static'.`
|
|
674
|
+
Aynı düzeltme **her** enum alanı için geçerlidir — yanlış yazılmış bir `operator`, `module` ya da
|
|
675
|
+
`commonType` artık sessizce ilk üyeye düşmez.
|
|
676
|
+
- **Dolu tabloya zorunlu (NOT NULL) alan eklemenin iki yolu var**, ikisi de ölçüldü:
|
|
677
|
+
1. Alana varsayılan ver (`defaultValueType: "default-value"` + `defaultValue`) → **tek çağrı**.
|
|
678
|
+
2. Önce `nullable: true` ekle → satırları `row_update` ile doldur → `entity_patch` ile
|
|
679
|
+
`/fields[slug=…]/nullable` `false` yap → **üç tur**.
|
|
680
|
+
|
|
681
|
+
Varsayılansız zorunlu alan doğrudan eklenirse istek artık bu iki yolu adıyla söyleyen bir hata
|
|
682
|
+
döner (eskiden ham migration hatası geliyordu: `column "…" contains null values`).
|
|
683
|
+
- **`row_list.filters` desteklenir.** Tekil filtre `{ field, operator, value, filterType:'single' }`,
|
|
684
|
+
grup filtre `{ filterType:'group', conditionType, children:[...] }` biçimindedir. Tekil filtrede
|
|
685
|
+
`field`, grupta `children` zorunludur; `in`/`notin` operatörleri liste değer ister. Eksik/yanlış
|
|
686
|
+
şekiller SQL derleyicisine düşmeden alanı ve beklenen şekli söyleyen doğrulama hatası döner.
|
|
687
|
+
- **`row_create` identity Guid birincil anahtara verdiğin `id`'yi artık korur** — 2026-09-04
|
|
688
|
+
öncesinde platform değeri sessizce kendi ürettiğiyle değiştiriyordu (`EntityRuntimeValidator`);
|
|
689
|
+
şimdi değer verilmişse o yazılır, verilmemişse üretilir. Seed ve veri taşımada "gönderilen id ile
|
|
690
|
+
oluşan id farklı" sınıfı buradan geliyordu. `users` tablosunun kendi kısıtları ayrıdır; oradaki
|
|
691
|
+
eşlemeyi e-posta üzerinden kurmak en güvenli yoldur.
|
|
375
692
|
- Silmeden önce `entity_dependencies` / `dependency_graph`.
|
|
376
693
|
|
|
694
|
+
## Toplu ve türetilmiş satır
|
|
695
|
+
|
|
696
|
+
`row_*` bir **veritabanı yönetim yüzeyidir**: satırı gövdede taşır, iş kurallarını koşturur,
|
|
697
|
+
anahtarı üretir. **Var olan satır kümesinden TÜRETİLEN** bir küme onun işi değildir — durum
|
|
698
|
+
geçmişi, hareket/log tablosu, junction dolgusu, backfill. İki koşu bunu elle yazdı (2026-09-05:
|
|
699
|
+
802 satır, 2026-09-06: 552 satır); ikincisinde koşunun 65 dakikasının **~21'i** tek bu faza gitti,
|
|
700
|
+
gateway süresi ise aynı fazda toplam 7.9 sn'ydi — maliyetin tamamı gövde yazmaktı.
|
|
701
|
+
|
|
702
|
+
Türetilmiş satırın yolu **ham SQL**'dir: `INSERT … SELECT` kaydedilir ve query kaynaklı bir uçtan
|
|
703
|
+
çalıştırılır. `candidate_id` gibi bağlar `SELECT`'in içinde kalır, hiçbir id yanıttan yanıta
|
|
704
|
+
taşınmaz.
|
|
705
|
+
|
|
706
|
+
| Satırlar nereden geliyor | Yol |
|
|
707
|
+
|---|---|
|
|
708
|
+
| Dışarıdan: kullanıcı verisi, içe aktarma, sabit liste | `row_create` / `row_update` |
|
|
709
|
+
| Var olan satırlardan türetiliyor: geçmiş, hareket, junction, backfill | kayıtlı `INSERT … SELECT` + `source: 'query'` uç |
|
|
710
|
+
|
|
711
|
+
> **Ham SQL UYGULAMA katmanını atlar, VERİTABANI katmanını atlamaz.**
|
|
712
|
+
> Koşmayan: automation'lar (satır olayı hiç doğmaz), `EntityRuntimeValidator`'ın tip ayrıştırma
|
|
713
|
+
> ve identity anahtar üretimi, incremental kod üretimi, çoklu dilli companion yazımı, audit izi.
|
|
714
|
+
> **Koşan:** tablonun bütün veritabanı kısıtları — NOT NULL, FK, UNIQUE ve **enum'un CHECK
|
|
715
|
+
> constraint'i**. Ölçüldü: `source` alanına listede olmayan bir değer yazmak
|
|
716
|
+
> `23514 … violates check constraint "check_<alan id'si>"` veriyor (HTTP 500). Kısıt adı alanın
|
|
717
|
+
> **id'sini** taşır (tireler alt çizgi olur), kolon adını değil — 23514 alırken suçluyu böyle
|
|
718
|
+
> bulursun. Yani enum'a çöp yazamazsın, ama iş kuralın automation'daysa o çalışmaz:
|
|
719
|
+
> seed/backfill için istediğin tam olarak budur, **iş kuralı çalışması gereken satırı bu yoldan
|
|
720
|
+
> yazma.**
|
|
721
|
+
|
|
722
|
+
### Reçete (uçtan uca ölçüldü, 2026-09-07)
|
|
723
|
+
|
|
724
|
+
```jsonc
|
|
725
|
+
// 1) sorguyu KAYDET — query_create
|
|
726
|
+
{ "name": "Aday Geçmişi Dolgusu", "slug": "seed_candidate_history",
|
|
727
|
+
"datasourceId": "<built-in datasource id>",
|
|
728
|
+
"rawQuery": "INSERT INTO status_history (id, candidate_id, status, occurred_at) SELECT gen_random_uuid(), c.id, c.status, c.applied_at FROM candidate c" }
|
|
729
|
+
|
|
730
|
+
// 2) ucu aç — microservice_create (MS-002 warn'ı bekleniyor: ham SQL bilinçli tercih)
|
|
731
|
+
{ "name": "Aday Geçmişi Dolgusu", "slug": "seed-candidate-history",
|
|
732
|
+
"description": "Var olan adaylardan durum geçmişi satırlarını üretir (tek seferlik dolgu).",
|
|
733
|
+
"type": "rest-api", "httpMethod": "post", "url": "seed-candidate-history",
|
|
734
|
+
"source": "query", "queryId": "<query id>" }
|
|
735
|
+
|
|
736
|
+
// 3) çalıştır — microservice_execute { method: 'POST' } → HTTP 204, GÖVDE YOK
|
|
737
|
+
// 4) doğrula — row_list('<hedef entity>') → satırlar burada; sayım tek doğrulama yolu
|
|
738
|
+
```
|
|
739
|
+
|
|
740
|
+
### Ölçülmüş sözleşme
|
|
741
|
+
|
|
742
|
+
- **Tablo adı = entity slug'ı, kolon adı = alan slug'ı.** İlişki alanının kolonu da kendi
|
|
743
|
+
slug'ıdır (`src_id`), ayrı bir FK adı türetilmez. Şema öneki YAZMA: bağlantı zaten ortamın
|
|
744
|
+
şemasına bağlıdır.
|
|
745
|
+
- **Anahtar sözleşmesi anahtar TİPİNE göre TERSİNE döner** — ikisi de `identity: true` +
|
|
746
|
+
`generationType: 0` olduğu hâlde:
|
|
747
|
+
|
|
748
|
+
| Anahtar | Ham SQL'de | Yanlışta ne olur |
|
|
749
|
+
|---|---|---|
|
|
750
|
+
| `UUID` / `guid` | anahtarı **SEN ver** (`gen_random_uuid()`) | `23502: null value in column "id" … violates not-null constraint` (HTTP 500) |
|
|
751
|
+
| `INT2/INT4/INT8` / `number` | anahtarı **HİÇ VERME** | `428C9: cannot insert a non-DEFAULT value into column "id" … GENERATED ALWAYS` (HTTP 500) |
|
|
752
|
+
|
|
753
|
+
Sebebi: GUID anahtarı **uygulama** üretiyor (`EntityRuntimeValidator`, `Guid.CreateVersion7()`),
|
|
754
|
+
kolonun veritabanı varsayılanı yok; INT anahtar ise Postgres `GENERATED ALWAYS` identity'sidir.
|
|
755
|
+
Alan tanımındaki `specifications.hasAutoData: true` bir **Studio ipucudur, DB varsayılanı
|
|
756
|
+
değildir** — ham SQL'de sana anahtarı üretmez.
|
|
757
|
+
- **`gen_random_uuid()` v4 üretir**, platformun kendi anahtarları v7'dir (zaman sıralı). Aynı
|
|
758
|
+
tabloda iki sürüm karışır; anahtara göre sıralayan bir ekranın varsa sıralamayı zaman
|
|
759
|
+
kolonundan yap.
|
|
760
|
+
- **`query_preview` yazan sorguyu çalıştırır ve GERİ ALIR ve bunu SÖYLEMEZ.** Yanıt yalnız
|
|
761
|
+
`{ affectedRowCount, elapsedMilliseconds }` taşıyor; `affectedRowCount` gerçek sayıyı döndüğü
|
|
762
|
+
için başarı gibi görünüyor, tabloda satır kalmıyor. Kalıcılığın tek kanıtı sonradan çekilen
|
|
763
|
+
`row_list`'tir.
|
|
764
|
+
- **`query_create` gövdeyi kaydetmeden önce bir kez koşturur ve geri alır** — yazan bir sorguyu
|
|
765
|
+
kaydetmek satır bırakmaz, ama SQL hatası kayıt anında patlar. Yazan sorgunun `returnScheme`'i
|
|
766
|
+
boş döner (yanıt şeması yalnız okuyan sorgudan üretilir).
|
|
767
|
+
- **Uç `204` ve BOŞ gövde döner** — `affectedRowCount` bile gelmez. Kaç satır yazıldığını
|
|
768
|
+
`row_list` ile say.
|
|
769
|
+
- `;` ile birden çok ifade ya da `RETURNING` içeren sorgu `multiple` tipine düşer ve okuma
|
|
770
|
+
yolundan koşar (kaynak: `PostgreSqlConnector.FindQueryType`); tek ifadeli sade `INSERT` için
|
|
771
|
+
tip `insert`'tür.
|
|
772
|
+
- **`WITH … INSERT` (CTE önekli yazma) BUGÜN REDDEDİLİYOR.** Ölçüldü 2026-09-07: `query_create`
|
|
773
|
+
`500 "Please correct the query syntax."`, `query_preview` `400 "Please use a supported query
|
|
774
|
+
type."` veriyor. Sorgu Postgres'te geçerlidir; reddeden, platformun tip sınıflandırıcısıdır
|
|
775
|
+
(`FindMainCommand` `WITH`i çözemeyip `NotSupportedQueryTypeException` atıyor). Depo kodunda
|
|
776
|
+
`WITH` çözümü **var** (`bcf722d1`, 2026-09-04) ama canlıda yok — `B-63`. **Bugünkü yazım:**
|
|
777
|
+
CTE yerine `JOIN (VALUES …) AS x(kolon, kolon)` kullan ve ifade `INSERT` ile başlasın.
|
|
778
|
+
|
|
779
|
+
### Merdiven kalıbı: aşama geçmişi (S-4'ün tam şekli)
|
|
780
|
+
|
|
781
|
+
Her adayın **bulunduğu aşamaya kadarki** geçişlerini üretir. 300 adaydan **579 satır** tek çağrıda
|
|
782
|
+
yazıldı (ölçüldü 2026-09-07); huni piramidi 210 → 135 → 90 → 60 → 39 → 24 → 12 → 6 → 3, FK'sız
|
|
783
|
+
satır 0, `applied_at`'ten önceye düşen satır 0.
|
|
784
|
+
|
|
785
|
+
```sql
|
|
786
|
+
INSERT INTO candidate_status_history (id, candidate_id, from_status, to_status, occurred_at)
|
|
787
|
+
SELECT gen_random_uuid(), c.id, p.stage, n.stage,
|
|
788
|
+
c.applied_at + (n.pos - 1) * interval '3 days'
|
|
789
|
+
FROM candidate c
|
|
790
|
+
JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
|
|
791
|
+
AS cur(pos, stage) ON cur.stage = c.status -- adayın bugünkü aşaması
|
|
792
|
+
JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
|
|
793
|
+
AS n(pos, stage) ON n.pos > 1 AND n.pos <= cur.pos -- ona kadarki her aşama
|
|
794
|
+
JOIN (VALUES (1,'Applied'),(2,'Screening'),(3,'Interview'),(4,'Offer'),(5,'Hired'))
|
|
795
|
+
AS p(pos, stage) ON p.pos = n.pos - 1 -- bir önceki aşama
|
|
796
|
+
```
|
|
797
|
+
|
|
798
|
+
Merdiven listesi CTE olamadığı için üç kez tekrarlanır (yukarıdaki `WITH` maddesi). Zaman damgası
|
|
799
|
+
`applied_at`'ten türetildiği için geçmiş **tarihseldir** — `UtcNow()` tuzağı (`AUT-003`) bu yolda
|
|
800
|
+
hiç doğmaz, çünkü automation yoktur.
|
|
801
|
+
|
|
802
|
+
Toplu **güncelleme** de aynı yola aittir: var olan satırlardan hesaplanan bir damga/toplam için
|
|
803
|
+
`UPDATE … FROM` yaz, satırları `row_update`'in gövdesinde taşıma.
|
|
804
|
+
|
|
805
|
+
## İçe aktarma — dosyayı gövdede taşıma
|
|
806
|
+
|
|
807
|
+
Excel/CSV/JSON'dan gelen bir tabloyu `row_create` gövdesiyle satır satır yazmayın. 300 satırlık bir
|
|
808
|
+
listeyi elle taşımak iki koşunun en pahalı kalemiydi; dosyanın kendisi zaten elinizde.
|
|
809
|
+
|
|
810
|
+
**Hangi yol?** Tablo ZATEN VARSA ve satır sayısı 5.000'in altındaysa `row_create { path }`
|
|
811
|
+
yeter — tek adım, şema kurmaz, kuru koşu yapmaz. Şema kurmak, sütun tipini tartışmak, ilişki
|
|
812
|
+
adaylarını tespit etmek ya da hepsi-ya-hiç garantisi gerekiyorsa `import_*` zinciri gerekir:
|
|
813
|
+
kuru koşusu vardır, hataları satır satır listeler ve sınırlı kapsamla geri alınabilir (§4).
|
|
814
|
+
|
|
815
|
+
Zincir dört çağrıdır ve **hiçbiri satırı gövdede taşımaz**:
|
|
816
|
+
|
|
817
|
+
```
|
|
818
|
+
import_analyze { datasourceId, path } → sayfalar, sütunlar, çıkarılan tipler, ilk satırlar
|
|
819
|
+
import_plan { datasourceId, fileId, tables } → planId + KURU KOŞU sayıları ve satır hataları
|
|
820
|
+
import_run { datasourceId, planId } → jobId (202), iş arka planda
|
|
821
|
+
import_status { jobId } → validating → writing → completed + sonuç
|
|
822
|
+
```
|
|
823
|
+
|
|
824
|
+
`path` yerine `fileId` de verilebilir (dosya `file_upload` ile önceden yüklendiyse).
|
|
825
|
+
|
|
826
|
+
### 1. Analiz — hiçbir şey yazılmaz
|
|
827
|
+
|
|
828
|
+
Tipi, başlık satırını ve yerel ayarı görmeden plan gövdesi yazmayın. Yerel ayar sonucun
|
|
829
|
+
kendisidir: `tr-TR` profilinde `15.03.2024` tarih ve `42.500,00` ondalık; `en-US` profilinde
|
|
830
|
+
ikisi de **metin** olur. Yanıttaki `unconvertible` sıfır değilse önce `locale`'i sorgulayın,
|
|
831
|
+
sonra sütun tipini.
|
|
832
|
+
|
|
833
|
+
`summary` sayfa/satır/aday/uyarı sayısını, `note` ilişki adaylarını özetler.
|
|
834
|
+
|
|
835
|
+
**`unique` bayrağı bir gözlemdir, kısıt değil.** "Bu dosyadaki bütün değerler farklıydı" demektir;
|
|
836
|
+
30 satırlık bir örnekte maaş ya da ad sütunu da benzersiz çıkar. Plan gövdesinde `field.unique`
|
|
837
|
+
yalnız **anahtar sütun** için `true` verilir (`flags` içinde `key` olan); her benzersiz sütunu
|
|
838
|
+
unique kurmak tabloyu tek dosyaya kilitler ve aynı zammı iki kişiye veren bir sonraki dosya
|
|
839
|
+
`unique` hatasıyla düşer.
|
|
840
|
+
|
|
841
|
+
### 1a. JSON dosyası — kök dört biçimden biri
|
|
842
|
+
|
|
843
|
+
`.json` de okunur ve aynı zincirden geçer. Kökün şekli sayfaların nasıl çıkacağını belirler:
|
|
844
|
+
|
|
845
|
+
| Kök | Ne olur |
|
|
846
|
+
|---|---|
|
|
847
|
+
| Nesne dizisi `[{...}, {...}]` | Tek sayfa; sayfa adı dosya adıdır, başlık satırı anahtarların birleşimidir (ilk görülme sırasına göre) |
|
|
848
|
+
| Tek nesne `{ "ad": "Test" }` | Tek satırlık sayfa; mevcut bir tabloya tek kayıt eklemenin en kısa yolu |
|
|
849
|
+
| HER özelliği nesne dizisi olan nesne `{ "iller": [...], "ilceler": [...] }` | Her özellik ayrı sayfa; sayfa adı özelliğin adıdır — çok tablolu planın JSON karşılığı budur. Bir özellik bile nesne dizisi değilse kök tek satır sayılır |
|
|
850
|
+
| `{ "meta": {...}, "data": [...] }` | `row_export`'un ürettiği gövde; `data` tek sayfa olur, `meta` `_zeyrek` sayfasına düşer ve analizde atlanır |
|
|
851
|
+
|
|
852
|
+
Boş nesne, skaler kök, sözdizimi ya da kodlama hatası gerekçeli `400 ImportFileUnreadableException`
|
|
853
|
+
alır. Metin BOM'a, yoksa katı UTF-8'e, olmazsa windows-1254'e göre çözülür; `headerRow` yok sayılır.
|
|
854
|
+
|
|
855
|
+
**JSON'da tip dosyadan gelir, yerel ayardan değil.** Sayı ve `true`/`false` doğrudan okunur;
|
|
856
|
+
`locale` yalnız metin olarak yazılmış tarih ve sayıları çözmek için kalır. Eksik anahtar boş
|
|
857
|
+
hücredir. İç içe nesne sıkıştırılmış JSON metnine düşer. Skaler dizi `"; "` ile birleşir —
|
|
858
|
+
`multi-value` adayı bu sayede JSON'da da çıkar, ayracı `"; "` verin.
|
|
859
|
+
|
|
860
|
+
### 1b. İlişki adayları — tekrar eden sütunlar
|
|
861
|
+
|
|
862
|
+
Analiz her sayfa için `relationCandidates` döner. Bunlar **öneridir**: seçimi `import_plan`
|
|
863
|
+
gövdesindeki `columns[].relation` yapar. Aday yoksa sütun metin kalır ve hiçbir tablo açılmaz;
|
|
864
|
+
`recommended` değeri `text` olan bir aday da (bkz. `manual`) siz seçmedikçe hiçbir şey açmaz.
|
|
865
|
+
|
|
866
|
+
| `kind` | Ne gördü | Seçenekler | Ne zaman hangisi |
|
|
867
|
+
|---|---|---|---|
|
|
868
|
+
| `category` | Az sayıda değer kendini tekrar ediyor | `relation-existing` · `relation-new` · `select` · `text` | `target.existingEntityId` doluysa **önce onu dene** (`relation-existing`); yoksa 20'den az değer için `select`, üstü için `relation-new` |
|
|
869
|
+
| `multi-value` | Bir hücrede ayraçla ayrılmış liste | `multi-relation-new` · `multi-relation-existing` · `multi-select` · `text` | Ayracı `delimiter` ile geçir. `multi-relation-new` ayrı bir tablo, ana tabloda bir m2m alanı ve junction tablosunu kurar; 20'den az değer için `multi-select` daha ucuzdur. **MEVCUT tabloya** `many: true` yazılıyorsa m2m alanı tabloda zaten olmalı — yoksa plan `ImportPlanStale` ile düşer |
|
|
870
|
+
| `hierarchy` | İki sütun arasında üst/alt bağımlılığı (`ilçe → il`) | `hierarchy-two-tables` · `hierarchy-parent-only` · `text` | `hierarchy-two-tables` varsayılan; `parentColumnIndex` ve `parentTable` ALT sütunun `relation`'ına konur |
|
|
871
|
+
| `cross-sheet` | Değerler başka bir sayfanın benzersiz sütununda | `relation-sheet` · `text` | O sayfayı da `tables`'a ekleyin; `target.sheetKey` hangi sayfa olduğunu söyler |
|
|
872
|
+
| `manual` | Hiçbir şey tekrar etmiyor — sütun serbest metinden ayırt edilemiyor | `relation-existing` · `relation-new` · `text` | Değerler mevcut bir tabloyla örtüşüyorsa önerisi `relation-existing` olur (personel dosyasındaki `İl` sütunu, satır başına benzersiz olsa bile `İller` tablosuna bağlanır); örtüşme yoksa öneri `text`'tir ve **tablo açmak insan kararıdır**. Ortalama 40 karakterden uzun metin hiç önerilmez |
|
|
873
|
+
|
|
874
|
+
**Platform tablosunu önce dene.** `roles` (ad) ve `users` (e-posta) projede zaten vardır; aday
|
|
875
|
+
onlarla eşleşiyorsa yeni tablo açmak yerine `relation-existing` seçin — yoksa aynı kavramın iki
|
|
876
|
+
tablosu olur.
|
|
877
|
+
|
|
878
|
+
**`fdViolations > 0` ise bağımlılık yaklaşıktır.** Aday yine gelir ama kaç satırın kuralı bozduğunu
|
|
879
|
+
söyler; `unmatched: 'error'` ile koşun ve düşen satırları rapor dosyasından okuyun.
|
|
880
|
+
|
|
881
|
+
**`parentCandidates` — her sütun bir üst sütunun altına bağlanabilir.** Aday, `column` ·
|
|
882
|
+
`distinctCount` · `nameViolations` üçlüsüyle gelir. Ad tekrarı bağlanmaya engel değildir:
|
|
883
|
+
bir alt kaydın kimliği adı değil, **üstüyle birlikte adıdır**. Türkiye verisinde "Merkez" ilçesi
|
|
884
|
+
onlarca ilde, "Cumhuriyet" mahallesi onlarca ilçede geçer; `nameViolations` bunu sayar, seçim
|
|
885
|
+
sizindir. Seçtiğinizde `parentColumnIndex` (ve yeni tablo açıyorsanız `parentTable`) o sütunun
|
|
886
|
+
`relation`'ına yazılır — hiyerarşi adayıyla aynı gövde.
|
|
887
|
+
|
|
888
|
+
### 1c. Hiyerarşi gövdesi — İl → İlçe → Mahalle
|
|
889
|
+
|
|
890
|
+
Zincir ayrı bir kural değildir: her ardışık çift kendi `hierarchy` adayını üretir ve her biri
|
|
891
|
+
**alt sütunun** `relation`'ına yazılır. Üst tablo ayrıca bir sütuna bağlanmaz.
|
|
892
|
+
|
|
893
|
+
```json
|
|
894
|
+
"columns": [
|
|
895
|
+
{ "index": 0, "include": true,
|
|
896
|
+
"relation": { "newTable": { "name": "İller", "slug": "iller" }, "unmatched": "error" } },
|
|
897
|
+
{ "index": 1, "include": true,
|
|
898
|
+
"relation": { "newTable": { "name": "İlçeler", "slug": "ilceler" },
|
|
899
|
+
"parentColumnIndex": 0,
|
|
900
|
+
"parentTable": { "name": "İller", "slug": "iller" },
|
|
901
|
+
"unmatched": "error" } },
|
|
902
|
+
{ "index": 2, "include": true,
|
|
903
|
+
"relation": { "newTable": { "name": "Mahalleler", "slug": "mahalleler" },
|
|
904
|
+
"parentColumnIndex": 1,
|
|
905
|
+
"parentTable": { "name": "İlçeler", "slug": "ilceler" },
|
|
906
|
+
"unmatched": "error" } }
|
|
907
|
+
]
|
|
908
|
+
```
|
|
909
|
+
|
|
910
|
+
Planın döndüğü `tables[].order` **backend planlayıcısından gelir** (FK topolojik sırası):
|
|
911
|
+
`iller` → `ilceler` → `mahalleler` → ana tablo. Ajan sıralamaz, sıraya güvenir.
|
|
912
|
+
|
|
913
|
+
Ana tabloda **ilişki yazdığınız her sütun için** bir ilişki alanı açılır: yukarıdaki gövde
|
|
914
|
+
`il`, `ilce` ve `mahalle` için üç alan verir. Bir sütuna doğrudan bağlanmak istemiyorsanız
|
|
915
|
+
`relation`'ını yazmayın, yalnız `parentColumnIndex` hedefi olarak bırakın; alt seviye yine
|
|
916
|
+
kapsamlı çözülür.
|
|
917
|
+
|
|
918
|
+
**Zincir kimlikle çözülür, adla değil.** Satır yazılırken en üstteki sütun kapsamsız, her alt
|
|
919
|
+
seviye bir üstündeki çözülmüş id ile eşlenir; alt tablo satırları da üst zincirinin tamamıyla
|
|
920
|
+
tekilleştirilir. Bu yüzden "Bolu/Merkez" ile "Çorum/Merkez" ayrı iki ilçe satırı olur.
|
|
921
|
+
|
|
922
|
+
**Adaylar en yakın üstten başlar.** `import_analyze` zincirde üç çift döndürür (`il→ilçe`,
|
|
923
|
+
`il→mahalle`, `ilçe→mahalle`) ve sıralamayı üst sütunun farklı değer sayısına göre azalan yapar;
|
|
924
|
+
listedeki ilk aday `ilçe→mahalle` olur. Bir sütun yalnız **bir** ilişki taşıyabildiği için
|
|
925
|
+
`il→mahalle`'yi de seçerseniz `ilçe→mahalle` ezilir ve zincir kopar.
|
|
926
|
+
|
|
927
|
+
### 2. Plan — kuru koşu
|
|
928
|
+
|
|
929
|
+
`import_plan` tek satır bile yazmadan tip dönüşümünü, zorunluluğu, benzersizliği ve ilişki
|
|
930
|
+
çözümünü dener. Gövde analiz çıktısından kurulur:
|
|
931
|
+
|
|
932
|
+
```json
|
|
933
|
+
{
|
|
934
|
+
"datasourceId": "…",
|
|
935
|
+
"fileId": "…",
|
|
936
|
+
"locale": "tr-TR",
|
|
937
|
+
"errorPolicy": "skip-rows",
|
|
938
|
+
"tables": [
|
|
939
|
+
{
|
|
940
|
+
"sheetKey": "Personel",
|
|
941
|
+
"mode": "create",
|
|
942
|
+
"target": { "name": "Personel", "slug": "personel" },
|
|
943
|
+
"columns": [
|
|
944
|
+
{ "index": 0, "field": { "slug": "sicil_no", "name": "Sicil No", "commonType": "number", "dbType": "INT8", "unique": true, "nullable": false } },
|
|
945
|
+
{ "index": 1, "field": { "slug": "ad_soyad", "name": "Ad Soyad", "commonType": "text", "dbType": "VARCHAR", "display": true } }
|
|
946
|
+
]
|
|
947
|
+
},
|
|
948
|
+
{
|
|
949
|
+
"sheetKey": "Departmanlar",
|
|
950
|
+
"mode": "upsert",
|
|
951
|
+
"target": { "entityId": "…" },
|
|
952
|
+
"keyField": "ad",
|
|
953
|
+
"emptyCellPolicy": "keep",
|
|
954
|
+
"columns": [{ "index": 0, "existingFieldSlug": "ad" }]
|
|
955
|
+
}
|
|
956
|
+
]
|
|
957
|
+
}
|
|
958
|
+
```
|
|
959
|
+
|
|
960
|
+
- `mode: create` → `target.name` + `target.slug` ve her sütunda `field`.
|
|
961
|
+
- `mode: append | upsert | update` → `target.entityId` ve her sütunda `existingFieldSlug`.
|
|
962
|
+
- `upsert`/`update` → `keyField` **zorunlu** ve benzersiz olmalı (unique alan, tek alanlı unique
|
|
963
|
+
index, `code` ya da `id`); değilse 422.
|
|
964
|
+
- `emptyCellPolicy: keep` boş hücreyi yok sayar — mevcut değeri silmez ve "değişti" saymaz.
|
|
965
|
+
|
|
966
|
+
> **Kuru koşu hatalarını düzeltmeden `import_run` çağırmayın.** `errorPolicy: 'skip-rows'`
|
|
967
|
+
> hatalı satırı sessizce atlar; plandaki `errors` listesine bakmadan çalıştırırsanız eksik
|
|
968
|
+
> tablonun farkına varmazsınız. Hata oranı %50'yi geçerse `import_run` `force: true` ister.
|
|
969
|
+
|
|
970
|
+
### 3. Çalıştır ve izle
|
|
971
|
+
|
|
972
|
+
`import_run` **202** döner, gateway beklemez. `import_status` `writing` durumunda
|
|
973
|
+
`progress.written/total` verir ve `retryAfterSeconds: 5` ekler; `completed` olunca
|
|
974
|
+
`result.tables[].inserted/updated/unchanged/skipped` ve `reportFileId` gelir. Atlanan satırların
|
|
975
|
+
tam listesi o dosyadadır (`file_download`).
|
|
976
|
+
|
|
977
|
+
İçe aktarma **development kararıdır**: `import_plan`, `import_run`, `import_undo` ve `import_cancel`
|
|
978
|
+
production'da istek gitmeden reddedilir.
|
|
979
|
+
|
|
980
|
+
Şemalar: `ngapps://schemas/ImportAnalysisDto`, `ngapps://schemas/ImportPlanDto`,
|
|
981
|
+
`ngapps://schemas/DataJobDto`.
|
|
982
|
+
|
|
983
|
+
### 4. Geçmiş, iptal ve geri alma
|
|
984
|
+
|
|
985
|
+
- `import_history` en yeniden eskiye listeler; `kind: 'import'` planları da getirir.
|
|
986
|
+
- `import_cancel` `pending`/`validating`'i hemen, `writing`'i parti sonunda durdurur; yazılanlar kalır,
|
|
987
|
+
geri alınamaz. İş önce biterse 409.
|
|
988
|
+
- `import_undo` yalnız `undoAvailableUntil` dolu `completed` işte çalışır; 202 döner, bitmeden
|
|
989
|
+
tekrar çağrılmaz. Kapsam:
|
|
990
|
+
- açılan tablo, başka tablo bağlı değilse **bütün satırlarıyla** silinir;
|
|
991
|
+
- referans satırları silinir, güncellenen sütunlar önceki değerine döner;
|
|
992
|
+
- mevcut tabloya **eklenen** satırlar kalır (yalnız ekleme yapan işte pencere açılmaz);
|
|
993
|
+
- çakışma denetimi yoktur.
|
|
994
|
+
|
|
995
|
+
## Dışa aktarma ve geri yükleme
|
|
996
|
+
|
|
997
|
+
`row_export` bir tablonun satırlarını dosyaya yazar ve dosyayı **Sürücü'ye** koyar. Bağlama yalnız
|
|
998
|
+
`fileId`, ad ve bayt sayısı döner — içerik hiç geçmez, okumak isterseniz `file_download`.
|
|
999
|
+
|
|
1000
|
+
```
|
|
1001
|
+
row_export { entityId, format: 'xlsx' | 'csv' | 'json', scope, reimportable? }
|
|
1002
|
+
→ { fileId, fileName, bytes } ≤ 5.000 satır
|
|
1003
|
+
→ { jobId } üstü; import_status { jobId } ile izlenir,
|
|
1004
|
+
biten işin dosyası result.reportFileId
|
|
1005
|
+
```
|
|
1006
|
+
|
|
1007
|
+
Kapsam üç şekildedir: `{ kind: 'view', filters, sort }` (grid'in gördüğü küme),
|
|
1008
|
+
`{ kind: 'all' }`, `{ kind: 'selected', rowIds }` (en çok 1.000 id — üstü filtreyle).
|
|
1009
|
+
Sütunlar `columns: 'visible'` + `selectedFields` ile daraltılır. CSV UTF-8 BOM'ludur; ayraç ve
|
|
1010
|
+
ondalık projenin dilinden gelir (tr `;` · `1,5`, en `,` · `1.5`), şablonda `locale` ile seçilir.
|
|
1011
|
+
|
|
1012
|
+
### Akış C — dışa aktar, düzenle, geri yükle
|
|
1013
|
+
|
|
1014
|
+
Bir tabloyu toplu düzeltmenin yolu satır satır `row_update` değildir. `reimportable: true`
|
|
1015
|
+
dosyaya `id` sütununu ve `_zeyrek` meta sayfasını ekler; o iki şey olmadan geri yükleme hangi
|
|
1016
|
+
satırın hangisi olduğunu bilemez ve her satırı yeni kayıt sayar.
|
|
1017
|
+
|
|
1018
|
+
```
|
|
1019
|
+
row_export { entityId, reimportable: true } → fileId
|
|
1020
|
+
file_download { id: fileId, outPath } → dosya diske iner
|
|
1021
|
+
<dosyayı düzenle>
|
|
1022
|
+
file_upload { path } → yeni fileId
|
|
1023
|
+
import_analyze { datasourceId, fileId } → meta sayfa okunur, tablo tanınır
|
|
1024
|
+
import_plan { …, tables: [{ mode: 'upsert', keyField: 'id' }] }
|
|
1025
|
+
import_run { planId } → 202, import_status ile izlenir
|
|
1026
|
+
```
|
|
1027
|
+
|
|
1028
|
+
`keyField: 'id'` ile mod `upsert`: dosyadaki id eşleşen satır güncellenir, eşleşmeyen eklenir.
|
|
1029
|
+
`reimportable` ilişki sütunlarını ad+id olarak yazar, dolayısıyla adı elle değiştirseniz de id
|
|
1030
|
+
kazanır — ilişkiyi gerçekten değiştirmek istiyorsanız id sütununu düzenleyin.
|
|
1031
|
+
|
|
1032
|
+
### Şablon — dosyanın şeklini önce görün
|
|
1033
|
+
|
|
1034
|
+
`row_export { entityId, template: true, format?, locale? }` tablonun alanlarından **iki sentetik
|
|
1035
|
+
örnek satırlı** şablon üretir ve değiştirilmeden geri yüklenir: PK, sistem ve artan kod sütunu yok,
|
|
1036
|
+
`password` sıradan metin, ipucu veri alanında değil (xlsx başlık yorumu + `_zeyrek`, json `meta`).
|
|
1037
|
+
`inline: true` şekli yanıta da koyar: `sample.columns` (slug, başlık, tip, zorunlu, seçenekler),
|
|
1038
|
+
`sample.keyField`, `sample.data`. İlişki sütunu boş ve `manual`dır: hücreye hedefin `matchField`
|
|
1039
|
+
değerini yazın, şablon hedeften satır okumaz. `data` JSON biçimindedir (ondalık nokta, tarih
|
|
1040
|
+
`yyyy-MM-dd`); bu değerlerle CSV yazıyorsanız analiz ve planda `locale: 'en-US'` verin.
|
|
1041
|
+
`password` gibi gizli adlı sütunun örnek değeri gateway'de `***` maskelenir; kendi değerinizi
|
|
1042
|
+
yazın. Toplu yazımda satır satır `row_create` yerine:
|
|
1043
|
+
|
|
1044
|
+
```
|
|
1045
|
+
row_export { entityId, template: true, inline: true } → sample.columns, sample.data, sample.keyField
|
|
1046
|
+
<başlıkları aynen koruyarak CSV metni üret>
|
|
1047
|
+
file_upload { text, filename: '<slug>.csv' } → fileId
|
|
1048
|
+
import_analyze { datasourceId, fileId, locale } → sütun slug'ları = alan slug'ları
|
|
1049
|
+
import_plan { datasourceId, fileId, locale, tables: [{ mode: 'append' | 'upsert', keyField: sample.keyField, … }] }
|
|
1050
|
+
import_run { planId } → 202, import_status ile izlenir
|
|
1051
|
+
```
|
|
1052
|
+
|
|
377
1053
|
## Büyük entity'lerde
|
|
378
1054
|
|
|
379
1055
|
`entity_get view:'outline'` alan listesi; `view:'part', part:'fields[slug=email]'` tek alan;
|
|
380
1056
|
değişiklik `entity_patch`.
|
|
1057
|
+
|
|
1058
|
+
## Sürüm ve 409 — `entity_patch` tam gövde YAZMAZ
|
|
1059
|
+
|
|
1060
|
+
`entity_patch` okuduğu tanımın üstüne JSON Patch uygular ama geri **tam gövde göndermez**: op'lar
|
|
1061
|
+
platformun `PATCH /entities/{id}` ucuna `{ baseVersion, ops }` olarak gider ve yalnız değişen
|
|
1062
|
+
alanlar yazılır. Bir alan adı değişimi `fields` tablosunda **tek** UPDATE üretir (ölçüldü
|
|
1063
|
+
2026-09-08, `pg_stat_user_tables`).
|
|
1064
|
+
|
|
1065
|
+
`baseVersion` okunan kaydın `version` alanıdır ve zorunludur (eksikse 422). Sen okuduktan sonra
|
|
1066
|
+
kayıt değiştiyse platform **409 `AggregateVersionConflictException`** döner ve HİÇBİR op uygulanmaz
|
|
1067
|
+
— `entity_get` ile yeniden okuyup patch'i güncel hâle göre kur, aynı op'ları eski sürümle tekrar
|
|
1068
|
+
gönderme.
|
|
1069
|
+
|
|
1070
|
+
`version` bir **string**tir (mikrosaniye zaman damgası, `Number.MAX_SAFE_INTEGER`'ın üstünde):
|
|
1071
|
+
sayıya çevirirsen yuvarlanır ve her patch 409 döner. Yanıttaki `newVersion` bir sonraki patch'in
|
|
1072
|
+
`baseVersion`'ıdır.
|