@appaflytech/zeyrek-ai-mcp 0.0.6 → 0.0.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (98) hide show
  1. package/.env.example +2 -2
  2. package/.env.production.example +8 -0
  3. package/README.md +9 -8
  4. package/dist/config.js +18 -3
  5. package/dist/knowledge/backendConstraints.js +88 -0
  6. package/dist/knowledge/componentCatalog.js +415 -68
  7. package/dist/knowledge/componentEvents.js +120 -25
  8. package/dist/knowledge/functionGuards.js +134 -0
  9. package/dist/knowledge/normalize.js +512 -25
  10. package/dist/knowledge/outlines.js +31 -0
  11. package/dist/knowledge/policy.js +14 -5
  12. package/dist/knowledge/propTypes.js +5 -4
  13. package/dist/knowledge/rules.js +453 -6
  14. package/dist/knowledge/styleResolve.js +6 -1
  15. package/dist/platform/csv.js +123 -0
  16. package/dist/platform/errors.js +62 -3
  17. package/dist/platform/http.js +11 -2
  18. package/dist/platform/responses.js +16 -4
  19. package/dist/platform/screenshot.js +98 -9
  20. package/dist/platform/strictArguments.js +32 -0
  21. package/dist/platform/writeDiff.js +6 -3
  22. package/dist/resources/examples/flow-call-function.json +66 -0
  23. package/dist/resources/examples/function-total.json +70 -0
  24. package/dist/resources/examples/microfunction-node-sum.json +56 -0
  25. package/dist/resources/examples/page-dynamic-form.json +1 -1
  26. package/dist/resources/examples/page-sign-in.json +2 -2
  27. package/dist/resources/guides/component.md +14 -2
  28. package/dist/resources/guides/entity.md +321 -17
  29. package/dist/resources/guides/expressions.md +9 -0
  30. package/dist/resources/guides/functions.md +262 -0
  31. package/dist/resources/guides/getting-started.md +7 -0
  32. package/dist/resources/guides/layout.md +6 -0
  33. package/dist/resources/guides/merge.md +231 -0
  34. package/dist/resources/guides/microfunction.md +166 -0
  35. package/dist/resources/guides/microservice.md +166 -7
  36. package/dist/resources/guides/modify-existing.md +23 -0
  37. package/dist/resources/guides/page.md +101 -22
  38. package/dist/resources/guides/translation.md +35 -10
  39. package/dist/resources/guides/workflow.md +51 -2
  40. package/dist/resources/schema-fingerprint.json +4 -4
  41. package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
  42. package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
  43. package/dist/resources/schemas/CreateBulkRowCommand.json +3 -0
  44. package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
  45. package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
  46. package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
  47. package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
  48. package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
  49. package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
  50. package/dist/resources/schemas/CreateMicroserviceCommand.json +123 -2
  51. package/dist/resources/schemas/CreateWorkflowCommand.json +17 -3
  52. package/dist/resources/schemas/DataJobDto.json +267 -0
  53. package/dist/resources/schemas/ExportRowsCommand.json +172 -0
  54. package/dist/resources/schemas/Filter.json +3 -1
  55. package/dist/resources/schemas/GetRowsQuery.json +3 -1
  56. package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
  57. package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
  58. package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
  59. package/dist/resources/schemas/ImportPlanDto.json +260 -0
  60. package/dist/resources/schemas/ImportSampleDto.json +90 -0
  61. package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
  62. package/dist/resources/schemas/PreviewMicroserviceCommand.json +123 -2
  63. package/dist/resources/schemas/PreviewWorkflowCommand.json +17 -3
  64. package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
  65. package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
  66. package/dist/resources/schemas/RunImportCommand.json +62 -0
  67. package/dist/resources/schemas/StepDto.json +17 -3
  68. package/dist/resources/schemas/UpdateBulkRowCommand.json +3 -0
  69. package/dist/resources/schemas/UpdateEntityCommand.json +3 -1
  70. package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
  71. package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
  72. package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
  73. package/dist/resources/schemas/UpdateMicroserviceCommand.json +123 -2
  74. package/dist/resources/schemas/UpdateWorkflowCommand.json +17 -3
  75. package/dist/server.js +17 -4
  76. package/dist/tools/activities.js +40 -0
  77. package/dist/tools/automations.js +14 -5
  78. package/dist/tools/common.js +16 -0
  79. package/dist/tools/dependencies.js +1 -1
  80. package/dist/tools/environments.js +52 -0
  81. package/dist/tools/files.js +51 -50
  82. package/dist/tools/flows.js +104 -5
  83. package/dist/tools/functions.js +331 -0
  84. package/dist/tools/importSchemas.js +79 -0
  85. package/dist/tools/imports.js +241 -0
  86. package/dist/tools/layouts.js +4 -4
  87. package/dist/tools/merges.js +113 -0
  88. package/dist/tools/microfunctions.js +155 -0
  89. package/dist/tools/microservices.js +112 -9
  90. package/dist/tools/pages.js +188 -66
  91. package/dist/tools/platformInfo.js +4 -1
  92. package/dist/tools/queries.js +72 -6
  93. package/dist/tools/revisions.js +94 -0
  94. package/dist/tools/rows.js +342 -22
  95. package/dist/tools/screenWrite.js +11 -2
  96. package/dist/tools/templates.js +32 -5
  97. package/dist/tools/workflows.js +28 -6
  98. package/package.json +3 -1
