@appaflytech/zeyrek-ai-mcp 0.0.1

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 (88) hide show
  1. package/.env.example +46 -0
  2. package/README.md +260 -0
  3. package/dist/config.js +128 -0
  4. package/dist/index.js +88 -0
  5. package/dist/knowledge/componentCatalog.js +438 -0
  6. package/dist/knowledge/componentEvents.js +164 -0
  7. package/dist/knowledge/coverage.js +60 -0
  8. package/dist/knowledge/normalize.js +1309 -0
  9. package/dist/knowledge/outlines.js +167 -0
  10. package/dist/knowledge/policy.js +44 -0
  11. package/dist/knowledge/reservedTables.js +75 -0
  12. package/dist/knowledge/rules.js +1725 -0
  13. package/dist/observability/trace.js +337 -0
  14. package/dist/platform/errors.js +132 -0
  15. package/dist/platform/http.js +169 -0
  16. package/dist/platform/responses.js +94 -0
  17. package/dist/platform/token.js +79 -0
  18. package/dist/resources/examples/entity-pair-m2m.json +105 -0
  19. package/dist/resources/examples/flow-list-to-form.json +65 -0
  20. package/dist/resources/examples/flow-load-table.json +178 -0
  21. package/dist/resources/examples/microservice-table-list.json +49 -0
  22. package/dist/resources/examples/middleware-authenticate.json +194 -0
  23. package/dist/resources/examples/page-detail-profile.json +95 -0
  24. package/dist/resources/examples/page-dynamic-form.json +221 -0
  25. package/dist/resources/examples/page-form-upsert.json +279 -0
  26. package/dist/resources/examples/page-html-embed.json +106 -0
  27. package/dist/resources/examples/page-sign-in.json +194 -0
  28. package/dist/resources/examples/workflow-basic.json +94 -0
  29. package/dist/resources/guides/component.md +108 -0
  30. package/dist/resources/guides/entity.md +348 -0
  31. package/dist/resources/guides/expressions.md +139 -0
  32. package/dist/resources/guides/getting-started.md +109 -0
  33. package/dist/resources/guides/html.md +106 -0
  34. package/dist/resources/guides/microservice.md +313 -0
  35. package/dist/resources/guides/middleware.md +271 -0
  36. package/dist/resources/guides/modify-existing.md +35 -0
  37. package/dist/resources/guides/page.md +1574 -0
  38. package/dist/resources/guides/view.md +89 -0
  39. package/dist/resources/guides/workflow.md +359 -0
  40. package/dist/resources/knowledge.js +44 -0
  41. package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
  42. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
  43. package/dist/resources/schemas/CreateEntityCommand.json +387 -0
  44. package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
  45. package/dist/resources/schemas/CreateQueryCommand.json +43 -0
  46. package/dist/resources/schemas/CreateViewCommand.json +158 -0
  47. package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
  48. package/dist/resources/schemas/Filter.json +124 -0
  49. package/dist/resources/schemas/GetRowsQuery.json +122 -0
  50. package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
  51. package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
  52. package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
  53. package/dist/resources/schemas/StepDto.json +900 -0
  54. package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
  55. package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
  56. package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
  57. package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
  58. package/dist/resources/schemas/UpdateViewCommand.json +153 -0
  59. package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
  60. package/dist/resources/schemas.js +40 -0
  61. package/dist/server.js +63 -0
  62. package/dist/tools/access.js +27 -0
  63. package/dist/tools/applications.js +59 -0
  64. package/dist/tools/automations.js +103 -0
  65. package/dist/tools/common.js +18 -0
  66. package/dist/tools/components.js +280 -0
  67. package/dist/tools/datasources.js +82 -0
  68. package/dist/tools/dependencies.js +17 -0
  69. package/dist/tools/deployments.js +17 -0
  70. package/dist/tools/dtos.js +54 -0
  71. package/dist/tools/entities.js +166 -0
  72. package/dist/tools/files.js +138 -0
  73. package/dist/tools/flows.js +226 -0
  74. package/dist/tools/folders.js +51 -0
  75. package/dist/tools/layouts.js +105 -0
  76. package/dist/tools/microservices.js +198 -0
  77. package/dist/tools/organizations.js +65 -0
  78. package/dist/tools/pages.js +597 -0
  79. package/dist/tools/platformInfo.js +44 -0
  80. package/dist/tools/projects.js +152 -0
  81. package/dist/tools/queries.js +48 -0
  82. package/dist/tools/rows.js +115 -0
  83. package/dist/tools/screens.js +97 -0
  84. package/dist/tools/templates.js +65 -0
  85. package/dist/tools/variables.js +94 -0
  86. package/dist/tools/views.js +58 -0
  87. package/dist/tools/workflows.js +196 -0
  88. package/package.json +49 -0
