@appaflytech/zeyrek-ai-mcp 0.0.2 → 0.0.4
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 +21 -7
- package/.env.production.example +42 -0
- package/README.md +14 -7
- package/dist/config.js +113 -46
- package/dist/knowledge/componentCatalog.js +476 -32
- package/dist/knowledge/componentEvents.js +154 -12
- package/dist/knowledge/errorPath.js +64 -0
- package/dist/knowledge/normalize.js +12 -1
- package/dist/knowledge/outlines.js +9 -2
- package/dist/knowledge/rules.js +184 -3
- package/dist/knowledge/styleResolve.js +485 -0
- package/dist/observability/trace.js +4 -0
- package/dist/platform/http.js +3 -0
- package/dist/platform/responses.js +80 -4
- package/dist/platform/screenshot.js +106 -0
- package/dist/resources/examples/layout-app-shell.json +368 -0
- package/dist/resources/examples/microservice-table-list.json +1 -0
- package/dist/resources/examples/middleware-authenticate.json +243 -47
- package/dist/resources/examples/workflow-basic.json +24 -2
- package/dist/resources/guides/component.md +6 -3
- package/dist/resources/guides/entity.md +33 -1
- package/dist/resources/guides/external-component.md +129 -0
- package/dist/resources/guides/layout.md +147 -0
- package/dist/resources/guides/microservice.md +35 -0
- package/dist/resources/guides/middleware.md +10 -0
- package/dist/resources/guides/page.md +137 -15
- package/dist/resources/guides/translation.md +80 -0
- package/dist/resources/guides/workflow.md +127 -2
- package/dist/server.js +4 -0
- package/dist/tools/externalComponents.js +285 -0
- package/dist/tools/layouts.js +48 -6
- package/dist/tools/microservices.js +2 -2
- package/dist/tools/pages.js +98 -30
- package/dist/tools/platformInfo.js +2 -2
- package/dist/tools/projects.js +7 -2
- package/dist/tools/screens.js +36 -0
- package/dist/tools/translations.js +37 -0
- package/dist/tools/workflows.js +3 -3
- package/package.json +4 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# Çoklu Dil Rehberi (page/content translations)
|
|
2
|
+
|
|
3
|
+
Sayfa içeriğini (metin, buton etiketi, placeholder…) çoklu dilli yapan özellik. Studio panelinin
|
|
4
|
+
kendi arayüz dili (`i18n.md`, yalnız frontend'i ilgilendirir) ile **karıştırma** — bu rehber
|
|
5
|
+
**son kullanıcının gördüğü sayfa metnini** kapsar. Kaynak: `TranslationsController.cs`
|
|
6
|
+
(`zeyrek-backend/src/NgApps.Tenant.API/Controllers/`) + `zeyrek-frontend/docs/features/page-translations.md`.
|
|
7
|
+
|
|
8
|
+
## Model
|
|
9
|
+
|
|
10
|
+
- Çeviri kaydı **application başına** tutulur (`(applicationId, language, key)` başına bir satır);
|
|
11
|
+
aynı application'daki tüm sayfalar aynı anahtar kümesini paylaşır.
|
|
12
|
+
- Bir kayıt isteğe bağlı bir **`componentId`**'ye bağlanabilir — o komponent silinince kaydı da
|
|
13
|
+
kademeli silinir. Salt organizasyon amaçlıdır; runtime çözümlemeyi etkilemez (çözümleme yalnız
|
|
14
|
+
`key`'e bakar).
|
|
15
|
+
- Proje **dilleri** ve **varsayılan dil** application değil **proje** düzeyinde ayardır
|
|
16
|
+
(`project_get`/`project_update` `languages`/`defaultLanguage`) ve tam **ISO 639-1** kod
|
|
17
|
+
kümesinden seçilir (`ProjectConstants.SupportedLanguages`, ~184 kod). `translation_update`'e
|
|
18
|
+
proje dillerinde olmayan bir kod verirsen platform isteği reddeder — önce `project_update` ile
|
|
19
|
+
dili ekle.
|
|
20
|
+
|
|
21
|
+
## Araçlar
|
|
22
|
+
|
|
23
|
+
| Tool | Ne yapar |
|
|
24
|
+
|---|---|
|
|
25
|
+
| `translation_list` | Application'ın tüm `(language, key, value)` kayıtlarını düz liste döner |
|
|
26
|
+
| `translation_update` | `keys` (ekle/güncelle, `componentId` opsiyonel) ve/veya `deletedKeys` (sil) — ikisi de aynı çağrıda verilebilir, en az biri dolu olmalı |
|
|
27
|
+
| `project_update` | `languages`/`defaultLanguage` — proje dillerini kurar/değiştirir |
|
|
28
|
+
|
|
29
|
+
```jsonc
|
|
30
|
+
// yeni anahtar aç / var olanı güncelle
|
|
31
|
+
{ "applicationId": "<app id>", "keys": [
|
|
32
|
+
{ "key": "home.welcome", "values": { "tr": "Hoş geldiniz", "en": "Welcome" } }
|
|
33
|
+
] }
|
|
34
|
+
|
|
35
|
+
// bir anahtarı sil, bir başkasını aynı çağrıda güncelle
|
|
36
|
+
{ "applicationId": "<app id>",
|
|
37
|
+
"keys": [{ "key": "home.title", "values": { "tr": "Ana sayfa" } }],
|
|
38
|
+
"deletedKeys": ["home.old_banner"] }
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
## Bir prop'u çeviriye bağlamak
|
|
42
|
+
|
|
43
|
+
Sayfa şemasında (`platform_guide('page')` Kural 1) çeviriye bağlı bir prop **Prop nesnesinin**
|
|
44
|
+
`type:'translation'` varyantıdır — `PropValueType.Translation` (`DesignComponentProperty.cs`)
|
|
45
|
+
backend'de zaten tanımlı, Studio'nun "Translate" aksiyonunun ürettiği şekil budur:
|
|
46
|
+
|
|
47
|
+
```jsonc
|
|
48
|
+
{ "op": "replace_prop", "target": "welcome_text", "prop": "text",
|
|
49
|
+
"value": { "type": "translation", "ref": "home.welcome", "data": "Hoş geldiniz" } }
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
- **Anahtar `ref`'tedir, `data` DEĞİL.** Runtime her `getNodeProps` turunda `data`'yı
|
|
53
|
+
`resolveTranslation(...)` sonucuyla EZER (`code` prop'larıyla aynı davranış — `page.md` Kural 1c
|
|
54
|
+
bunun `code` karşılığını anlatır). `ref`'i okumadan/yazmadan `data`'ya güvenme; `data` yalnız
|
|
55
|
+
Studio'nun sakladığı önceki statik değerdir (fallback/geri dönüş amaçlı, çözümlemeyi ETKİLEMEZ).
|
|
56
|
+
- **Fallback zinciri:** aktif dil → projenin varsayılan dili → anahtarın kendisi. Boş string de
|
|
57
|
+
"eksik" sayılır ve zincirde bir alt basamağa düşer — bir anahtar hiçbir zaman boş render olmaz.
|
|
58
|
+
- Hangi prop'lar çeviriye bağlanabilir platformca kısıtlanmaz (Studio'da yalnız `text`/`long-text`
|
|
59
|
+
tipli prop'larda "Translate" aksiyonu gösterilir, ama şema düzeyinde her static prop aynı Prop
|
|
60
|
+
şeklini kabul eder).
|
|
61
|
+
|
|
62
|
+
## Dil değiştirme: `language-selector`
|
|
63
|
+
|
|
64
|
+
Son kullanıcı dili `language-selector` komponentiyle değiştirir (`platform_guide('component')`
|
|
65
|
+
+ `componentCatalog` içindeki `language-selector` kaydı): `change` olayı `machine.setLanguage(value)`
|
|
66
|
+
çağırır, seçim `localStorage`'a (`zeyrek:{appSlug}:lang`) yazılır ve çeviriye bağlı **bütün**
|
|
67
|
+
prop'lar sayfa yenilenmeden yeniden çözülür — ayrı bir akış kurmana gerek yok, komponenti sayfaya
|
|
68
|
+
eklemen yeterli. Projede dil tanımlı değilse (`languages` boşsa) liste boş çizilir.
|
|
69
|
+
|
|
70
|
+
## Bilinen sınırlar
|
|
71
|
+
|
|
72
|
+
- **Flow adımlarında Translation kaynağı YOK.** `value.type` sözlüğünde (`platform_guide('page')`
|
|
73
|
+
Kural 1c sonundaki tablo) `translation` yoktur — bir flow adımının **değerini** doğrudan bir
|
|
74
|
+
çeviri anahtarına bağlayamazsın, yalnız sayfa şemasındaki statik prop'lar bağlanabilir. İfadeden
|
|
75
|
+
okumak gerekiyorsa `t('anahtar')` Jexl fonksiyonu kullanılır (`platform_guide('expressions')`).
|
|
76
|
+
- **Runtime sayfa yanıtı sunucu tarafında önbelleklidir** (Postgres `caches` tablosu). Bir
|
|
77
|
+
`translation_update`/`project_update(languages)` çağrısından hemen sonra `page_runtime_get`
|
|
78
|
+
eski değerleri gösterebilir — önbellek süresi dolana kadar. Acil doğrulama gerekiyorsa
|
|
79
|
+
`translation_list` ile yazma tarafını, `page_runtime_get` ile okuma tarafını AYRI doğrula.
|
|
80
|
+
- Mobil runtime (`apps/mobile`) bu özelliği henüz beslemiyor — yalnız web.
|
|
@@ -2,6 +2,65 @@
|
|
|
2
2
|
|
|
3
3
|
Buradaki kurallar gerçek projelerde (qm, hr) **deneyerek doğrulandı**.
|
|
4
4
|
|
|
5
|
+
## Her adım NE İŞE YARADIĞINI yazar (WF-024, block)
|
|
6
|
+
|
|
7
|
+
Studio kanvası adımın **adını** gösterir, ne yaptığını göstermez. 11 satırlık bir `code` adımının
|
|
8
|
+
işini anlamak için ifadeyi satır satır okumak gerekir — akışı devralan geliştirici için bu, her
|
|
9
|
+
seferinde tersine mühendisliktir. Bu yüzden **her adım** ve **akışın kendisi** bir `description`
|
|
10
|
+
taşır; gateway açıklamasız gövdeyi platforma göndermez.
|
|
11
|
+
|
|
12
|
+
```jsonc
|
|
13
|
+
{ "name": "anket-kaydet", "type": "workflow",
|
|
14
|
+
"description": "Anket başlığını ve sorularını tek çağrıda kaydeder.",
|
|
15
|
+
"steps": [
|
|
16
|
+
{ "id": "<GUID>", "name": "createSurvey", "type": "table",
|
|
17
|
+
"description": "Anket başlığını surveys'e yazar; oluşan id sonraki adımlarda kullanılır.",
|
|
18
|
+
"rule": { … } }
|
|
19
|
+
] }
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
- **Mekanizmayı değil NİYETİ yaz.** `"assignments tablosunu status='Aktif' ile okur"` koddan zaten
|
|
23
|
+
okunuyor ve hiçbir şey eklemiyor. `"kullanıcının asal pozisyonunu seçer; birden çok aktif atama
|
|
24
|
+
varsa is_primary olanı tercih eder, o da yoksa ilkini alır"` — yazılmadıkça hiçbir yerde
|
|
25
|
+
bulunmayan bilgi budur.
|
|
26
|
+
- **`start` ve `end` muaftır** (kural onları hiç sormaz): ikisi de yapısal işaretçi, açıklanacak
|
|
27
|
+
davranışları yok.
|
|
28
|
+
- **Gateway'in enjekte ettiği `gatewayOnError` adımı kendi açıklamasını taşır** — o adımı sen
|
|
29
|
+
yazmadığın için kural seni onun yüzünden durdurmaz.
|
|
30
|
+
- Alan **kalıcıdır ve çalışma zamanını etkilemez**: motor okumaz, yalnız Studio'da kanvas
|
|
31
|
+
düğümünde ⓘ ikonu + popover olarak ve adım panelinde düzenlenebilir metin olarak görünür.
|
|
32
|
+
İnsan da düzeltebilir.
|
|
33
|
+
- `preview` bu kuralın dışındadır — kaydedilmeyen bir denemede açıklama aranmaz.
|
|
34
|
+
- Aynı disiplin ucun kendisi için de geçerli: `platform_guide('microservice')` §MS-014.
|
|
35
|
+
|
|
36
|
+
## Tuzağı varsa `caution` yaz (opsiyonel, kural yok)
|
|
37
|
+
|
|
38
|
+
`description` "bu adım ne işe yarar" sorusunu cevaplar. `caution` bambaşka bir soruyu cevaplar:
|
|
39
|
+
**"burada neye dikkat etmek gerekiyor."** İkisi ayrı alandır çünkü tek metne sıkıştırıldıklarında
|
|
40
|
+
ikisi de okunmaz olur — açıklama uyarı listesine döner, uyarı da açıklamanın içinde kaybolur.
|
|
41
|
+
|
|
42
|
+
**Bu alanı hiçbir kural zorlamaz ve doldurma metin kabul edilmez.** Adımın gerçek bir tuzağı
|
|
43
|
+
yoksa alanı hiç yazma. Yazmaya değer olanlar:
|
|
44
|
+
|
|
45
|
+
- **İdempotent değil** — "iki kez çalışırsa çift atama yazar; çağıran tarafta koruma yok"
|
|
46
|
+
- **Kapsam bu adımda değil** — "tenant filtresi burada yok, kimlik kapsaması bağlı middleware'de"
|
|
47
|
+
- **Dış bağımlılık** — "ödeme sağlayıcısına senkron çağrı; sağlayıcı yavaşlarsa istek bekler"
|
|
48
|
+
- **Sessiz düşen dal** — "eşleşme bulunamazsa hata dönmez, boş dizi döner"
|
|
49
|
+
|
|
50
|
+
```jsonc
|
|
51
|
+
{ "id": "<GUID>", "name": "createAssignment", "type": "table",
|
|
52
|
+
"description": "Kullanıcıyı seçilen pozisyona atar.",
|
|
53
|
+
"caution": "İki kez çalışırsa çift atama yazar; çağıran tarafta idempotency koruması yok." }
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Yazılmayacaklar:** `"bu adım veriyi okur, dikkat"` gibi mekanizma tekrarı, `description`'ın
|
|
57
|
+
başka kelimelerle yazılmışı, `"dikkatli olun"` gibi genel tavsiye. Bunlar kanvastaki ⚠ işaretini
|
|
58
|
+
değersizleştirir — işaretin tüm değeri **seyrek** olmasından gelir.
|
|
59
|
+
|
|
60
|
+
Alan `description` gibi inerttir: motor okumaz. Studio'da kanvas düğümünde kehribar ⚠ ikonu,
|
|
61
|
+
adım panelinde düzenlenebilir metin, analiz belgesinde ayrı satır olarak görünür; `workflow_get`
|
|
62
|
+
outline görünümünde geri okunur — sonraki oturumda tuzağı tam gövdeyi almadan görürsün.
|
|
63
|
+
|
|
5
64
|
## Karar kuralları
|
|
6
65
|
|
|
7
66
|
1. **DTO önce (yalnız workflow tipinde).** `type='workflow'` için girdi/çıktı sözleşmesini
|
|
@@ -49,6 +108,55 @@ Bağlanmamış step çalışmaz. `start` tek branch taşır; normal step'ler `su
|
|
|
49
108
|
`condition` koşul branch'leri + `else`; terminal step'ler (`response`, `convert`, `revert`,
|
|
50
109
|
`end`) **branch taşımaz**.
|
|
51
110
|
|
|
111
|
+
## Hata yolu
|
|
112
|
+
|
|
113
|
+
**Gateway workflow ve middleware tanımlarına hata yolunu otomatik ekler** (yanıtta
|
|
114
|
+
`gatewayNormalized`): start adımına `type: 'global-error'` dalı + 500 dönen bir `response` adımı
|
|
115
|
+
(`gatewayOnError`). Kendi hata gövdeni istiyorsan bu adımın `bodyExpression`'ını değiştir ya da
|
|
116
|
+
tanımı kendi global-error dalınla gönder — dal **bağlıysa** (`nextStepId` doluysa) gateway
|
|
117
|
+
dokunmaz. `nextStepId: null` ile bağlanmamış gönderirsen gateway ikinci bir dal eklemez, mevcut
|
|
118
|
+
dalını enjekte ettiği hata adımına bağlar.
|
|
119
|
+
|
|
120
|
+
**Fail dallarını tek tek bağlamana gerek yok.** Bir adımın `fail` dalı bağlanmamışsa
|
|
121
|
+
(`nextStepId: null`) ya da hiç yoksa, hata **kendiliğinden** global-error dalına gider. `fail`
|
|
122
|
+
dalını yalnız o adıma özel bir telafi akışı (örn. "kayıt yoksa oluştur") istiyorsan bağla.
|
|
123
|
+
|
|
124
|
+
Global-error dalında `error` değişkeni tanımlıdır:
|
|
125
|
+
|
|
126
|
+
```jsonc
|
|
127
|
+
{ "code": "RowAlreadyExists", "message": "…", "detail": "…", "type": "…",
|
|
128
|
+
"step": { "id": "…", "name": "writeRow", "type": "table", "data": …, "input": [ … ] } }
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
`error.step` hangi adımın patladığını söyler — hata yanıtına koymadan önce iki kez düşün, iç
|
|
132
|
+
yapıyı dışarı sızdırır.
|
|
133
|
+
|
|
134
|
+
**Kurallar ve sınırlar:**
|
|
135
|
+
|
|
136
|
+
- **Global-error dalı TEK olmalı ve START adımında durmalı.** Başka adıma koyulan dal hiç
|
|
137
|
+
çalışmaz, platform 400 döner. Gateway bunu `WF-022` ile istek gitmeden reddeder.
|
|
138
|
+
- **Hata `response` adımı ReturnDto'ya bağlı değildir.** Global-error modunda platform sözleşmeyi
|
|
139
|
+
atlar, gövde serbesttir — `returnDtoId` ile çakışmaz.
|
|
140
|
+
- **Loop içi hatalar global-error dalına GİTMEZ.** Loop çocuk bağlamı bu yönlendirmeyi kapatır;
|
|
141
|
+
loop adımının kendi `fail` dalını bağla.
|
|
142
|
+
- **`automation` tipinde `response` adımı yasaktır**, bu yüzden gateway bu tipe enjeksiyon
|
|
143
|
+
yapmaz. Hata yolunu kendin yaz: global-error dalını `revert` (tetikleyen satır işlemini geri
|
|
144
|
+
alır) ya da `email`/`table` adımına bağla. Bağlı hata yolu yoksa `WF-021` uyarır.
|
|
145
|
+
- **Hata yolu hiç yoksa** akış patladığında platform 500 + ham exception mesajı döner. Çalışır,
|
|
146
|
+
ama çıktı denetimsizdir ve lokalize değildir.
|
|
147
|
+
|
|
148
|
+
> **Preview HTTP 200 dönerken bile akış patlamış olabilir.** `executionSuccess` yalnız SON adıma
|
|
149
|
+
> bakar; ortada patlayıp hata dalından dönen akış `true` gösterir. Gateway tüm adımları (iç içe
|
|
150
|
+
> `children` ve tetiklenen `automations` dâhil) tarar ve hata bulursa yanıta `runtimeErrors`
|
|
151
|
+
> alanını ekler — `workflow_preview` ve `automation_preview` yanıtlarında bu alanı kontrol et.
|
|
152
|
+
>
|
|
153
|
+
> **`runtimeErrors` her tool'da aynı şeyi ölçmez.** Alan yalnız `steps[]` taşıyan yanıtlarda adım
|
|
154
|
+
> adım tarama yapar. `microservice_execute` / `microservice_debug` / `microservice_preview`
|
|
155
|
+
> yanıtlarında gateway sadece `httpStatus >= 400` durumunu bulgu sayar: bu tool'lar adım ağacını
|
|
156
|
+
> `executionItems` olarak döner ve o düğümlerde hata işareti yoktur, yani HTTP 200 gövdesine gömülü
|
|
157
|
+
> bir microservice hatası `runtimeErrors` üretmez. Orada `body`'yi kendin oku ve şüpheliyse
|
|
158
|
+
> `microservice_debug` ile adım adım bak. `query_preview` yanıtları hiç kapsanmaz.
|
|
159
|
+
|
|
52
160
|
## Condition step kuralları
|
|
53
161
|
|
|
54
162
|
Koşullar `rule.items[]` dizisindedir ve **sırayla** değerlendirilir; ilk `true` olan dala
|
|
@@ -151,6 +259,11 @@ Tek uçta "başlık + N alt kayıt" yazmanın yolu budur (anket + sorular + seç
|
|
|
151
259
|
|
|
152
260
|
- `rule` içinde `datasourceId` + `entityId` zorunlu; `selectFields`/`filters` öğelerinde
|
|
153
261
|
**`fieldId` zorunlu** (yalnız `fieldSlug` yetmez → `EvaluateWorkflowStepFailedException`).
|
|
262
|
+
- **`selectFields` = akışın gerçekten OKUDUĞU alanlar.** Entity'nin tüm alanlarını işaretlemek
|
|
263
|
+
her satırı gereksiz büyütür (join'li adımda maliyet katlanır) ve adımın ne ürettiğini
|
|
264
|
+
okunamaz hâle getirir. Sonraki adımların, filtrelerin ve response `bodyExpression`'ının
|
|
265
|
+
dokunmadığı alanları çıkarın — gateway bunları **WF-023 (warn)** ile adıyla sayar. Çıktıyı
|
|
266
|
+
bütün olarak geçiriyorsanız (`{ list: rows.data }`) kural susar.
|
|
154
267
|
- **Table step JOIN kurabilir** ve alias/path disiplini microservice'inkiyle **birebir aynıdır**
|
|
155
268
|
(Studio aynı form bileşenlerini kullanır): `joins[].path` = `<kökEntityId>.<ilişkiAlanı…>`,
|
|
156
269
|
join'li `selectFields[].path` = kök id olmadan aynı zincir, kök alanlarda `path` yok.
|
|
@@ -243,12 +356,15 @@ zorunludur (`CreateWorkflowCommandValidator`: `When(ReturnDtoId.HasValue) → Re
|
|
|
243
356
|
```jsonc
|
|
244
357
|
{
|
|
245
358
|
"name": "Product Lookup", "slug": "product-lookup", "type": "workflow",
|
|
359
|
+
"description": "Ada göre tek ürün getirir; bulunamazsa hata yoluna düşer.",
|
|
246
360
|
"parameterDtoId": "<girdi DTO id>", "parameterBehavior": "object",
|
|
247
361
|
"returnDtoId": "<çıktı DTO id>", "returnBehavior": "object",
|
|
248
362
|
"steps": [
|
|
249
363
|
{ "id": "<GUID-1>", "name": "Start", "type": "start",
|
|
250
|
-
"branches": [{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" }
|
|
364
|
+
"branches": [{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" },
|
|
365
|
+
{ "id": "<GUID-7>", "name": "global-error", "type": "global-error", "nextStepId": "<GUID-8>" }] },
|
|
251
366
|
{ "id": "<GUID-3>", "name": "load_product", "type": "table",
|
|
367
|
+
"description": "input.productName ile ürünü okur; tek kayıt bekler.",
|
|
252
368
|
"rule": { "stepType": "table", "datasourceId": "<datasource id>", "entityId": "<entity id>",
|
|
253
369
|
"operation": "get", "isSingle": true,
|
|
254
370
|
"selectFields": [{ "alias": "<entity slug>", "fieldId": "<alan id>", "fieldSlug": "name" }],
|
|
@@ -258,9 +374,16 @@ zorunludur (`CreateWorkflowCommandValidator`: `When(ReturnDtoId.HasValue) → Re
|
|
|
258
374
|
"limit": { "name": "limit", "offset": "offset", "source": "query-string" } },
|
|
259
375
|
"branches": [{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-5>" },
|
|
260
376
|
{ "id": "<GUID-6>", "name": "fail", "type": "fail", "nextStepId": null }] },
|
|
377
|
+
// ^ bağlanmamış fail dalı: hata global-error dalına gider
|
|
261
378
|
{ "id": "<GUID-5>", "name": "product_response", "type": "response",
|
|
379
|
+
"description": "Bulunan ürünün adını 200 ile döner.",
|
|
262
380
|
"rule": { "bodyExpression": "{ name: load_product.data.name }", "httpStatusCode": 200,
|
|
263
|
-
"headerExpression": "{ }" }, "branches": [] }
|
|
381
|
+
"headerExpression": "{ }" }, "branches": [] },
|
|
382
|
+
{ "id": "<GUID-8>", "name": "onError", "type": "response",
|
|
383
|
+
"description": "Akış patlarsa hata kodunu ve mesajını 500 ile döner.",
|
|
384
|
+
"rule": { "stepType": "response", "httpStatusCode": 500,
|
|
385
|
+
"bodyExpression": "({ code: error.code, message: error.message })" },
|
|
386
|
+
"branches": [] }
|
|
264
387
|
]
|
|
265
388
|
}
|
|
266
389
|
```
|
|
@@ -315,6 +438,8 @@ zorunludur (`CreateWorkflowCommandValidator`: `When(ReturnDtoId.HasValue) → Re
|
|
|
315
438
|
- Automation'da `isQueue`/`operation` eksik ya da returnDtoId verilmiş (WF-010, block)
|
|
316
439
|
- `fields` ∩ `selectFields` çakışması (WF-012, warn) · join'li table'da boş selectFields (WF-007, warn)
|
|
317
440
|
- Join'li table step'inde kök `rule.alias` boş (WF-019, block — Postgres 42601'in sebebi)
|
|
441
|
+
- Akışta hiç okunmayan `selectFields` alanı (WF-023, warn — gereksiz taşınan sütun)
|
|
442
|
+
- Adımda ya da akışta `description` yok (WF-024, block — Studio'da adım okunamaz kalır)
|
|
318
443
|
- İlişki alanının içini okuyan ifade (WF-020, warn — join yoksa alan düz FK'dır)
|
|
319
444
|
- `set-user` adımı olmadan `user` okuyan middleware (MW-003, warn)
|
|
320
445
|
|
package/dist/server.js
CHANGED
|
@@ -5,12 +5,14 @@ import { registerDeploymentTools } from './tools/deployments.js';
|
|
|
5
5
|
import { registerDatasourceTools } from './tools/datasources.js';
|
|
6
6
|
import { registerAutomationTools } from './tools/automations.js';
|
|
7
7
|
import { registerOrganizationTools } from './tools/organizations.js';
|
|
8
|
+
import { registerExternalComponentTools } from './tools/externalComponents.js';
|
|
8
9
|
import { registerComponentTools } from './tools/components.js';
|
|
9
10
|
import { registerPlatformTools } from './tools/platformInfo.js';
|
|
10
11
|
import { registerTemplateTools } from './tools/templates.js';
|
|
11
12
|
import { registerWorkflowTools } from './tools/workflows.js';
|
|
12
13
|
import { registerLayoutTools } from './tools/layouts.js';
|
|
13
14
|
import { registerAccessTools } from './tools/access.js';
|
|
15
|
+
import { registerTranslationTools } from './tools/translations.js';
|
|
14
16
|
import { registerFlowTools } from './tools/flows.js';
|
|
15
17
|
import { registerKnowledgeResources } from './resources/knowledge.js';
|
|
16
18
|
import { registerSchemaResources } from './resources/schemas.js';
|
|
@@ -41,12 +43,14 @@ const moduleRegistrations = {
|
|
|
41
43
|
view: registerViewTools,
|
|
42
44
|
page: registerPageTools,
|
|
43
45
|
component: registerComponentTools,
|
|
46
|
+
externalComponent: registerExternalComponentTools,
|
|
44
47
|
flow: registerFlowTools,
|
|
45
48
|
layout: registerLayoutTools,
|
|
46
49
|
variable: registerVariableTools,
|
|
47
50
|
folder: registerFolderTools,
|
|
48
51
|
file: registerFileTools,
|
|
49
52
|
access: registerAccessTools,
|
|
53
|
+
translation: registerTranslationTools,
|
|
50
54
|
template: registerTemplateTools,
|
|
51
55
|
organization: registerOrganizationTools,
|
|
52
56
|
dependency: registerDependencyTools,
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { mkdir, readFile, writeFile } from 'node:fs/promises';
|
|
3
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
4
|
+
import { handle } from '../platform/responses.js';
|
|
5
|
+
import { basename, join, resolve } from 'node:path';
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
/** Platformun köprü protokolü; scaffold ve publish bu sürüme göre üretir/doğrular */
|
|
8
|
+
const PROTOCOL_VERSION = 1;
|
|
9
|
+
/** Backend `ExternalComponentRules.MaxBundleSizeBytes` ile aynı sınır */
|
|
10
|
+
const maxBundleBytes = 2 * 1024 * 1024;
|
|
11
|
+
const slugPattern = /^x-[a-z][a-z0-9-]*$/;
|
|
12
|
+
const slugParam = z.string().regex(slugPattern)
|
|
13
|
+
.describe("Harici komponent slug'ı; 'x-' ile başlamak zorunda (örn. 'x-rating-stars') — bu önek platform slug'larıyla çakışmayı imkânsız kılar");
|
|
14
|
+
const versionParam = z.string().min(1).describe("Paket sürümü, örn. '1.0.0'; aynı sürüm ikinci kez yayınlanamaz");
|
|
15
|
+
/**
|
|
16
|
+
* Doğrulama raporunu ajanın düzeltme döngüsü kurabileceği şekilde özetler: block
|
|
17
|
+
* bulguları işi durdurur, warn bulguları aktivasyonda açık onay ister.
|
|
18
|
+
*/
|
|
19
|
+
function summarizeFindings(findings) {
|
|
20
|
+
const blocking = (findings ?? []).filter(finding => finding.severity === 'block');
|
|
21
|
+
const warnings = (findings ?? []).filter(finding => finding.severity === 'warn');
|
|
22
|
+
return {
|
|
23
|
+
blocking,
|
|
24
|
+
warnings,
|
|
25
|
+
nextStep: blocking.length
|
|
26
|
+
? 'Paket reddedildi ve depolamaya yazılmadı. Yukarıdaki block bulgularını kaynakta düzeltip yeniden yayınlayın.'
|
|
27
|
+
: warnings.length
|
|
28
|
+
? "Paket taslak olarak kaydedildi. Uyarıları inceleyin; aktive etmek için external_component_activate'i acknowledgeWarnings:true ile çağırın."
|
|
29
|
+
: "Paket taslak olarak kaydedildi. Önizlemede doğruladıktan sonra external_component_activate ile yayına alın."
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
const elementTemplate = (slug, props) => `const PROPS = ${JSON.stringify(props)}
|
|
33
|
+
|
|
34
|
+
export default class ZeyrekElement extends HTMLElement {
|
|
35
|
+
#props = {}
|
|
36
|
+
#context = null
|
|
37
|
+
#machine = null
|
|
38
|
+
|
|
39
|
+
${props.map(prop => ` set ${prop}(value) {
|
|
40
|
+
this.#props.${prop} = value
|
|
41
|
+
this.#render()
|
|
42
|
+
}`).join('\n\n')}
|
|
43
|
+
|
|
44
|
+
set zxContext(value) {
|
|
45
|
+
this.#context = value
|
|
46
|
+
this.#render()
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
set zxMachine(value) {
|
|
50
|
+
this.#machine = value
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
connectedCallback() {
|
|
54
|
+
this.#render()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Platforma bir olay bildirir. setter:true, data içindeki değerin aynı adlı
|
|
59
|
+
* prop'a geri yazılmasını ister — value gibi iki yönlü davranan proplar için.
|
|
60
|
+
*/
|
|
61
|
+
emit(name, data, options = {}) {
|
|
62
|
+
this.dispatchEvent(new CustomEvent('zx-emit', { detail: { name, data, ...options } }))
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
#render() {
|
|
66
|
+
if (this.#context?.editorMode) {
|
|
67
|
+
this.textContent = '${slug}'
|
|
68
|
+
return
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
this.textContent = PROPS.map(prop => \`\${prop}: \${this.#props[prop] ?? ''}\`).join(' · ')
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
`;
|
|
75
|
+
const manifestTemplate = (slug, name, props) => JSON.stringify({
|
|
76
|
+
slug,
|
|
77
|
+
name,
|
|
78
|
+
description: `${name} — organizasyon tarafından yayınlanan komponent.`,
|
|
79
|
+
icon: 'widgets',
|
|
80
|
+
categories: ['common'],
|
|
81
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
82
|
+
definition: {
|
|
83
|
+
props: props.map(prop => ({ title: prop, name: prop, type: 'text' })),
|
|
84
|
+
events: [{ name: 'change', title: 'When value changed' }],
|
|
85
|
+
validations: [],
|
|
86
|
+
blocks: [{ breakpoint: 1024, size: { w: 3, h: 8 }, minSize: { w: 1, h: 1 }, maxSize: { w: 12, h: 5 } }],
|
|
87
|
+
defaults: {}
|
|
88
|
+
}
|
|
89
|
+
}, null, 2);
|
|
90
|
+
const readmeTemplate = (slug) => `# ${slug}
|
|
91
|
+
|
|
92
|
+
Zeyrek harici komponenti. Köprü protokolü sürümü: ${PROTOCOL_VERSION}.
|
|
93
|
+
|
|
94
|
+
## Sözleşme
|
|
95
|
+
|
|
96
|
+
Host, element'e şema prop'larını **JS property** olarak yazar (attribute değil —
|
|
97
|
+
dizi ve nesneler bozulmadan geçsin diye), yanına iki kanal koyar:
|
|
98
|
+
|
|
99
|
+
- \`zxContext\`: \`{ elementId, appearance, states, isRequired, editorMode, language }\`
|
|
100
|
+
- \`zxMachine\`: platform yüzeyi (state, değişken, dil, yetki, sibling prop'lar)
|
|
101
|
+
|
|
102
|
+
Element geriye tek bir DOM olayıyla konuşur: \`zx-emit\`, detail'i
|
|
103
|
+
\`{ name, data?, setter?, flowId?, params? }\`. \`setter: true\` verirseniz
|
|
104
|
+
\`data\` içindeki değer aynı adlı prop'a geri yazılır.
|
|
105
|
+
|
|
106
|
+
## Kurallar
|
|
107
|
+
|
|
108
|
+
- Tek \`export default\`, \`HTMLElement\` türeten bir sınıf olmalı.
|
|
109
|
+
- \`eval\`, \`new Function\`, dinamik \`import()\`, \`document.cookie\`,
|
|
110
|
+
\`window.machine\`, service worker kaydı **yasak** — yayınlama reddedilir.
|
|
111
|
+
- Ağ erişimi, doğrudan storage ve \`innerHTML\` ataması uyarı üretir; aktive
|
|
112
|
+
ederken açık onay ister.
|
|
113
|
+
- Renk, boşluk ve tipografi için \`--ng-*\` tema değişkenlerini kullanın; sabit
|
|
114
|
+
renk yazmayın.
|
|
115
|
+
|
|
116
|
+
## Yayınlama
|
|
117
|
+
|
|
118
|
+
1. \`manifest.json\` içindeki prop ve event tanımlarını gerçek komponentinize göre düzenleyin.
|
|
119
|
+
2. \`external_component_publish\` — kaydı ve paketi yükler, doğrulama raporunu döner.
|
|
120
|
+
3. \`external_component_activate\` — taslağı yayına alır.
|
|
121
|
+
`;
|
|
122
|
+
export function registerExternalComponentTools(server, platform) {
|
|
123
|
+
const { config } = platform;
|
|
124
|
+
server.registerTool('external_component_list', {
|
|
125
|
+
title: 'Yayınlanan komponentleri listele',
|
|
126
|
+
description: "Organizasyonun kendi yayınladığı komponentleri (Studio'da 'Your Components' başlığı altında görünenler) listeler: slug, sürüm, taslak mı yayında mı. Platformun hazır komponentleri için component_list kullanın.",
|
|
127
|
+
inputSchema: {
|
|
128
|
+
project: projectParam,
|
|
129
|
+
environment: environmentParam,
|
|
130
|
+
applicationId: guid
|
|
131
|
+
},
|
|
132
|
+
annotations: readOnly
|
|
133
|
+
}, handle(config, async ({ project, environment, applicationId }) => {
|
|
134
|
+
const response = await platform.tenant('GET', project, `/components/${applicationId}`, {
|
|
135
|
+
environment,
|
|
136
|
+
query: { pageIndex: 0, pageLength: 999 }
|
|
137
|
+
});
|
|
138
|
+
const components = (response?.data ?? []).filter(component => component.origin === 'external');
|
|
139
|
+
return {
|
|
140
|
+
components: components.map(component => ({
|
|
141
|
+
id: component.id,
|
|
142
|
+
slug: component.slug,
|
|
143
|
+
name: component.name,
|
|
144
|
+
bundleVersion: component.bundleVersion ?? null,
|
|
145
|
+
bundleStatus: component.bundleStatus ?? null,
|
|
146
|
+
protocolVersion: component.protocolVersion ?? null
|
|
147
|
+
})),
|
|
148
|
+
note: components.length
|
|
149
|
+
? undefined
|
|
150
|
+
: "Bu uygulamada yayınlanmış komponent yok. İhtiyacınızı karşılayan hazır bir komponent bulamadıysanız external_component_scaffold ile kendinizinkini yazmaya başlayabilirsiniz."
|
|
151
|
+
};
|
|
152
|
+
}));
|
|
153
|
+
server.registerTool('external_component_scaffold', {
|
|
154
|
+
title: 'Harici komponent iskeleti üret',
|
|
155
|
+
description: "Yerel bir dizine çalışan bir harici komponent iskeleti yazar: element.mjs (köprü protokolünü karşılayan sınıf), manifest.json (prop/event/boyut tanımı) ve README. Mevcut katalog ihtiyacınızı karşılamıyorsa buradan başlayın; ürettiği dosyaları düzenleyip external_component_publish ile yayınlarsınız. Dosyalar gateway sürecinin çalıştığı makineye yazılır, içerikleri model bağlamına girmez.",
|
|
156
|
+
inputSchema: {
|
|
157
|
+
directory: z.string().min(1).describe('İskeletin yazılacağı yerel dizin; yoksa oluşturulur'),
|
|
158
|
+
slug: slugParam,
|
|
159
|
+
name: z.string().min(1).describe("Studio toolbox'ta görünecek ad"),
|
|
160
|
+
props: z.array(z.string().min(1)).optional().describe("Komponentin alacağı prop adları; verilmezse ['value'] varsayılır. id/class/style/name gibi DOM adları kullanılamaz")
|
|
161
|
+
},
|
|
162
|
+
annotations: writes
|
|
163
|
+
}, handle(config, async ({ directory, slug, name, props }) => {
|
|
164
|
+
const propNames = props?.length ? props : ['value'];
|
|
165
|
+
const reserved = propNames.filter(prop => /^(id|class|className|style|slot|name|children|innerHTML|outerHTML|textContent)$/.test(prop) || prop.startsWith('zx') || prop.startsWith('on'));
|
|
166
|
+
if (reserved.length) {
|
|
167
|
+
throw new PlatformApiError(`external_component_scaffold: şu prop adları platform tarafından rezerve edilmiş ve yayınlama sırasında reddedilir: ${reserved.join(', ')}.`);
|
|
168
|
+
}
|
|
169
|
+
const target = resolve(directory);
|
|
170
|
+
await mkdir(target, { recursive: true });
|
|
171
|
+
const files = [
|
|
172
|
+
{ path: join(target, 'element.mjs'), content: elementTemplate(slug, propNames) },
|
|
173
|
+
{ path: join(target, 'manifest.json'), content: manifestTemplate(slug, name, propNames) },
|
|
174
|
+
{ path: join(target, 'README.md'), content: readmeTemplate(slug) }
|
|
175
|
+
];
|
|
176
|
+
for (const file of files) {
|
|
177
|
+
await writeFile(file.path, file.content, 'utf8');
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
directory: target,
|
|
181
|
+
files: files.map(file => basename(file.path)),
|
|
182
|
+
protocolVersion: PROTOCOL_VERSION,
|
|
183
|
+
nextStep: `element.mjs ve manifest.json dosyalarını komponentinize göre düzenleyin, sonra external_component_publish ile bundlePath='${join(target, 'element.mjs')}' ve manifestPath='${join(target, 'manifest.json')}' vererek yayınlayın.`
|
|
184
|
+
};
|
|
185
|
+
}));
|
|
186
|
+
server.registerTool('external_component_publish', {
|
|
187
|
+
title: 'Harici komponenti yayınla',
|
|
188
|
+
description: "Komponent kaydını ve paketini platforma yükler. Paket sunucudaki doğrulama hattından geçer (şema + güvenlik taraması); block bulgusu varsa depolamaya hiç yazılmaz ve rapor döner. Kabul edilen paket taslak (draft) olarak kaydedilir — Studio önizlemesinde görünür, yayınlanmış uygulamada görünmez; yayına almak için external_component_activate. Dosyalar gateway sürecinin okuyabildiği yerel yollardır, içerikleri model bağlamına girmez.",
|
|
189
|
+
inputSchema: {
|
|
190
|
+
project: projectParam,
|
|
191
|
+
environment: environmentParam,
|
|
192
|
+
applicationId: guid,
|
|
193
|
+
bundlePath: z.string().min(1).describe('Tek dosyalık ES module paketinin yerel yolu (.mjs)'),
|
|
194
|
+
manifestPath: z.string().min(1).describe('manifest.json yerel yolu; slug, name, definition ve protocolVersion buradan okunur'),
|
|
195
|
+
version: versionParam
|
|
196
|
+
},
|
|
197
|
+
annotations: writes
|
|
198
|
+
}, handle(config, async ({ project, environment, applicationId, bundlePath, manifestPath, version }) => {
|
|
199
|
+
const manifestFile = resolve(manifestPath);
|
|
200
|
+
const bundleFile = resolve(bundlePath);
|
|
201
|
+
let manifest;
|
|
202
|
+
try {
|
|
203
|
+
manifest = JSON.parse(await readFile(manifestFile, 'utf8'));
|
|
204
|
+
}
|
|
205
|
+
catch (error) {
|
|
206
|
+
throw new PlatformApiError(`external_component_publish: manifest okunamadı ya da geçerli JSON değil — ${manifestFile} (${error instanceof Error ? error.message : String(error)}).`);
|
|
207
|
+
}
|
|
208
|
+
const slug = typeof manifest.slug === 'string' ? manifest.slug : undefined;
|
|
209
|
+
if (!slug || !slugPattern.test(slug)) {
|
|
210
|
+
throw new PlatformApiError(`external_component_publish: manifest'teki slug 'x-' ile başlamalı ve küçük harf/rakam/tire içermeli — okunan: ${String(manifest.slug)}.`);
|
|
211
|
+
}
|
|
212
|
+
let bytes;
|
|
213
|
+
try {
|
|
214
|
+
bytes = await readFile(bundleFile);
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw new PlatformApiError(`external_component_publish: paket okunamadı — ${bundleFile} (${error instanceof Error ? error.message : String(error)}). Yol gateway sürecinin çalıştığı makineye göre çözülür.`);
|
|
218
|
+
}
|
|
219
|
+
if (bytes.byteLength > maxBundleBytes) {
|
|
220
|
+
throw new PlatformApiError(`external_component_publish: paket ${maxBundleBytes} bayt sınırını aşıyor (${bytes.byteLength} B).`);
|
|
221
|
+
}
|
|
222
|
+
const record = await platform.tenant('POST', project, `/components/${applicationId}/external`, {
|
|
223
|
+
environment,
|
|
224
|
+
body: {
|
|
225
|
+
slug,
|
|
226
|
+
name: manifest.name,
|
|
227
|
+
description: manifest.description,
|
|
228
|
+
icon: manifest.icon,
|
|
229
|
+
categories: manifest.categories ?? [],
|
|
230
|
+
definition: manifest.definition,
|
|
231
|
+
protocolVersion: manifest.protocolVersion ?? PROTOCOL_VERSION
|
|
232
|
+
}
|
|
233
|
+
});
|
|
234
|
+
const bundle = await platform.tenant('POST', project, `/components/${applicationId}/external/${slug}/bundle`, {
|
|
235
|
+
environment,
|
|
236
|
+
multipart: [
|
|
237
|
+
{ name: 'Version', value: version },
|
|
238
|
+
{ name: 'Bundle', filename: basename(bundleFile), contentType: 'text/javascript', content: bytes }
|
|
239
|
+
]
|
|
240
|
+
});
|
|
241
|
+
return {
|
|
242
|
+
component: record,
|
|
243
|
+
bundle: bundle?.data,
|
|
244
|
+
...summarizeFindings(bundle?.data?.findings)
|
|
245
|
+
};
|
|
246
|
+
}));
|
|
247
|
+
server.registerTool('external_component_activate', {
|
|
248
|
+
title: 'Paket sürümünü yayına al',
|
|
249
|
+
description: "Taslak bir paket sürümünü yayına alır; yalnızca metadata değişir, platform yeniden başlatılmaz ve açık oturumlar bozulmaz. Eski bir sürüm verilirse aynı mekanizma geri alma (rollback) olarak çalışır. Doğrulama uyarısı olan bir sürüm acknowledgeWarnings:true olmadan aktive edilemez.",
|
|
250
|
+
inputSchema: {
|
|
251
|
+
project: projectParam,
|
|
252
|
+
environment: environmentParam,
|
|
253
|
+
applicationId: guid,
|
|
254
|
+
slug: slugParam,
|
|
255
|
+
version: versionParam,
|
|
256
|
+
acknowledgeWarnings: z.boolean().optional().describe('Doğrulama uyarılarını okuyup kabul ettiğinizi bildirir; uyarısı olan sürümlerde zorunlu')
|
|
257
|
+
},
|
|
258
|
+
annotations: updates
|
|
259
|
+
}, handle(config, ({ project, environment, applicationId, slug, version, acknowledgeWarnings }) => platform.tenant('POST', project, `/components/${applicationId}/external/${slug}/activate`, {
|
|
260
|
+
environment,
|
|
261
|
+
body: { version, acknowledgeWarnings: acknowledgeWarnings === true }
|
|
262
|
+
})));
|
|
263
|
+
server.registerTool('external_component_versions', {
|
|
264
|
+
title: 'Paket sürümlerini listele',
|
|
265
|
+
description: 'Bir harici komponentin yayınlanmış paket sürümlerini, hangisinin aktif olduğunu ve her sürümün doğrulama raporunu listeler. Geri alınacak sürümü buradan seçin.',
|
|
266
|
+
inputSchema: {
|
|
267
|
+
project: projectParam,
|
|
268
|
+
environment: environmentParam,
|
|
269
|
+
applicationId: guid,
|
|
270
|
+
slug: slugParam
|
|
271
|
+
},
|
|
272
|
+
annotations: readOnly
|
|
273
|
+
}, handle(config, ({ project, environment, applicationId, slug }) => platform.tenant('GET', project, `/components/${applicationId}/external/${slug}/versions`, { environment })));
|
|
274
|
+
server.registerTool('external_component_delete', {
|
|
275
|
+
title: 'Harici komponenti sil',
|
|
276
|
+
description: 'Yayınlanan komponent kaydını siler. Sayfalarda kullanılıyorsa o sayfalar komponenti kaybeder ve yerinde bir hata kartı görünür — silmeden önce dependency_graph ile kullanımını kontrol edin.',
|
|
277
|
+
inputSchema: {
|
|
278
|
+
project: projectParam,
|
|
279
|
+
environment: environmentParam,
|
|
280
|
+
applicationId: guid,
|
|
281
|
+
id: guid.describe('external_component_list çıktısındaki komponent id')
|
|
282
|
+
},
|
|
283
|
+
annotations: destructive
|
|
284
|
+
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/components/${applicationId}/${id}`, { environment })));
|
|
285
|
+
}
|
package/dist/tools/layouts.js
CHANGED
|
@@ -1,17 +1,30 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
2
2
|
import { normalizeComponentDefaults, componentDefaultsNote, blockSizeNote, heightTypeNote, invalidScreenEventNames, invalidWidgetTypeNodes, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
3
|
-
import { fetchHost, layoutUpdateBody, schemaParam, eventsParam } from './screens.js';
|
|
3
|
+
import { fetchHost, layoutUpdateBody, schemaParam, eventsParam, modeParam, summarize, findNode } from './screens.js';
|
|
4
4
|
import { applyCatalogLayoutRules } from './components.js';
|
|
5
5
|
import { PlatformApiError } from '../platform/errors.js';
|
|
6
6
|
import { handle } from '../platform/responses.js';
|
|
7
7
|
import { z } from 'zod';
|
|
8
8
|
const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür.");
|
|
9
|
+
function countSlots(nodes) {
|
|
10
|
+
if (!Array.isArray(nodes))
|
|
11
|
+
return 0;
|
|
12
|
+
return nodes.reduce((total, node) => {
|
|
13
|
+
if (!node || typeof node !== 'object')
|
|
14
|
+
return total;
|
|
15
|
+
const record = node;
|
|
16
|
+
return total + (record.type === 'slot' ? 1 : 0) + countSlots(record.children);
|
|
17
|
+
}, 0);
|
|
18
|
+
}
|
|
9
19
|
function slotWarning(views) {
|
|
10
|
-
const
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
20
|
+
const slots = countSlots(views);
|
|
21
|
+
if (slots === 0) {
|
|
22
|
+
return ["LAY-002: layout'ta 'slot' tipli komponent YOK — Studio ve runtime, sayfa şemasını yalnız layout'taki slot'un içine yerleştirir (page.mappers: layout varsa render şeması layout views'üdür); bu layout'a bağlanan her sayfanın içeriği görünmez olur. views'e bir { type: 'slot' } komponenti ekleyin ve page_state.layoutSlots ile doğrulayın."];
|
|
23
|
+
}
|
|
24
|
+
if (slots > 1) {
|
|
25
|
+
return [`LAY-006: layout'ta ${slots} adet 'slot' var — enjeksiyon HER slot'a AYRI AYRI kopyalar (layout.functions.ts injectPageSchemaIntoSlots ağacı dolaşır ve type==='slot' gören her düğümün children'ına sayfa şemasının klonunu yazar; önizlemede page.mappers.mapPageLayoutToPreview aynısını yapar). Sonuç: sayfa birden çok kez çizilir ve aynı elementId'ler tekrarlanır. Tek slot bırakın.`];
|
|
26
|
+
}
|
|
27
|
+
return [];
|
|
15
28
|
}
|
|
16
29
|
export function registerLayoutTools(server, platform) {
|
|
17
30
|
const config = platform.config;
|
|
@@ -27,6 +40,35 @@ export function registerLayoutTools(server, platform) {
|
|
|
27
40
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
|
|
28
41
|
annotations: readOnly
|
|
29
42
|
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
|
|
43
|
+
server.registerTool('layout_schema_get', {
|
|
44
|
+
title: "Layout komponent ağacını oku",
|
|
45
|
+
description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. DİKKAT: layout'ta micro-op hattı YOKTUR (backend'de PATCH /layouts/.../schema ucu yok), yani buradan okunan updatedAt bir expectedUpdatedAt kontrolünde kullanılamaz; değişiklik layout_update ile tam gövde olarak yazılır.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
project: projectParam,
|
|
48
|
+
environment: environmentParam,
|
|
49
|
+
applicationId: guid,
|
|
50
|
+
id: guid,
|
|
51
|
+
mode: modeParam,
|
|
52
|
+
componentId: z.string().optional().describe("subtree kipinde kök alınacak komponentin elementId'si")
|
|
53
|
+
},
|
|
54
|
+
annotations: readOnly
|
|
55
|
+
}, handle(config, async ({ project, environment, applicationId, id, mode, componentId }) => {
|
|
56
|
+
const layout = await fetchHost(platform, 'layout', project, environment, applicationId, id);
|
|
57
|
+
const audit = layout.audit;
|
|
58
|
+
const updatedAt = audit?.updatedAt ?? audit?.createdAt;
|
|
59
|
+
const views = (layout.views ?? []);
|
|
60
|
+
if (mode === 'full')
|
|
61
|
+
return { updatedAt, views };
|
|
62
|
+
if (mode === 'subtree') {
|
|
63
|
+
if (!componentId)
|
|
64
|
+
throw new PlatformApiError("mode='subtree' için componentId zorunludur.");
|
|
65
|
+
const subtree = findNode(views, componentId);
|
|
66
|
+
if (!subtree)
|
|
67
|
+
throw new PlatformApiError(`Layout'ta '${componentId}' id'li bir komponent yok; layout_schema_get'i summary kipinde çağırıp hedefi doğrulayın.`);
|
|
68
|
+
return { updatedAt, views: subtree };
|
|
69
|
+
}
|
|
70
|
+
return { updatedAt, views: views.map(node => summarize(node)) };
|
|
71
|
+
}));
|
|
30
72
|
server.registerTool('layout_create', {
|
|
31
73
|
title: 'Layout oluştur',
|
|
32
74
|
description: "Yeni layout oluşturur. Şablon için mevcut bir layout'u layout_get ile alın; slot kurgusunu page_state çıktısındaki layoutSlots ile doğrulayın.",
|