@@ -0,0 +1,70 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": true,
4
+ "note": "toplamHesapla(fiyat, adet = 1) = fiyat * adet — aynı gövde İKİ KAYNAKTA: source:'studio' (uygulama kapsamlı, tarayıcı: {{ }} bağlaması ve UI flow 'code'/'function' adımı) ve source:'workflow' (sunucu: workflow/middleware/automation ifadesinde çıplak ad). Kod ve parametre biçimi aynıdır, fark nerede çalıştığıdır. Canlı kanıt: 2026-09-10 fonksiyon-demo sayfası ({{ Functions.toplamHesapla(10, 3) }} → 30) ve workflow_preview code adımı (return toplamHesapla(10, 3) → 30).",
5
+ "harvestedFrom": "kod + canlı doğrulama: zeyrek-backend Contexts/Studio/GlobalFunctions, zeyrek-frontend packages/ng-apps-flow/src/functions (2026-09-10)"
6
+ },
7
+ "_rules": {
8
+ "body": "Gövde DÜZ deyimlerdir, iki kaynakta aynı: 'return fiyat * adet;'. function (…) { } sarmalı ya da arrow yazılmaz. return serbesttir; dönüşsüz gövde undefined döner. Platform kaydetmeden sözdizimini ayrıştırır (422 GlobalFunctionBodyInvalid).",
9
+ "parameters": "Bildirim SIRALIDIR ve çağrı KONUMSALDIR (K5): toplamHesapla(10, 3) → fiyat=10, adet=3. defaultValue argüman undefined kalınca uygulanır; required kontrolü ondan SONRA çalışır ve 'toplamHesapla: \"fiyat\" is required' (studio'da 'Functions.toplamHesapla: …') fırlatır. type yalnız Studio imzası içindir, çalışma zamanı tip denetlemez.",
10
+ "scope": "Yalnız source:'studio'. application: her sayfada (tarayıcı). page: yalnız o sayfanın tarayıcı bağlamında, pageId zorunlu. Ad uygulama içinde kapsamlar arası benzersizdir (K3); workflow adı tenant içinde benzersizdir. Aynı ad iki kaynakta yan yana olabilir.",
11
+ "isAsync": "Yalnız studio'da anlamlı; false bırakın: true olsaydı {{ }} bağlaması '[object Promise]' çizerdi. await gerekmeyen gövdede isAsync:true FN-004 uyarısı alır. workflow kaynağında true FN-004 block (motor senkron).",
12
+ "naming": "Ad JS tanımlayıcısı; ayrılmış sözcük olamaz. studio'da 'Functions' yasak (FN-001, block); workflow'da motor global adları (system, input, Now, crypto…) yasak (FN-001, block). Uygulama fonksiyonunda sayfaya özgü ad (komponent adı, state.page), workflow fonksiyonunda adım adı kullanmayın — çağrıldığı yerde yoksa 'X is not defined'.",
13
+ "source": "workflow: SUNUCU, tenant geneli, applicationId/scope/pageId VERİLMEZ, isAsync olamaz, çıplak adla çağrılır. studio: TARAYICI, applicationId + scope zorunlu (application: her sayfada; page: yalnız o sayfa, pageId zorunlu), Functions.<ad>() ile çağrılır. Kaynak sonradan değişmez; sunucu ifadesinde Functions.<ad>( → WF-030 block."
14
+ },
15
+ "functionCreateArgs": {
16
+ "source": "studio",
17
+ "applicationId": "<application id>",
18
+ "scope": "application",
19
+ "name": "toplamHesapla",
20
+ "description": "fiyat × adet; adet verilmezse 1 sayılır",
21
+ "isAsync": false,
22
+ "parameters": [
23
+ {
24
+ "name": "fiyat",
25
+ "type": "number",
26
+ "required": true,
27
+ "description": "Birim fiyat"
28
+ },
29
+ {
30
+ "name": "adet",
31
+ "type": "number",
32
+ "defaultValue": 1,
33
+ "description": "Adet; verilmezse 1"
34
+ }
35
+ ],
36
+ "body": "return fiyat * adet;"
37
+ },
38
+ "workflowFunctionCreateArgs": {
39
+ "source": "workflow",
40
+ "name": "toplamHesapla",
41
+ "description": "fiyat × adet; adet verilmezse 1 sayılır",
42
+ "parameters": [
43
+ {
44
+ "name": "fiyat",
45
+ "type": "number",
46
+ "required": true,
47
+ "description": "Birim fiyat"
48
+ },
49
+ {
50
+ "name": "adet",
51
+ "type": "number",
52
+ "defaultValue": 1,
53
+ "description": "Adet; verilmezse 1"
54
+ }
55
+ ],
56
+ "body": "return fiyat * adet;"
57
+ },
58
+ "usage": {
59
+ "propBinding": {
60
+ "op": "set_binding",
61
+ "target": "txt_toplam",
62
+ "prop": "content",
63
+ "expression": "{{ Functions.toplamHesapla(10, 3) }}"
64
+ },
65
+ "uiFlowCodeStep": "const toplam = Functions.toplamHesapla(num_fiyat.value, num_adet.value);\nstate.page.sonuc = toplam;",
66
+ "uiFlowFunctionStep": "ngapps://examples/flow-call-function",
67
+ "workflowCodeStep": "return { toplam: toplamHesapla(10, 3) };",
68
+ "automationCondition": "toplamHesapla(row.fiyat, row.adet) > 1000"
69
+ }
70
+ }
@@ -0,0 +1,56 @@
1
+ {
2
+ "_meta": {
3
+ "synthetic": false,
4
+ "note": "test-project/development ortamında gerçek uçta doğrulandı (2026-09-11): publish:true ile kayıt 'publishing' döndü ve 'published' oldu (publishedReference zf-test-project-development-...), microfunction_invoke {a:1,b:2} → ok:true output {total:3, environment:'development'}, aynı akışa bağlı microservice_execute → 200 {total:3}. Kurallar: inline kaynakta entrypoint GÖNDERİLMEZ (sabit function.handler); timeoutSeconds ve memoryMb non-nullable int'tir, eksik gönderilirse platform 422 döner; adım rule.microfunctionId YALNIZ published kaydı gösterebilir (WF-MF-001)."
5
+ },
6
+ "microfunction": {
7
+ "name": "toplam",
8
+ "description": "İki sayıyı toplar ve hangi ortamda koştuğunu döner.",
9
+ "language": "node",
10
+ "provider": "built-in",
11
+ "sourceKind": "inline",
12
+ "code": "exports.handler = async (args, context) => {\n return { total: (args.a ?? 0) + (args.b ?? 0), environment: context.environment }\n}\n",
13
+ "parameterDtoId": "<girdi DTO id: { a: number, b: number }>",
14
+ "parameterBehavior": "object",
15
+ "returnDtoId": "<çıktı DTO id: { total: number, environment: string }>",
16
+ "returnBehavior": "object",
17
+ "timeoutSeconds": 30,
18
+ "memoryMb": 256,
19
+ "publish": true
20
+ },
21
+ "workflowStep": {
22
+ "id": "<GUID-3>",
23
+ "name": "toplam",
24
+ "type": "microfunction",
25
+ "description": "Girdideki iki sayıyı microfunction'da toplar.",
26
+ "rule": {
27
+ "microfunctionId": "<yukarıdaki kaydın id'si; status published olmalı>",
28
+ "expression": "({ a: input.a, b: input.b })"
29
+ },
30
+ "branches": [
31
+ {
32
+ "id": "<GUID-4>",
33
+ "name": "success",
34
+ "type": "success",
35
+ "nextStepId": "<response adımının id'si>"
36
+ },
37
+ {
38
+ "id": "<GUID-5>",
39
+ "name": "fail",
40
+ "type": "fail",
41
+ "nextStepId": null
42
+ }
43
+ ]
44
+ },
45
+ "responseStep": {
46
+ "id": "<GUID-6>",
47
+ "name": "Respond",
48
+ "type": "response",
49
+ "description": "Toplamı çağırana döner.",
50
+ "rule": {
51
+ "dataTransferObjectId": "<çıktı DTO id>",
52
+ "expression": "({ total: Steps.toplam.data.total })"
53
+ },
54
+ "branches": []
55
+ }
56
+ }
@@ -25,7 +25,7 @@
25
25
  "page_container (container, isWrapper:true — tema değişkenleri ve ortalanmış sütun BURADA)",