@@ -0,0 +1,89 @@
1
+ # View Rehberi (tam metin arama)
2
+
3
+ View, **serbest metin araması** içindir: seçtiğin alanlar bir analizörden geçirilip `slug` adlı
4
+ projeksiyon tablosuna indekslenir. **Raporlama/agregasyon aracı değildir** — özet/sayım için
5
+ `query_create` (raw SQL) + query kaynaklı microservice kullan.
6
+
7
+ Gövde şemaları: `ngapps://schemas/CreateViewCommand` · `ngapps://schemas/UpdateViewCommand`.
8
+
9
+ ## Kuruluş sırası
10
+
11
+ ```
12
+ 1. datasource_list (datasourceId — create'te zorunlu, update'te alan YOK)
13
+ 2. entity_get (indekslenecek alanların fieldId'leri)
14
+ 3. view_create
15
+ 4. microservice_create (source: 'view') ← tüketim yolu
16
+ 5. view_rebuild (toplu veri değişikliğinden sonra)
17
+ ```
18
+
19
+ ## Çalışan gövde
20
+
21
+ ```jsonc
22
+ {
23
+ "name": "Talep Arama", "slug": "ticket_search",
24
+ "datasourceId": "<datasource id>", "providerType": "postgres",
25
+ "analyzer": { "language": "turkish", "tokenizer": "standard",
26
+ "charFilters": [], "tokenFilters": ["lowercase", "asciifolding"] },
27
+ "entities": [{ "entityId": "<tickets entity id>", "alias": "tickets" }],
28
+ "fields": [
29
+ { "viewEntityAlias": "tickets", "fieldId": "<title field id>", "alias": "title", "weight": 2 },
30
+ { "viewEntityAlias": "tickets", "fieldId": "<description field id>", "alias": "body", "weight": 1 }
31
+ ]
32
+ }
33
+ ```
34
+
35
+ - `analyzer` verilmezse platform `ViewAnalyzer.Default()` kullanır — Türkçe içerikte
36
+ `language: 'turkish'` + `tokenFilters: ['lowercase','asciifolding']` açıkça verilmelidir, yoksa
37
+ "Talep" ile "talep" ya da "yazilim" ile "yazılım" eşleşmez.
38
+ - `fields[].weight` sıralama ağırlığıdır (`double`): başlık 2, gövde 1 gibi.
39
+ - `fields[].viewEntityAlias`, `entities[].alias` ile birebir eşleşmeli. Eşleşmezse alan **sessizce
40
+ ilk tabloya** bağlanır (`ViewSourceMapper.Map` → `tablesByAlias.GetValueOrDefault(...) ?? tables.FirstOrDefault()`).
41
+ - `view_get` alanları `viewEntityId` ile döndürür, yazma tarafı `viewEntityAlias` ister; gateway
42
+ çeviriyi yapar, çeviremezse isteği reddeder.
43
+
44
+ ## Enum sözlüğü (hepsi küçük harf)
45
+
46
+ | Alan | Değerler |
47
+ |---|---|
48
+ | `providerType` | `postgres` |
49
+ | `analyzer.language` | `simple` · `turkish` · `english` |
50
+ | `analyzer.tokenizer` | `standard` · `whitespace` · `edge-ngram` |
51
+ | `analyzer.charFilters[]` | `html-strip` |
52
+ | `analyzer.tokenFilters[]` | `lowercase` · `stopwords` · `stemmer` · `asciifolding` · `synonym` |
53
+
54
+ `edge-ngram` "yazarken arama" (typeahead) içindir ve indeksi büyütür; tam kelime araması için
55
+ `standard` yeterlidir.
56
+
57
+ ## Çoklu entity JOIN DEĞİLDİR
58
+
59
+ `entities` birden çok tablo alır, ama aralarında **ilişki tanımı yoktur** — `ViewSourceMapper.Map`
60
+ yalnız tabloları ve alanları eşler. Her tablo ayrı ayrı indekslenir; eşleşme hangi tablodan
61
+ geldiyse dönen kayıt **o tablonun satırıdır**.
62
+
63
+ Yani *"talebi yorum içeriğinden bul"* bu yolla kurulamaz: yorumdan eşleşme gelirse elinde yorum
64
+ satırı olur, talep değil.
65
+
66
+ **Doğru desen — denormalize arama alanı:**
67
+
68
+ ```
69
+ 1. tickets'a bir arama alanı ekle (ör. search_blob, TEXT)
70
+ 2. ticket_comments üzerinde create/update automation'ı: yorum metnini ilgili talebin
71
+ search_blob'una ekle (row.ticket_id ile talebi bul, mevcut blob + yeni yorum)
72
+ 3. view tek entity (tickets) üzerinde title + description + search_blob alanlarını indeksler
73
+ ```
74
+
75
+ Bu kurulumda yalnız yorumda geçen bir kelime **talebi** döndürür. Automation tarifi:
76
+ `platform_guide('workflow')` §"Automation gerçekleri".
77
+
78
+ ## Tüketim: `source: 'view'` microservice'i
79
+
80
+ View'i okumanın tek yolu view kaynaklı bir microservice'tir ve o uç **zorunlu `searchText`**
81
+ parametresi ister (+ `limit`/`offset`). Ayrıntı: `platform_guide('microservice')`.
82
+
83
+ ## Bakım
84
+
85
+ - `view_rebuild` projeksiyonu sıfırdan kurar; toplu satır yüklemesinden ya da alan listesi
86
+ değişikliğinden sonra çağır.
87
+ - View'in `slug`'ı projeksiyon tablosunun/indekslerinin adıdır — slug değiştirmek onları yeniden
88
+ adlandırır; elle oluşturulmuş bir tabloyla çakışmasın diye entity slug'larıyla aynı isim uzayında
89
+ düşün (`platform_guide('entity')` §"Slug seçimi").
@@ -0,0 +1,359 @@
1
+ # Workflow Rehberi
2
+
3
+ Buradaki kurallar gerçek projelerde (qm, hr) **deneyerek doğrulandı**.
4
+
5
+ ## Karar kuralları
6
+
7
+ 1. **DTO önce (yalnız workflow tipinde).** `type='workflow'` için girdi/çıktı sözleşmesini
8
+ DTO olarak kur: `parameterDtoId` + `parameterBehavior`, `returnDtoId` + `returnBehavior`
9
+ (DtoId verilen her yerde Behavior da zorunlu). Platform her entity için `isDefault: true`
10
+ DTO üretir — önce `dto_list` ile ara, çoğu zaman yenisini yazmak gerekmez.
11
+ 2. **Veri adımı seçimi: önce table.** Query step yalnız ham SQL şartsa.
12
+ 3. **Step/branch id'leri gerçek GUID** olmalı (`"s-start"` gibi id'ler `System.Guid` çevrim
13
+ hatası verir) ve **veritabanında satır oldukları için global benzersiz** — başka akıştaki
14
+ GUID'i tekrar kullanmak `RowAlreadyExists` üretir; her akışa taze GUID bloğu ver.
15
+ 4. **Kaydetmeden önce `workflow_preview`.** Test girdisi **`previewPayload`** anahtarıyla
16
+ verilir (`parameters`/`input` çalışmaz, ifadeler `undefined` üretir); değişkenler
17
+ `previewVariables`.
18
+ - **`previewPayload` bir STRING olmalı ve tek kanonik biçimi `"({ … })"`'dir.** Alan
19
+ JavaScript **kaynak metni** bekliyor (`PreviewWorkflowCommand` gövdeyi `jsEngine.Eval` ile
20
+ çalıştırır) ve **ifadenin sonucu bir NESNE olmak zorundadır**:
21
+
22
+ ```jsonc
23
+ "previewPayload": "({ email: 'a@b.c', password: '1234' })" // ✔ tek doğru biçim
24
+ "previewPayload": { "email": "a@b.c" } // ✘ 500: Unexpected end of input
25
+ "previewPayload": "JSON.stringify({ email: 'a@b.c' })" // ✘ SESSİZ: sonuç string, input {} kalır
26
+ "previewPayload": "{ email: 'a@b.c' }" // ✘ SESSİZ: JS bunu BLOK olarak ayrıştırır
27
+ ```
28
+
29
+ Nesne göndermenin hatası gürültülüdür (`Unexpected end of input (<anonymous>:1:8x)`, sabit
30
+ sütun numarası, step'lerden bağımsız — step ifadelerini suçlamayın). **Sessiz varyant daha
31
+ tehlikelidir:** ifade nesne üretmezse preview `input: {}` ile koşar ve suçu adıma atan bir
32
+ mesaj alırsın (`Please provide the 'members.email' parameter.`) — asıl sorun girdinin hiç
33
+ bağlanmamış olmasıdır. Yanıttaki `"input"` alanının dolu olduğunu her koşumda kontrol et.
34
+ Gateway üç hatalı biçimi de **WF-016** ile istek gitmeden reddeder.
35
+ Automation önizlemesinde aynı biçimle `row`/`previousRow`/`operation`, middleware'de
36
+ `cookie`/`header`/`queryString`/`body`/`triggerSource` anahtarları verilir.
37
+ - **Preview YAZMAZ — transaction içinde koşup rollback eder.** Adımlar gerçek id'ler döndürür
38
+ ama koşum bitince o satırlar tabloda **yoktur** (ölçüm 2026-08-17: `users` + `employees` +
39
+ `leave_balances` yazan bir akış preview'da uçtan uca geçti, sonra `row_list` üçünde de
40
+ bulamadı). İki sonucu var: (1) yazma adımlı akışları çekinmeden önizleyebilirsin;
41
+ (2) **döndürdüğü id'ler kalıcı değildir** — o id ile `row_update` denersen
42
+ `Sequence contains no elements` alırsın ve hata seni entity'yi suçlamaya iter.
43
+ Kalıcı satır istiyorsan akışı bağlı ucundan `microservice_execute` ile çağır.
44
+
45
+ ## Step'ler nasıl bağlanır
46
+
47
+ Her step'in `branches[]` dizisi vardır; `nextStepId` sonraki step'in id'sini gösterir.
48
+ Bağlanmamış step çalışmaz. `start` tek branch taşır; normal step'ler `success` + `fail`;
49
+ `condition` koşul branch'leri + `else`; terminal step'ler (`response`, `convert`, `revert`,
50
+ `end`) **branch taşımaz**.
51
+
52
+ ## Condition step kuralları
53
+
54
+ Koşullar `rule.items[]` dizisindedir ve **sırayla** değerlendirilir; ilk `true` olan dala
55
+ gidilir, hiçbiri tutmazsa `else` dalı yürür:
56
+
57
+ ```jsonc
58
+ { "id": "<GUID-C>", "name": "status_check", "type": "condition",
59
+ "rule": { "stepType": "condition", "queryStepSourceType": "library", "isExpression": false,
60
+ "items": [
61
+ { "expression": "load_row.data.length === 0", "branchId": "<GUID-B1>" },
62
+ { "expression": "load_row.data[0].status !== 'Onaylandı'", "branchId": "<GUID-B2>" }
63
+ ] },
64
+ "branches": [
65
+ { "id": "<GUID-B1>", "name": "<GUID-B1>", "type": "success", "nextStepId": "<404 step>" },
66
+ { "id": "<GUID-B2>", "name": "<GUID-B2>", "type": "success", "nextStepId": "<422 step>" },
67
+ { "id": "<GUID-B3>", "name": "else", "type": "success", "nextStepId": "<devam step>" }
68
+ ] }
69
+ ```
70
+
71
+ - Her `items[].branchId` bir branch'in **id'sidir**; runtime bu eşleşmeyle dallanır.
72
+ - **Koşul branch'inin `name`'i, kendi id'sinin GUID string'i OLMALIDIR** (`else` dalı sabit
73
+ `"else"` kalır). Studio kenarları `branch.name` ↔ `items[].branchId` eşleşmesiyle çizer;
74
+ `randevu_yok` gibi anlamlı bir ad verilirse akış **runtime'da kusursuz çalışır ama Studio'da
75
+ koşullar bağlanmamış görünür** — sessiz bozulma, execute testi yakalayamaz. Okunabilir
76
+ adlandırmayı step adında yap; koşulların anlamı ifadelerinden okunur.
77
+ **Gateway bunu otomatik düzeltir:** `items[].branchId`'nin gösterdiği branch'in adını id'siyle
78
+ eşitler ve `gatewayNormalized` ile bildirir; eşleşmeyen bir `branchId` kalırsa WF-014 isteği
79
+ durdurur (o durumda koşul hiçbir dala bağlı değildir).
80
+ - İfadeler önceki step çıktılarıyla çalışır (`<stepAdı>.data…`); ilişki alanı nesne dönebilir,
81
+ `x.doctor_id.id ? x.doctor_id.id : x.doctor_id` kalıbı iki biçimi de karşılar.
82
+
83
+ ## Üç workflow tipi
84
+
85
+ | type | Ne zaman | Tetiklenme | Özel kurallar |
86
+ |---|---|---|---|
87
+ | `workflow` | REST ucu gibi çağrılabilir iş akışı | HTTP (`/{app}/{slug}`) | `returnDtoId` zorunlu (WF-002) |
88
+ | `automation` | Entity satır olayına tepki | `entityId` + `operation` + `isQueue` | returnDtoId YASAK; aşağıdaki "Automation gerçekleri" |
89
+ | `middleware` | Microservice/sayfa **çalışmadan önce** koşan guard/setter (auth, `user`/`settings` değişkenleri) | Bağlandığı uç çağrılınca (`middlewareIds`) | returnDtoId gerekmez; `set-user`/`variable`/`setcookie`/`redirect` yalnız bu tipte. **Ayrı rehberi var: `platform_guide('middleware')`** |
90
+
91
+ ## Step tipi × workflow tipi izin matrisi
92
+
93
+ | Step | workflow | automation | middleware |
94
+ |------|:-:|:-:|:-:|
95
+ | start, condition, code, loop, table, query, http, workflow, email, file-upload | ✓ | ✓ | ✓ |
96
+ | variable (Set Variable), set-user, setcookie, redirect | – | – | ✓ |
97
+ | response | ✓ | – | ✓ |
98
+ | convert | ✓ | – | – |
99
+ | revert (Fail and Rollback) | – | ✓ | – |
100
+
101
+
102
+ ## Loop step — dizinin her öğesi için bir adım koştur
103
+
104
+ Tek uçta "başlık + N alt kayıt" yazmanın yolu budur (anket + sorular + seçenekler tek çağrıda;
105
+ ölçüldü 2026-08-19, `anket-admin/anket-kaydet`).
106
+
107
+ ```jsonc
108
+ { "id": "<GUID>", "name": "loopQuestions", "type": "loop",
109
+ "rule": {
110
+ "stepType": "table", // ÇOCUK adımın tipi (loop değil)
111
+ "arrayExpression": "input.questions || []", // JS ifadesi → dizi
112
+ "stepRule": { /* çocuğun kendi rule'u — burada table create */ }
113
+ },
114
+ "branches": [{ "id": "<GUID>", "name": "success", "nextStepId": "<sonraki>" }] }
115
+ ```
116
+
117
+ - **Çocuk kural `stepRule`'dur, `children` DEĞİL.** (Gateway **WF-018** ile yakalar: `stepRule` yoksa
118
+ ya da `arrayExpression` boşsa istek gitmez.) API DTO'sunda alan tekildir
119
+ (`StepRuleDto StepRule`); `children` gönderirsen `rule.Children` boş kalır ve loop
120
+ **`NullReferenceException`** ile patlar — hata mesajı `arrayExpression`'ı işaret ettiği için
121
+ seni ifadeyi suçlamaya iter, oysa sorun anahtar adıdır.
122
+ - **Geçerli öğe `loopItem`'dır.** Çocuğun `fields[].value` / `filters[].value` ifadelerinde
123
+ `loopItem.<alan>` yazılır; `input` ve önceki adımlar (`createSurvey.data.id`) da görünmeye
124
+ devam eder.
125
+ - **Çıktı `[{ item, result }]` dizisidir.** `result` çocuk adımın çıktısıdır; `table create`
126
+ için `{ id }`. Sonraki adımda `loopQuestions.data` ile okunur — **yazılan kayıtların id'sini
127
+ girdideki öğeyle eşleştirmenin yolu budur.**
128
+ - **İkinci seviye eşleme için `code` adımına gerek yok:** ikinci loop'un `arrayExpression`'ı
129
+ zaten JS'tir, eşlemeyi orada yap:
130
+ ```js
131
+ (loopQuestions.data || []).reduce(function(acc, q){
132
+ var qid = q.result && q.result.id;
133
+ (input.options || []).filter(function(o){ return o.qkey === (q.item && q.item.key); })
134
+ .forEach(function(o){ acc.push({ label: o.label, question_id: qid }); });
135
+ return acc; }, [])
136
+ ```
137
+ İstemci alt kayıtları **düz** gönderir ve satırları `key`/`qkey` ile eşler — iç içe dizi
138
+ göndermek gerekmez (iç içe loop denenmedi; `loopItem` iç loop'ta gölgeleneceği için önerilmez).
139
+
140
+ > **Table step'te `create`/`update` satır değerleri `fields[]`'tedir ve JS İFADESİDİR.**
141
+ > `{ "fieldId": "<alan id>", "source": "expression", "value": "input.title", "order": 0 }` —
142
+ > `PrepareParameters` her `fields[].value`'yu `EvaluateExpression` ile koşturur
143
+ > (`TableStep.cs:392`). `delete` işleminde `fields` boş kalır, iş `filters[]`'dedir.
144
+
145
+ > **GUID alanları HEX olmak zorunda.** Okunabilirlik için blok öneki kullanırken (`f1000001-…`)
146
+ > hex olmayan harf koyma: `g1000001-…` gönderdiğimde platform
147
+ > `The JSON value could not be converted to System.Guid` ile reddetti. Geçerli önek harfleri
148
+ > yalnız `a`–`f`'tir.
149
+
150
+ ## Table step kuralları
151
+
152
+ - `rule` içinde `datasourceId` + `entityId` zorunlu; `selectFields`/`filters` öğelerinde
153
+ **`fieldId` zorunlu** (yalnız `fieldSlug` yetmez → `EvaluateWorkflowStepFailedException`).
154
+ - **Table step JOIN kurabilir** ve alias/path disiplini microservice'inkiyle **birebir aynıdır**
155
+ (Studio aynı form bileşenlerini kullanır): `joins[].path` = `<kökEntityId>.<ilişkiAlanı…>`,
156
+ join'li `selectFields[].path` = kök id olmadan aynı zincir, kök alanlarda `path` yok.
157
+ Join'in çıktısı sonraki adımlarda **iç içe** gelir: `step.data[0].course_id.code`.
158
+ - **`rule.sort` ve `rule.limit` her table step'te verilir** — yoksa Studio'da workflow sayfası
159
+ `Cannot read properties of null (reading 'sortValue')` ile hiç açılmaz (**WF-013, block**).
160
+ - Join path disiplini burada da **MS-007 (block)** ile aynıdır.
161
+ - **Join varsa `rule.alias` (KÖK tablo alias'ı) zorunludur** ve kök entity'nin slug'ı olmalıdır:
162
+ join'in ON koşulunu `"<kökAlias>.<ilişkiAlanı>"` kurar, boş alias SQL'e `.<alan>` yazdırır ve
163
+ Postgres **`42601: zero-length delimited identifier`** ile reddeder — adım hiç çalışmaz, hata
164
+ sebebi söylemez. Gateway alias'ı kök `selectFields`'tan türetebiliyorsa doldurur, türetemezse
165
+ **WF-019** block'uyla isteği hiç göndermez. (Join'siz adımda alias eksikliği sorun çıkarmaz;
166
+ bu yüzden "join'siz çalışıyordu" gözlemi yanıltıcıdır.)
167
+ - **İlişki alanı bu bağlamda DÜZ FK'dır.** `row_list` ilişki alanını her zaman
168
+ `{ id, <displayAlanı> }` nesnesi döndürür (her ilişki için join'i kendisi üretir); table adımı
169
+ yalnız **`joins` ile bildirdiğin** ilişkiyi nesneye çevirir. Bildirilmeyen alanda
170
+ `<alan>.<altAlan>` okuması `undefined` verir — hata değil, SESSİZ boş sonuç (rol dizisi
171
+ boşalırsa yetki kapısı yanlış karar verir; crm koşumu B-1). Gateway **WF-020** ile uyarır.
172
+ Dayanıklı yazım: `var v = x.role_id && x.role_id.id ? x.role_id.id : x.role_id;`
173
+
174
+ ```jsonc
175
+ // Öğrencinin bir dersteki notlarını, ders bilgisiyle birlikte çeken table step
176
+ "rule": {
177
+ "stepType": "table", "queryStepSourceType": "library", "isExpression": false,
178
+ "datasourceId": "<datasource id>", "entityId": "<grades entity id>",
179
+ "operation": "get", "alias": "grades", "isSingle": false,
180
+ "selectFields": [
181
+ { "alias": "grades", "fieldId": "<score id>", "fieldSlug": "score" },
182
+ { "alias": "grades", "fieldId": "<weight id>", "fieldSlug": "weight" },
183
+ { "alias": "courses", "fieldId": "<code id>", "fieldSlug": "code", "path": "<grades.course_id>" }
184
+ ],
185
+ "joins": [
186
+ { "alias": "courses", "entityId": "<courses id>", "fieldId": "<grades.course_id>",
187
+ "path": "<grades entity id>.<grades.course_id>", "joins": [] }
188
+ ],
189
+ "filters": [
190
+ { "alias": "grades", "fieldId": "<student_id>", "operator": "eq", "source": "expression",
191
+ "value": "input.student_id", "filterType": "single", "conditionType": "and",
192
+ "isRequired": true, "children": [], "order": 0 }
193
+ ],
194
+ "fields": [], "returnFields": [],
195
+ "sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
196
+ "limit": { "name": "limit", "offset": "offset", "source": "query-string" }
197
+ }
198
+ ```
199
+ - **Filtre `value` alanı JavaScript ifadesidir** (eval edilir): sabit → `"'EMP004'"` (tırnak
200
+ içinde), dinamik → `"input.employee_no + '|' + input.year"`, ternary/`new Date()` çalışır.
201
+ Tırnaksız sabit `X is not defined` hatası verir. İfadelerde kullanılabilen platform
202
+ fonksiyonları (`system.getPageUrl`, `file.excel`, `UtcNow()`, `crypto.*`…) ve bağlam için
203
+ **`platform_guide('expressions')`** — uydurma fonksiyon adı runtime 500'üdür.
204
+ - Yazma step'lerinde (create/update): **yazılacak** alanlar `fields`, **okunacak** alanlar
205
+ `selectFields` — aynı alan ikisinde birden olamaz (`An item with the same key...`).
206
+ - **Yazma step'inin çıktısı yalnız primary key'dir** (`<step>.data.id`); sonraki step'lerin ya
207
+ da response'un başka alana ihtiyacı varsa bunları **`returnFields`** ile iste
208
+ (`{ alias, fieldId, fieldSlug }` öğeleriyle) — `selectFields` write operasyonunda sessizce
209
+ yok sayılır.
210
+
211
+ > **Gateway table step'lerinde `path`/`sort`/`limit` eksiklerini otomatik dolduruyor**
212
+ > (yanıtta `gatewayNormalized`). Dolu değerlere dokunmaz; yanlış path yine WF/MS block'una takılır.
213
+
214
+ ## Response step'i gövdeyi `bodyExpression`'dan üretir
215
+
216
+ `rule: { dataTransferObjectId }` **hiçbir şey döndürmez** (`result: null`). Çalışan şekil:
217
+
218
+ ```jsonc
219
+ "rule": {
220
+ "queryStepSourceType": "library", "isExpression": false,
221
+ "bodyExpression": "{\n success: true,\n message: 'Kayıt oluşturuldu.',\n id: new_enrollment.data.id\n}",
222
+ "httpStatusCode": 200, "headerExpression": "{ }",
223
+ "items": [], "returnType": "grouped", "enableDynamicFilters": false,
224
+ "selectFields": [], "fields": [], "returnFields": [], "filters": [], "joins": []
225
+ }
226
+ ```
227
+
228
+ Önceki step'in çıktısına **step adı + `.data`** ile erişilir (`existing_enrollment.data[0].id`,
229
+ `new_enrollment.data.id`) — bu yüzden step adları geçerli JavaScript tanımlayıcısı olmalı.
230
+ `returnDtoId` yine zorunludur (WF-002); sözleşmeyi o tanımlar, gövdeyi `bodyExpression` üretir.
231
+
232
+ ## `DtoId` verilen yerde `Behavior` de zorunlu
233
+
234
+ `returnDtoId` verildiyse `returnBehavior`, `parameterDtoId` verildiyse `parameterBehavior`
235
+ zorunludur (`CreateWorkflowCommandValidator`: `When(ReturnDtoId.HasValue) → ReturnBehavior
236
+ .NotEmpty()`); atlamak `'Return Behavior' must not be empty` ile ilk denemeyi kırar. Değerler:
237
+ `object` (varsayılan tercih) · `array` (akış DİZİ döndürüyorsa) · `primitive` (DTO primitive ise
238
+ — aksi hâlde `WrongDtoBehaviorException`). **Gateway boş bırakılanı `object` ile doldurup
239
+ `gatewayNormalized` ile bildirir**; dizi/primitive dönen akışlarda değeri sen ver.
240
+
241
+ ## Asgari iskelet (id'ler gerçek GUID olacak — placeholder'ları değiştir)
242
+
243
+ ```jsonc
244
+ {
245
+ "name": "Product Lookup", "slug": "product-lookup", "type": "workflow",
246
+ "parameterDtoId": "<girdi DTO id>", "parameterBehavior": "object",
247
+ "returnDtoId": "<çıktı DTO id>", "returnBehavior": "object",
248
+ "steps": [
249
+ { "id": "<GUID-1>", "name": "Start", "type": "start",
250
+ "branches": [{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" }] },
251
+ { "id": "<GUID-3>", "name": "load_product", "type": "table",
252
+ "rule": { "stepType": "table", "datasourceId": "<datasource id>", "entityId": "<entity id>",
253
+ "operation": "get", "isSingle": true,
254
+ "selectFields": [{ "alias": "<entity slug>", "fieldId": "<alan id>", "fieldSlug": "name" }],
255
+ "filters": [{ "fieldId": "<alan id>", "fieldSlug": "name", "operator": "eq", "value": "input.productName" }],
256
+ "joins": [],
257
+ "sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
258
+ "limit": { "name": "limit", "offset": "offset", "source": "query-string" } },
259
+ "branches": [{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-5>" },
260
+ { "id": "<GUID-6>", "name": "fail", "type": "fail", "nextStepId": null }] },
261
+ { "id": "<GUID-5>", "name": "product_response", "type": "response",
262
+ "rule": { "bodyExpression": "{ name: load_product.data.name }", "httpStatusCode": 200,
263
+ "headerExpression": "{ }" }, "branches": [] }
264
+ ]
265
+ }
266
+ ```
267
+
268
+ ## Automation gerçekleri (hr + qm projelerinde doğrulandı)
269
+
270
+ - **`returnDtoId`/`parameterDtoId` automation'da YASAKTIR** (validator: "'Return Dto Id' must
271
+ be empty"). `returnBehavior: "object"` verilebilir.
272
+ - **`state: 'unready'` automation için NORMALDİR ve tetiklemeyi etkilemez** — qm'deki çalışan
273
+ automation'lar da unready'dir; "Studio'dan aktifleştirme" diye bir işlem yoktur.
274
+ - **Tetikleme kapısı üç alandır**, üçü de `automation` nesnesinde: `entityId` (tetikleyen
275
+ entity), `operation` (`create`/`update`/`delete`), **`isQueue` (false=anlık, true=kuyruklu;
276
+ null bırakılırsa tetikleme sorgusu HİÇ eşleşmez — automation sessizce ölü kalır)**.
277
+ `condition` opsiyonel JavaScript ifadesidir. `isActive` diye bir alan YOKTUR (yok sayılır).
278
+ - `steps` hem üst seviyede hem `automation` içinde verilir (PLT-007); `response` step'i
279
+ kullanılamaz, hata yolu için `revert` vardır.
280
+ - **Tetikleyen satıra `input` ile DEĞİL `row` ile erişilir**; update'te `previousRow`, işlemi
281
+ yapan kullanıcı `user` altındadır (`row.title`, `previousRow.user`, `user.id`). Step
282
+ `fields[]` öğelerinde `source: 'body'` + `value: '<ifade>'` kullanılır.
283
+ - **`isQueue: false` senkron demek değildir.** Tetikleme ateşle-unut çalışır; satırı yazdıktan
284
+ hemen sonra sonucu okursanız automation'ın yazdığı satırı göremezsiniz. Kısa bir bekleme
285
+ sonrası tekrar okuyun.
286
+ - Hem `row_create` ucundan hem `operation: 'create'` yapan bir microservice'ten tetiklenir —
287
+ ikisi de aynı hook'a düşer.
288
+ - **Bilinen platform sınırı (P-03):** kısa aralıklı ardışık tetiklerde (özellikle `rows[]` ile
289
+ TOPLU `row_create`) automation yalnız ilk satır için tetiklenebiliyor — kayıp satırlar
290
+ `automation_histories`'te hiç görünmez. Otomasyonlu entity'lere satırları **tekil** yazın
291
+ (ya da sonradan `automation_trigger` ile telafi edin); toplu ekleme sonrası tetiklenmeyi
292
+ mutlaka sayarak doğrulayın.
293
+ - Doğrulama: `automation_trigger` + `automation_histories` (ya da tetikleyen satırı yazıp
294
+ sonucu kontrol et).
295
+
296
+ ```jsonc
297
+ {
298
+ "name": "New Hire Setup", "slug": "new-hire-setup", "type": "automation",
299
+ "returnBehavior": "object",
300
+ "steps": [ ...GUID id'li steps... ],
301
+ "automation": {
302
+ "name": "New Hire Setup", "slug": "new-hire-setup", "type": "automation",
303
+ "entityId": "<tetikleyen entity id>",
304
+ "operation": "create",
305
+ "isQueue": false,
306
+ "steps": [ ...aynı steps... ]
307
+ }
308
+ }
309
+ ```
310
+
311
+ ## Sık hatalar (politika lint'inin yakaladıkları)
312
+
313
+ - Bağlanmamış step grafı (WF-001, block) · workflow'da returnDtoId eksik (WF-002, block)
314
+ - Yanlış tipte step (WF-005, block) · GUID olmayan step/branch id'si (WF-011, block)
315
+ - Automation'da `isQueue`/`operation` eksik ya da returnDtoId verilmiş (WF-010, block)
316
+ - `fields` ∩ `selectFields` çakışması (WF-012, warn) · join'li table'da boş selectFields (WF-007, warn)
317
+ - Join'li table step'inde kök `rule.alias` boş (WF-019, block — Postgres 42601'in sebebi)
318
+ - İlişki alanının içini okuyan ifade (WF-020, warn — join yoksa alan düz FK'dır)
319
+ - `set-user` adımı olmadan `user` okuyan middleware (MW-003, warn)
320
+
321
+ ## Hedefli değişiklik: `workflow_patch` yollarında İNDEKS KULLANMA
322
+
323
+ Saklanan `steps` dizisinin sırası, `workflow_create`'e **gönderdiğin sıra değildir**. `/steps/5/...`
324
+ gibi indeks yolları bu yüzden başka adımı yazar ve yanıt yine `applied: N` der — sessiz veri
325
+ bozulması (crm koşumu B-4). Seçici kullan:
326
+
327
+ ```jsonc
328
+ { "op": "replace", "path": "/steps[id=<stepId>]/rule/expression", "value": "…" }
329
+ { "op": "replace", "path": "/steps[name=setUser]/rule/joins", "value": [] }
330
+ ```
331
+
332
+ Gateway seçiciyi gövde platforma **gitmeden** indekse çevirir, çözülemezse isteği hiç göndermez
333
+ ve yanıtta `patchTargets` ile her op'un hangi adıma (ad + tip + id) indiğini söyler. Aynı seçici
334
+ `microservice_patch`'te de çalışır (`/sourceParameters/joins[alias=customers]/path`).
335
+
336
+
337
+
338
+ ## Table adımının filtresi: `alias` YOKSA filtre SESSİZCE DÜŞER
339
+
340
+ Bir `table` adımının `filters[]` girdisinde **`alias` zorunludur** ve entity'nin adım içindeki
341
+ `alias` değeriyle birebir aynı olmalıdır. Eksikse filtre uygulanmaz; hata da çıkmaz.
342
+
343
+ `operation: 'get'` + `isSingle: true` ile sonucu **felakete dönüşür**: sorgu tablodan rastgele
344
+ (sıralamaya göre değişen) bir satır döndürür ve akış onu doğru kayıt sanar. Ölçüm (2026-08-18,
345
+ `ik` zimmet akışı): `asset_id` verilmesine rağmen her koşumda başka bir demirbaş döndü; aynı
346
+ kusur `operation: 'update'` filtresinde **yanlış satırı günceller**.
347
+
348
+ ```jsonc
349
+ // ✘ filtre yok sayılır — istek 200 döner
350
+ { "fieldSlug": "id", "operator": "eq", "value": "input.asset_id", "fieldId": "<id alanı>" }
351
+
352
+ // ✔ doğrusu
353
+ { "alias": "assets", "fieldId": "<id alanı>", "fieldSlug": "id", "operator": "eq",
354
+ "source": "expression", "value": "input.asset_id",
355
+ "filterType": "single", "conditionType": "and", "isRequired": true, "children": [], "order": 0 }
356
+ ```
357
+
358
+ `source` da **`expression`** olmalı: verilmezse `body`'ye düşer ve `value` bir ifade değil
359
+ **gövde alanı adı** sayılır — yine filtre kaybolur.
@@ -0,0 +1,44 @@
1
+ import { fileURLToPath } from 'node:url';
2
+ import { readdirSync, readFileSync } from 'node:fs';
3
+ import { join, dirname } from 'node:path';
4
+ const resourcesDirectory = dirname(fileURLToPath(import.meta.url));
5
+ export function listKnowledgeFiles(kind) {
6
+ try {
7
+ return readdirSync(join(resourcesDirectory, kind)).filter(file => file.endsWith(kind === 'guides' ? '.md' : '.json'));
8
+ }
9
+ catch {
10
+ return [];
11
+ }
12
+ }
13
+ export function readKnowledgeFile(kind, file) {
14
+ return readFileSync(join(resourcesDirectory, kind, file), 'utf8');
15
+ }
16
+ export function registerKnowledgeResources(server) {
17
+ for (const kind of ['guides', 'examples']) {
18
+ const files = listKnowledgeFiles(kind);
19
+ const mimeType = kind === 'guides' ? 'text/markdown' : 'application/json';
20
+ server.registerResource(`${kind}-index`, `ngapps://${kind}/index`, {
21
+ title: kind === 'guides' ? 'Rehber dizini' : 'Altın örnek dizini',
22
+ description: kind === 'guides'
23
+ ? 'Platform iş kuralları rehberleri; modül tool\'larını kullanmadan önce okuyun.'
24
+ : 'Onaylanmış tam gövde örnekleri; boş org\'da şablon kaynağı budur.',
25
+ mimeType: 'application/json'
26
+ }, async (uri) => ({
27
+ contents: [{
28
+ uri: uri.href,
29
+ mimeType: 'application/json',
30
+ text: JSON.stringify({ [kind]: files.map(file => `ngapps://${kind}/${file.replace(/\.(md|json)$/, '')}`) })
31
+ }]
32
+ }));
33
+ for (const file of files) {
34
+ const name = file.replace(/\.(md|json)$/, '');
35
+ server.registerResource(`${kind}-${name}`, `ngapps://${kind}/${name}`, {
36
+ title: name,
37
+ description: kind === 'guides' ? `${name} modülü iş kuralları rehberi` : `${name} altın örneği`,
38
+ mimeType
39
+ }, async (uri) => ({
40
+ contents: [{ uri: uri.href, mimeType, text: readKnowledgeFile(kind, file) }]
41
+ }));
42
+ }
43
+ }
44
+ }
@@ -0,0 +1,54 @@
1
+ {
2
+ "schema": {
3
+ "type": "object",
4
+ "properties": {
5
+ "name": {
6
+ "type": "string",
7
+ "nullable": true
8
+ },
9
+ "description": {
10
+ "type": "string",
11
+ "nullable": true
12
+ },
13
+ "dbName": {
14
+ "type": "string",
15
+ "nullable": true
16
+ },
17
+ "type": {
18
+ "$ref": "#/components/schemas/NgApps.Domain.Contexts.Datastore.Datasources.DatasourceType"
19
+ },
20
+ "host": {
21
+ "type": "string",
22
+ "nullable": true
23
+ },
24
+ "port": {
25
+ "type": "integer",
26
+ "format": "int32"
27
+ },
28
+ "properties": {
29
+ "nullable": true
30
+ },
31
+ "connectionOptions": {
32
+ "type": "object",
33
+ "additionalProperties": {
34
+ "type": "string",
35
+ "nullable": true
36
+ },
37
+ "nullable": true
38
+ }
39
+ },
40
+ "additionalProperties": false
41
+ },
42
+ "components": {
43
+ "schemas": {
44
+ "NgApps.Domain.Contexts.Datastore.Datasources.DatasourceType": {
45
+ "enum": [
46
+ "postgresql",
47
+ "mongodb"
48
+ ],
49
+ "type": "string",
50
+ "format": ""
51
+ }
52
+ }
53
+ }
54
+ }