26
26
  "├─ t_title (text, h1)",
27
27
  "├─ t_desc (text, p)",
28
- "├─ q_form (form, isWrapper:false, hideBorders+hidePadding)",
28
+ "├─ q_form (form, isWrapper:false, style: border none + padding 0)",
29
29
  "│ └─ q_list (repeater, isWrapper:false, rows:[], autoRowSize:true, verticalSpacing:14)",
30
30
  "│ └─ q_card (container, isWrapper:false) ← ŞABLONUN KÖKÜ",
31
31
  "│ ├─ q_head (text) y:0 content: {{ item.no + '. ' + item.text + (item.required ? ' *' : '') }}",
@@ -13,7 +13,7 @@
13
13
  "rawRedirect": "redirect isRaw:true iken path ÇOK SATIRLI JS bloğudur ve son ifadenin değeri hedef adrestir. 'Korunan sayfaya gitmek isterken login'e düşen kullanıcıyı geri gönderme' deseni budur; yoksa sabit bir sayfaya gidilir (pageId + path).",
14
14
  "objectProp": "Prop değeri nesne olabilir: icon → { type:'static', ref:{ name:'progress_activity' } }.",
15
15
  "pageIsPublic": "Giriş sayfasına middlewareIds BAĞLANMAZ (guard'lı olsaydı kullanıcı giriş yapamazdı). Buna karşılık diğer sayfalar ve korunan uçlar middlewareIds ile bağlanır.",
16
- "notificationIsHidden": "notification'ın şemasında isHidden 2026-08-26'dan beri VARDIR: props.isHidden'ı doğrudan bildirime yazabilirsin. Bu örnekteki box_message sarmalayıcısı o dönemin kalıntısıdır (crm koşumu B-10) ve tek bir bildirim için ARTIK GEREKMEZ — burada duruyor çünkü şeridi bir kaba sarıp birden çok parçayı birlikte gösterip gizliyor. appearance.isHidden ise hiçbir zaman çözüm DEĞİLDİR — runtime onu hiç okumaz.",
16
+ "notificationIsHidden": "notification'ın şemasında isHidden 2026-08-26'dan beri VARDIR: props.isHidden'ı doğrudan bildirime yazabilirsin. Bu örnekteki box_message sarmalayıcısı o dönemin kalıntısıdır (crm koşumu B-10) ve tek bir bildirim için ARTIK GEREKMEZ — burada duruyor çünkü şeridi bir kaba sarıp birden çok parçayı birlikte gösterip gizliyor. appearance'ta ise isHidden alanı YOKTUR (2026-09-15'te kaldırıldı) — görünürlük yalnız props.isHidden'dır.",
17
17
  "validationShape": "Validation gövdesi DÖRT alan taşır: { type, message, value, isActive }. 'name' anahtarı JsonIgnore'dur — {name, value, message} yazarsan tip enum varsayılanına düşer ve alan sessizce REGEX doğrulamasına döner. Tip adları TİRELİDİR (max-length / min-length / max-value / min-value); 'maxLength' backend'e yazılır ama runtime doğrulayıcı onu tanımaz ve doğrulama hiç koşmaz. Gateway düzeltebildiğini düzeltir, kalanını PAGE-016 ile reddeder."
18
18
  },
19
19
  "pageCreateArgs": {
@@ -64,7 +64,7 @@
64
64
  "parent": "auth_layout",
65
65
  "component": {
66
66
  "elementId": "sign_in_form", "kind": "component", "type": "form", "name": "sign_in_form",
67
- "props": { "title": { "type": "static", "data": "Giriş" }, "hideBorders": { "type": "static", "data": true } },
67
+ "props": { "title": { "type": "static", "data": "Giriş" }, "style": { "type": "static", "data": { "border": { "type": "static", "data": "none" } } } },
68
68
  "states": { "isSticky": false, "isDroppable": true, "isScrollable": false, "isWrapper": true },
69
69
  "blocks": [{ "breakpoint": 1024, "size": { "h": 24, "w": 12 }, "position": { "x": 0, "y": 4 } }],
70
70
  "children": [], "events": [], "validations": [], "stateBindings": []
@@ -96,7 +96,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
96
96
  |---|---|
97
97
  | `isDroppable` | İçine komponent alır (container/form/repeater true) |
98
98
  | `isScrollable` | İçeriği kendi alanında kaydırır (sabit yükseklikte `overflow-y: auto`) |
99
- | `isWrapper` | Sayfanın/layout'un/widget'ın ANA kabı — salt düzen elemanı: `height:100%` + **`min-height:80vh`**, kap başlığı ÇİZİLMEZ. **Yalnız sayfa boyu kaplaması gereken tek kap için kullan:** iç içe kutuya verirsen 720px'lik pencerede o kutu 36px içerikle bile 576px çizilir (ölçüldü, `platform_guide('page')` Kural 1f). Çerçeve/başlık gizlemek için wrapper değil `style` / `hideBorders` kullan. Kurulmuş bir düğümün bayrağını `resize_component` micro-op'unun `states` alanı değiştirir — düğümü yeniden kurmak gerekmez |
99
+ | `isWrapper` | Sayfanın/layout'un/widget'ın ANA kabı — salt düzen elemanı: `height:100%` + **`min-height:80vh`**, kap başlığı ÇİZİLMEZ. **Yalnız sayfa boyu kaplaması gereken tek kap için kullan:** iç içe kutuya verirsen 720px'lik pencerede o kutu 36px içerikle bile 576px çizilir (ölçüldü, `platform_guide('page')` Kural 1f). Çerçeve/başlık gizlemek için wrapper değil `style` (`border:'none'`) kullan. Kurulmuş bir düğümün bayrağını `resize_component` micro-op'unun `states` alanı değiştirir — düğümü yeniden kurmak gerekmez |
100
100
  | `isSticky` | Kaydırmada yerinde kalır (header/sidebar; z-index'i otomatik) |
101
101
  | `isFloating` | Düğümü akıştan çıkarıp yüzdürür (katman davranışı; nadir) |
102
102
  | `isQuickStack` | Studio'nun Quick Stack iskelet hücresi işareti — editörde kesikli çerçeve çizer, **runtime'da hiçbir etkisi yoktur**. MCP'den iskelet kurarken yazmak serbesttir ama gerekmez |
@@ -117,7 +117,7 @@ Her şema düğümünde iki sözlük vardır; ikisi de ortak render katmanında
117
117
  | `widthType` | `cover` · `contain` · `auto` · `fixed` · `fill` | İlk ikisi görsel/medya sığdırma. Kalanı **flex (ve eski cssgrid) kabın çocuğunun genişlik modu** (2026-09-01): `auto` içerik kadar, `fixed` `widthString`'den, `fill` kabın tüm genişliği (satırda `flex:1 1 100%`, sütunda `align-self:stretch`). **Yazılmazsa eski 12 kolon `blocks.size.w` payı geçerlidir** — öncelik: `fill` > `fixed` > `auto` > span |
118
118
  | `widthString` / `heightString` | CSS uzunluğu | Sabit ölçü; **yalnız ilgili `*Type: 'fixed'` iken okunur** (aksi PAGE-017 uyarısı). Kabul: çıplak sayı (px sayılır) · `px % vw vh vmin vmax rem em ch` birimli değer · `calc(...)`. Negatif/`pt`/`fr`/`auto` GEÇMEZ — platform kaydeder ama panel ve runtime sessizce düşürür (PAGE-018). Eski `widthPx` alanı 2026-09-01'de kaldırıldı |
119
119
  | `marginType` / `marginValue` | `normal` · `none` · `custom` + px | Dış boşluk: **yazılmazsa `normal` sayılır** = her kenara 4px (`spacing.ts` `CANVAS_NORMAL_MARGIN`); custom=`marginValue`. Bileşenleri dip dibe dizmek için `none` ŞART — `gap:'none'` olsa bile normal margin komşular arasında 8px bırakır (sıkı dizilim tarifi: `platform_guide('page')` Kural 1f-2) |
120
- | `paddingType` / `paddingValue` | `normal` · `none` · `custom` + px | Droppable kapların iç boşluğu: normal=10px |
120
+ | `paddingType` / `paddingValue` | `normal` · `none` · `custom` + px | İç boşluk: normal=10px. **Yalnız droppable kaplarda ve tipli stil modelindeki yapraklarda (box · text · container · statcard · icon · form) çizilir**; `slot`, kabuk kökleri (`layout`/`layout-shell`) ve `modal`/`drawer`/`auth-layout` hiç almaz — platform 200 döner, ekran değişmez (PAGE-029). Değerler piksel SAYISIDIR: `24`, `"24px"` değil (PAGE-030) |
121
121
  | `alignType` | `left` · `center` · `right` | İçerik hizalama (destekleyen komponentlerde) |
122
122
  | `aspectRatio` | sayı | En-boy oranı kilidi (medya komponentleri) |
123
123
 
@@ -163,6 +163,18 @@ düz URL string'i değil **dosya nesnesi** ister: `{ absolute_path, display_name
163
163
  `{ absolute_path: '<url>', display_name: '<ad>' }` biçimine çevir. Komponentin kendi sözleşmesi
164
164
  için `component_get` `propNotes`.
165
165
 
166
+ ## Tasarımcı toolbar'ı: `designer.toolbar` (studio'ya özel, kataloğa girmez)
167
+
168
+ Studio kanvasında seçili komponentin yanında çıkan bant, komponentin **kendi 2–4 prop'unu**
169
+ tek tıkla değiştirir (`textinput`: Disabled · Readonly · Size). Liste komponentin kendi
170
+ `schema.ts`'inde `designer.toolbar` olarak durur; kontrol prop tipinden türer (switch → toggle,
171
+ ≤ 4 seçenekli radio/dropdown/switcher → kısa metin grubu, fazlası açılır liste, number → sayı
172
+ alanı). Kural üretimde denetlenir: en fazla 4 ad, ad şemada var, tip izinli, `designer.hide`
173
+ ve `layout` grubundaki adlar giremez. **MCP tarafında karşılığı yoktur:** `designer` bloğu
174
+ `components.json`'a ve `component_get` yanıtına girmez; toolbar'daki her prop zaten sıradan
175
+ bir prop'tur, `set_prop` ile aynı şekilde yazılır. Bu bilgi yalnız "studio'da neden o
176
+ düğmeler var" sorusuna cevaptır.
177
+
166
178
  ## Komponent seçimi
167
179
 
168
180
  "Hangi işe hangi komponent" tablosu ve `table` / `datatable` karşılaştırması:
@@ -433,10 +433,12 @@ boş bırakmak geçerli bir tercihtir** (ENT-002 warn'ı bunu hatırlatır, zoru
433
433
  - İki entity'yi önce ilişkisiz oluşturup ilişki alanını sonra `entity_patch` ile eklemek en
434
434
  güvenli sıra (id'ler ancak oluşunca belli olur).
435
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` değil boş nesne `{}` döner** (ölçüm: İK koşusu D-8, 2026-09-01). JS'te
437
- `{}` truthy'dir: `if (row.manager_id)` "yöneticisi var" der. Varlık kontrolünü
438
- `row.manager_id?.id` üzerinden yap. (Backend'in `null` döndürmesi P0-6 olarak planlı; o gelene
439
- dek bu şekil geçerlidir.)
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.
440
442
 
441
443
  ## Index'ler
442
444
 
@@ -578,16 +580,55 @@ bilmezsen "kayıt bozulmuş" sanırsın.
578
580
  - `row_*` tool'ları `entityId` olarak slug kabul eder (`employees`, çakışmada
579
581
  `BuiltIn/employees`); gateway id'ye çevirir ve yanıtta `gatewayResolved` ile bildirir.
580
582
  - `row_create/row_update` anahtarları **field slug'larıdır** (name değil): `{"code": "IK"}`.
581
- - **Toplu yazımın (`rows[]`) id döndürmesine GÜVENME.** 2026-09-05'te uç `data: [{ "<pk slug>":
582
- değer }, …]` döndürüyordu; 2026-09-07 ölçümünde iki ayrı entity'de **boş gövde** döndü (gateway
583
- onu `{success: true}` diye gösterir) — satırlar yazıldı ama anahtarlar gelmedi. Fark `B-63`
584
- olarak takipte. Yazdığın satırların anahtarları lazımsa yanıta bel bağlama, `row_list` çek ve
585
- bu turu bütçene ekle. Daha iyisi: türetilmiş satırı bu id'lerle kurma — §"Toplu ve türetilmiş
586
- satır"daki SQL yolunda id hiç dolaşmaz.
587
- - Yazdıktan sonra `row_list` ile geri oku; ilişki alanları `{ id, name }` nesnesi olarak döner.
588
- - **Boş to-one ilişki `null` döner**, boş nesne değil — `if (row.manager_id)` kontrolü doğru
589
- çalışır (İK koşusu D-8; 2026-09-03 öncesinde `{}` dönüyordu ve JS'te truthy'ydi). Boş
590
- many-to-many alan `[]` döner.
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.
591
632
  - **`isAuditTrailActive: true` ile KURULAN entity'ye satır yazılamaz.** `sys_*` kolonları create
592
633
  yolunda oluşmaz, yalnız ilk **etkin** `entity_update`'inde eklenir; o ana kadar **her**
593
634
  `row_create` şu hatayla 500 verir:
@@ -621,9 +662,24 @@ bilmezsen "kayıt bozulmuş" sanırsın.
621
662
  > da yan kayıt oluşmaz; `true` + `basic/detailed` iken "anonim" dediğin satırın yanında IP
622
663
  > durur. Bu, ürünün gizlilik vaadini sessizce bozan tek ayardır.
623
664
 
624
- - **`defaultValueType: "static"` sessizce `"none"`e düşer** — DB varsayılanı kurulmaz. Bu yüzden
625
- her `BOOL`/sayısal alanı **her** `row_create`'te ve her workflow write adımında **açıkça** yaz;
626
- "varsayılanı platform koyar" varsayımı `null` satır üretir.
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`).
627
683
  - **`row_list.filters` desteklenir.** Tekil filtre `{ field, operator, value, filterType:'single' }`,
628
684
  grup filtre `{ filterType:'group', conditionType, children:[...] }` biçimindedir. Tekil filtrede
629
685
  `field`, grupta `children` zorunludur; `in`/`notin` operatörleri liste değer ister. Eksik/yanlış
@@ -746,6 +802,254 @@ hiç doğmaz, çünkü automation yoktur.
746
802
  Toplu **güncelleme** de aynı yola aittir: var olan satırlardan hesaplanan bir damga/toplam için
747
803
  `UPDATE … FROM` yaz, satırları `row_update`'in gövdesinde taşıma.
748
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
+
749
1053
  ## Büyük entity'lerde
750
1054
 
751
1055
  `entity_get view:'outline'` alan listesi; `view:'part', part:'fields[slug=email]'` tek alan;
@@ -35,6 +35,7 @@ tetikleyen satır `row` / `previousRow` / `user`.
35
35
  | `Now(tz)` | Verilen saat diliminde şimdi, **`"yyyy-MM-ddTHH:mm:ss"` METİN olarak**. **JS motoru UTC'ye sabitlidir** (`opt.TimeZone = Utc`), yani `new Date()` her zaman UTC'dir — yerel gün/saat mantığı için bunu kullanın, offset'i koda gömmeyin | `Now('Europe/Istanbul')` → `"2026-08-18T19:11:10"` |
36
36
  | `Today(tz)` | O saat diliminde bugün, `"yyyy-MM-dd"` metni — gün sınırı filtreleri için en kısa yol | `Today('Europe/Istanbul') + 'T00:00:00'` |
37
37
  | `ToTimeZone(v, tz)` | Bir değeri (Date, DateTimeOffset ya da ISO metin) o saat dilimine çevirir, aynı metin biçiminde | `ToTimeZone(Steps.randevu.data.baslangic, 'Europe/Istanbul')` |
38
+ | `Functions.<ad>(…)` | **YOK — sunucuda kullanıcı fonksiyonu yüklenmez** (2026-09-10). Gateway `WF-030` ile istek gitmeden bloklar; zorlanırsa Jint `Functions is not defined` fırlatır. Sunucuda paylaşılan hesap **`source:'workflow'` fonksiyondur** ve çıplak adla çağrılır (`sum(1, 2)`, `function_create source:'workflow'`) |
38
39
 
39
40
  > **Bu üçü METİN döner, Date DÖNMEZ** — bilerek. `UtcNow()` bir JS `Date` döndürür ve üzerinde
40
41
  > `getTime()`/`getUTCFullYear()` çalışır; ama Jint'in `DateTimeOffset` → `Date` çevriminde **saat
@@ -102,9 +103,11 @@ dalından devam eder.
102
103
  | `event` | `{name, data, source: {id, props}}` |
103
104
  | `cookie` | `cookie.get(name)` / `cookie.set(name, value, {expiresInHours, path})` / `cookie.remove(name)` — tarayıcı cookie'si |
104
105
  | `setComponentProperty(name, key, value)` / `getComponentProperty(name, key)` | fonksiyon alternatifi |
106
+ | `resetForm(name)` | Formu sıfırlar: alanlar başlangıç değerine döner, form "gönderilmemiş" olur, doğrulama mesajları silinir. Kayıt sonrası "yeni kayıt" için alanları tek tek `null`'a çekme — gönderilmiş formda her biri "zorunlu alan" mesajı çıkarır. Ad ya da komponent değişkeni alır: `resetForm('risk_form')` / `resetForm(risk_form)`. **Yalnız UI flow kodunda** |
105
107
  | `LocalStorage` · `Widget` · `params` | yerel veri · widget bağlamı · olay parametreleri |
106
108
  | `t(key)` | Çeviri anahtarını çözer: aktif dil → projenin varsayılan dili → anahtarın kendisi. Boş string de "eksik" sayılır. Sözlük `machine.context.state.translations`'tan gelir; dil `language-selector` ile değişince bir sonraki çağrı yeni dili görür. **Yalnız UI flow kodunda vardır** — backend ifadelerinde karşılığı `Loc()`'tur (`platform_guide('workflow')`) | `t('home.welcome')` |
107
109
  | `log(...)` | console (proxy'leri temizler) |
110
+ | `Functions` | **Kullanıcı fonksiyonları** (`function_create`): `Functions.toplamHesapla(fiyat, adet)` — uygulama kapsamlılar her sayfada, sayfa kapsamlılar yalnız kendi sayfasında. **Yalnız tarayıcı**; sunucuda bu ad alanı yoktur (2026-09-10, `WF-030`) |
108
111
 
109
112
  > **`.map is not a function` bir dizi sözleşmesi sorunu DEĞİL, düşmüş bir adımdır.**
110
113
  > Ölçüldü 2026-09-05 (`f09-repro/probe`, yerel kurulum): başarılı bir microservice adımında
@@ -128,6 +131,11 @@ prop `type: 'code'` olur, **ifade `ref` alanına `{{ }}` içinde** yazılır, `d
128
131
  değerini taşır:
129
132
  `"text": { "type": "code", "data": "Button text", "ref": "{{ state.page.MyTest.isActive ? 'Test' : 'FalseTest' }}" }`
130
133
 
134
+ > **Prop'ta parantezsiz ifade METİNDİR (PAGE-032).** Akış adımındaki `value.type: 'code'`
135
+ > ifadeyi doğrudan koşturur; prop'taki `code` ise bir şablondur ve yalnız `{{ }}` içini
136
+ > çalıştırır. `"ref": "new Date().getFullYear()"` prop'a harfi harfine o metni yazar, hata
137
+ > üretmez — `page_schema_patch` bu yüzden parantezsiz prop ifadesini reddeder.
138
+
131
139
  ## 3. Adım argümanlarında `value.type` çözümlemesi
132
140
 
133
141
  `microservice`/`setter` adımlarının `parameters[]`/`iteratees[]` değerlerinde:
@@ -179,3 +187,4 @@ Karıştırma: `roles_csv` **UI/filtre** tarafının yoludur; backend middleware
179
187
  | `system.` / `file.` / `crypto.` / `convert.` | ✓ | — |
180
188
  | `cookie` | okunur sözlük (`cookie.X`) | `get/set/remove` fonksiyonları |
181
189
  | `await` | — (senkron ifade) | ✓ (`code` adımı) |
190
+ | `Functions.<ad>()` (kullanıcı fonksiyonu) | — (sunucuda yok, `WF-030` block) | ✓ uygulama + o sayfanın sayfa fonksiyonları |