@appaflytech/zeyrek-ai-mcp 0.0.4 → 0.0.6
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 +8 -3
- package/.env.production.example +2 -0
- package/README.md +229 -193
- package/dist/config.js +20 -2
- package/dist/knowledge/backendConstraints.js +897 -0
- package/dist/knowledge/componentCatalog.js +196 -42
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +449 -16
- package/dist/knowledge/outlines.js +136 -12
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +618 -43
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/errors.js +46 -4
- package/dist/platform/http.js +33 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +9 -2
- package/dist/platform/screenshot.js +527 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +2 -2
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +3 -2
- package/dist/resources/guides/component.md +69 -8
- package/dist/resources/guides/entity.md +404 -16
- package/dist/resources/guides/expressions.md +44 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/getting-started.md +77 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/microservice.md +226 -13
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/page.md +383 -46
- package/dist/resources/guides/translation.md +126 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +198 -27
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateMicroserviceCommand.json +26 -2
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +95 -14
- package/dist/resources/schemas/GetRowsQuery.json +3 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +16 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +34 -9
- package/dist/resources/schemas/StepDto.json +23 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +33 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +49 -10
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +28 -3
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +95 -14
- package/dist/server.js +4 -0
- package/dist/tools/access.js +3 -3
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +63 -30
- package/dist/tools/common.js +39 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +20 -6
- package/dist/tools/flows.js +110 -11
- package/dist/tools/folders.js +7 -2
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/microservices.js +229 -38
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +365 -100
- package/dist/tools/platformInfo.js +152 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/rows.js +159 -45
- package/dist/tools/screenWrite.js +59 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +33 -16
- package/package.json +6 -3
package/dist/tools/rows.js
CHANGED
|
@@ -1,7 +1,38 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { findLocalizationCompanion, localizationRows, platformIgnoredLocalizations } from '../knowledge/localization.js';
|
|
2
|
+
import { guid, entityRef, resolveEntityId, withResolved, projectParam, environmentParam, languageParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
|
|
3
|
+
import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
4
|
+
import { withNormalization } from '../knowledge/normalize.js';
|
|
5
|
+
import { RefResolver } from '../platform/refs.js';
|
|
6
|
+
import { attachFindings, evaluatePolicy, enforcePolicy } from '../knowledge/policy.js';
|
|
7
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
8
|
+
import { languageHeader } from '../knowledge/languageCodes.js';
|
|
2
9
|
import { handle } from '../platform/responses.js';
|
|
3
10
|
import { z } from 'zod';
|
|
4
11
|
const rowValues = z.record(z.unknown()).describe("Alan SLUG'ı → değer sözlüğü; anahtarlar entity_get çıktısındaki field SLUG'larıdır (name değil — name kullanmak 'Please enter a valid value' hatası verir)");
|
|
12
|
+
/**
|
|
13
|
+
* `row_*` DBMS düzeyi bir satır yüzeyidir; var olan satır kümesinden TÜRETİLEN satırları
|
|
14
|
+
* (geçmiş, hareket, log, junction, backfill) gövdede taşımak onun işi değildir — 2026-09-05'te
|
|
15
|
+
* 802, 2026-09-06'da 552 satır bu yüzden elle yazıldı (`B-34`, `B-50`) ve ikinci koşunun 65
|
|
16
|
+
* dakikasının ~21'i buraya gitti. Eşik büyük tutuldu: bulgu her toplu yazmada bağırırsa koşunun
|
|
17
|
+
* sinyal oranını kendisi düşürür, bu yüzden yalnız "elle taşınamayacak kadar büyük" dizilerde
|
|
18
|
+
* konuşur ve cümlesi KOŞULLUDUR — dışarıdan gelen veriyi (içe aktarma, kullanıcı girdisi)
|
|
19
|
+
* yazan çağıranı suçlamaz.
|
|
20
|
+
*/
|
|
21
|
+
const DERIVED_ROW_THRESHOLD = 50;
|
|
22
|
+
function derivedRowFindings(rows) {
|
|
23
|
+
if (!rows || rows.length < DERIVED_ROW_THRESHOLD)
|
|
24
|
+
return [];
|
|
25
|
+
return [{
|
|
26
|
+
ruleId: 'ROW-001',
|
|
27
|
+
severity: 'warn',
|
|
28
|
+
message: `Gövdede ${rows.length} satır var. Bu satırlar VAR OLAN satırlardan türetiliyorsa (geçmiş/hareket/log/junction tablosu, backfill) onları gövdede taşımayın: tek bir kayıtlı 'INSERT … SELECT' sorgusu aynı işi TEK çağrıda yapar ve hiçbir id elle taşınmaz (ölçüldü 2026-09-07). Satırlar dışarıdan geliyorsa (içe aktarma, kullanıcı verisi) bu çağrı doğru yoldur. Ham SQL yolu UYGULAMA katmanını atlar (automation tetiklenmez, incremental sayaç ilerlemez, localization companion yazılmaz) ama veritabanı kısıtları — NOT NULL, FK, UNIQUE, enum CHECK — aynen uygulanır. Reçete, merdiven kalıbı ve anahtar sözleşmesi: platform_guide('entity') §'Toplu ve türetilmiş satır'.`,
|
|
29
|
+
guide: 'ngapps://guides/entity'
|
|
30
|
+
}];
|
|
31
|
+
}
|
|
32
|
+
const includeTranslationsParam = z.boolean().optional()
|
|
33
|
+
.describe("Çok dilli alanların TÜM dillerini de getirir: satıra 'localizations: { <dil>: { <alan>: <değer> } }' eklenir. Verilmezse satır yalnız istek dilinde düzleştirilmiş gelir (çevirisi olmayan alan varsayılan dile düşer).");
|
|
34
|
+
const localizationsParam = z.record(z.record(z.unknown())).optional()
|
|
35
|
+
.describe("Dil kodu → çok dilli alan değerleri; ör. { tr: { name: 'Yıllık izin' }, en: { name: 'Annual leave' } }. Verilen her dil upsert edilir, verilmeyen dile dokunulmaz. Dil kodu projenin dil listesinde değilse platform 400 döner ve ana satır da yazılmaz.");
|
|
5
36
|
export function registerRowTools(server, platform) {
|
|
6
37
|
const config = platform.config;
|
|
7
38
|
server.registerTool('row_list', {
|
|
@@ -10,106 +41,189 @@ export function registerRowTools(server, platform) {
|
|
|
10
41
|
inputSchema: {
|
|
11
42
|
project: projectParam,
|
|
12
43
|
environment: environmentParam,
|
|
13
|
-
entityId:
|
|
44
|
+
entityId: entityRef,
|
|
14
45
|
selectedFields: z.array(z.string()).optional().describe('Dönen satırlar bu alan adlarına indirgenir, id her zaman dahildir; platform tam satırı hesapladığı için kırpma gateway tarafında yapılır'),
|
|
15
46
|
filters: z.array(z.record(z.unknown())).optional().describe('Platform Filter dizisi; biçim için row_list\'i filtresiz çağırıp Studio\'daki bir filtreyi referans alın'),
|
|
47
|
+
language: languageParam,
|
|
48
|
+
includeTranslations: includeTranslationsParam,
|
|
16
49
|
...pagingParams
|
|
17
50
|
},
|
|
18
51
|
annotations: readOnly
|
|
19
|
-
}, handle(config, async ({ project, environment, entityId, ...body }) => {
|
|
20
|
-
const
|
|
52
|
+
}, handle(config, async ({ project, environment, entityId, language, ...body }) => {
|
|
53
|
+
const notes = [];
|
|
54
|
+
const headers = languageHeader(language, notes);
|
|
55
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
56
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
57
|
+
const payload = await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/list`, { environment, body, headers });
|
|
21
58
|
const wanted = body.selectedFields ?? [];
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
59
|
+
const rows = payload && typeof payload === 'object' && !Array.isArray(payload) ? payload.data : undefined;
|
|
60
|
+
if (!wanted.length || !Array.isArray(rows))
|
|
61
|
+
return withNormalization(withResolved(payload, resolver.resolved), notes);
|
|
62
|
+
/**
|
|
63
|
+
* 'localizations' keep listesine girmezse includeTranslations'ın getirdiği çeviriler tam da
|
|
64
|
+
* onları isteyen çağrıda kırpılırdı: iki parametre birlikte verildiğinde sessizce boşa çıkar.
|
|
65
|
+
*/
|
|
66
|
+
const keep = new Set([...wanted.map(field => field.toLowerCase()), 'id', ...(body.includeTranslations ? ['localizations'] : [])]);
|
|
67
|
+
return withNormalization(withResolved({
|
|
29
68
|
...payload,
|
|
30
69
|
data: rows.map(row => row && typeof row === 'object' && !Array.isArray(row)
|
|
31
70
|
? Object.fromEntries(Object.entries(row).filter(([key]) => keep.has(key.toLowerCase())))
|
|
32
71
|
: row)
|
|
33
|
-
};
|
|
72
|
+
}, resolver.resolved), notes);
|
|
34
73
|
}));
|
|
35
74
|
server.registerTool('row_get', {
|
|
36
75
|
title: 'Satır getir',
|
|
37
76
|
description: 'Tek satırı id ile getirir.',
|
|
38
|
-
inputSchema: { project: projectParam, environment: environmentParam, entityId:
|
|
77
|
+
inputSchema: { project: projectParam, environment: environmentParam, entityId: entityRef, id: z.string().min(1), language: languageParam, includeTranslations: includeTranslationsParam },
|
|
39
78
|
annotations: readOnly
|
|
40
|
-
}, handle(config, ({ project, environment, entityId, id
|
|
79
|
+
}, handle(config, async ({ project, environment, entityId, id, language, includeTranslations }) => {
|
|
80
|
+
const notes = [];
|
|
81
|
+
const headers = languageHeader(language, notes);
|
|
82
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
83
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
84
|
+
const payload = await platform.tenant('GET', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(id)}`, { environment, headers, ...(includeTranslations ? { query: { includeTranslations: true } } : {}) });
|
|
85
|
+
return withNormalization(withResolved(payload, resolver.resolved), notes);
|
|
86
|
+
}));
|
|
41
87
|
server.registerTool('row_create', {
|
|
42
88
|
title: 'Satır ekle',
|
|
43
|
-
description: "Tek satır için 'row', çoklu ekleme için 'rows' verin (ikisi birden verilmez).",
|
|
89
|
+
description: "Tek satır için 'row', çoklu ekleme için 'rows' verin (ikisi birden verilmez). Çoklu dilli entity'de 'localizations: { tr: {…}, en: {…} }' ile dil satırlarını da aynı çağrıda yazar: companion entity'yi ve FK'yı gateway bulur (yalnız 'row' ile). 'rows' TEK TRANSACTION'da yazılır: bir satır doğrulamadan düşerse HİÇBİRİ yazılmaz ve hata hangi satır indeksinin düştüğünü söyler. Automation'lar satır BAŞINA tetiklenir — sync (isQueue:true) automation'lar aynı transaction'da satır satır koşar, biri düşerse toplu yazma geri alınır. Tek çağrıda en çok 1000 satır. SINIR: row_* bir veritabanı yönetim yüzeyidir — satırları GÖVDEDE taşır. VAR OLAN satırlardan türetilen bir küme (geçmiş/hareket/log/junction, backfill) bu yola AİT DEĞİLDİR; onu tek bir kayıtlı 'INSERT … SELECT' sorgusu + query kaynaklı uç yazar, reçete platform_guide('entity') §'Toplu ve türetilmiş satır'. Toplu ucun id döndürmesine GÜVENMEYİN: 2026-09-07 ölçümünde boş gövde döndü (2026-09-05'te anahtar listesi dönüyordu, fark B-63) — id gerekiyorsa ardından row_list çekin. TOPLU eklemede dil değerlerini satırın KENDİ içine koyun ('rows: [{ kod: 'A1', localizations: { tr: {…} } }]') — üst düzey 'localizations' toplu uçta hangi satıra ait olduğunu söyleyemez. 'row' içine düz çok dilli slug yazarsanız değer İSTEK DİLİ için kaydedilir — dili 'language' ile seçin.",
|
|
44
90
|
inputSchema: {
|
|
45
91
|
project: projectParam,
|
|
46
92
|
environment: environmentParam,
|
|
47
|
-
entityId:
|
|
93
|
+
entityId: entityRef,
|
|
48
94
|
row: rowValues.optional(),
|
|
49
|
-
rows: z.array(z.record(z.unknown())).optional()
|
|
95
|
+
rows: z.array(z.record(z.unknown())).optional(),
|
|
96
|
+
localizations: localizationsParam,
|
|
97
|
+
language: languageParam,
|
|
98
|
+
rowContext: z.record(z.unknown()).optional()
|
|
99
|
+
.describe("Satır olayının DIŞINDAN automation'a geçirilecek değerler. Tetiklenen automation bunları ifadelerinde `rowContext.<anahtar>` ile okur — zaman damgası, göç kaynağı, toplu iş kimliği gibi. Tarihsel (geçmiş tarihli) veri üretmenin yolu budur: automation `UtcNow()` yerine `rowContext.occurredAt`'i yazabilir. Verilmezse automation bağlamı değişmez.")
|
|
50
100
|
},
|
|
51
101
|
annotations: writes
|
|
52
|
-
}, handle(config, ({ project, environment, entityId, row, rows }) => {
|
|
102
|
+
}, handle(config, async ({ project, environment, entityId, row, rows, localizations, rowContext, language }) => {
|
|
53
103
|
if (row && rows)
|
|
54
104
|
throw new Error("'row' ve 'rows' birlikte verilemez.");
|
|
55
|
-
if (
|
|
56
|
-
|
|
57
|
-
if (row)
|
|
58
|
-
|
|
59
|
-
|
|
105
|
+
if (localizations && !row)
|
|
106
|
+
throw new Error("Üst düzey 'localizations' yalnız 'row' ile verilir: toplu uçta hangi satıra ait olduğu belirsizdir. Toplu eklemede dil değerlerini satırın kendi içine koyun — rows: [{ …, localizations: { tr: {…} } }].");
|
|
107
|
+
if (!row && !rows)
|
|
108
|
+
throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
|
|
109
|
+
const notes = [];
|
|
110
|
+
const headers = languageHeader(language, notes);
|
|
111
|
+
const wantedLocalizations = localizations && Object.keys(localizations).length > 0 ? localizations : undefined;
|
|
112
|
+
const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', rows ? { rows } : { row }, { operation: 'create' }));
|
|
113
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
114
|
+
if (rows) {
|
|
115
|
+
const bulkEntityId = await resolveEntityId(resolver, entityId);
|
|
116
|
+
const bulkBody = { rows, ...(rowContext ? { rowContext } : {}) };
|
|
117
|
+
const bulkDrift = unmappedMembers('CreateBulkRowCommand', bulkBody);
|
|
118
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('POST', project, `/entities/${bulkEntityId}/row/bulk`, { environment, headers, body: bulkBody }), resolver.resolved), notes), bulkDrift), [...shapeFindings, ...derivedRowFindings(rows)]);
|
|
119
|
+
}
|
|
120
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
121
|
+
const body = { row, ...(wantedLocalizations ? { localizations: wantedLocalizations } : {}), ...(rowContext ? { rowContext } : {}) };
|
|
122
|
+
const drift = unmappedMembers('CreateRowCommand', body);
|
|
123
|
+
const response = await platform.tenantWithHeaders('POST', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body });
|
|
124
|
+
const created = response.body;
|
|
125
|
+
if (!wantedLocalizations || !platformIgnoredLocalizations(response.headers)) {
|
|
126
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved(created, resolver.resolved), notes), drift), shapeFindings);
|
|
127
|
+
}
|
|
128
|
+
notes.push("platform 'localizations' alanını tanımıyor (X-Unmapped-Members); dil satırlarını gateway companion entity'ye yazdı.");
|
|
129
|
+
const entity = await resolver.entity(entityId);
|
|
130
|
+
let mainRowId = created.data?.id;
|
|
131
|
+
try {
|
|
132
|
+
// Satır sözlüğü alan SLUG'larıyla anahtarlanır ve PK slug'ı 'id' olmak ZORUNDA değildir
|
|
133
|
+
// (CreateRowCommandHandler `entityDto.PrimaryKey()` kullanır). 'id' yoksa gerçek PK'yı
|
|
134
|
+
// bulmadan yazmak FK'sı NULL dil satırları üretir ve bunu başarı diye raporlardı.
|
|
135
|
+
if (mainRowId === undefined || mainRowId === null) {
|
|
136
|
+
const primaryKey = (await resolver.entityFields(entity)).find(field => field.primaryKey);
|
|
137
|
+
mainRowId = primaryKey ? created.data?.[primaryKey.slug] : undefined;
|
|
138
|
+
}
|
|
139
|
+
if (mainRowId === undefined || mainRowId === null) {
|
|
140
|
+
throw new Error("Ana satır yazıldı ama yanıtta birincil anahtar okunamadı; dil satırları FK'sız yazılmadı (FK'sı boş satır sessiz yanlış olurdu).");
|
|
141
|
+
}
|
|
142
|
+
const companion = await findLocalizationCompanion(resolver, entity);
|
|
143
|
+
const localizationBatch = localizationRows(companion, mainRowId, wantedLocalizations);
|
|
144
|
+
await platform.tenant('POST', project, `/entities/${companion.entityId}/row/bulk`, { environment, body: { rows: localizationBatch } });
|
|
145
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved({ ...created, localizations: { entityId: companion.entityId, languages: Object.keys(wantedLocalizations), count: localizationBatch.length } }, resolver.resolved), notes), drift), shapeFindings);
|
|
146
|
+
}
|
|
147
|
+
catch (error) {
|
|
148
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
149
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved({ ...created, localizationsFailed: { mainRowId, error: message, note: 'Ana satır YAZILDI, dil satırları yazılamadı; gateway geri alamaz. Ya companion entity\'ye row_create ile elle yazın ya da ana satırı row_delete ile silip tekrar deneyin.' } }, resolver.resolved), notes), drift), shapeFindings);
|
|
150
|
+
}
|
|
60
151
|
}));
|
|
61
152
|
server.registerTool('row_update', {
|
|
62
153
|
title: 'Satır güncelle',
|
|
63
|
-
description: "Tek satır için 'row', çoklu güncelleme için 'rows' verin; satır id alanı gövdede olmalıdır.",
|
|
154
|
+
description: "Tek satır için 'row', çoklu güncelleme için 'rows' verin; satır id alanı gövdede olmalıdır. 'rows' TEK TRANSACTION'da yazılır: bir satır düşerse HİÇBİRİ yazılmaz ve hata hangi satır indeksinin düştüğünü söyler. Automation'lar satır BAŞINA tetiklenir — sync (isQueue:true) automation'lar aynı transaction'da satır satır koşar. Tek çağrıda en çok 1000 satır. Aynı sınır row_create'te olduğu gibi geçerlidir: var olan satırlardan HESAPLANAN toplu güncelleme (durum/toplam/damga backfill'i) kayıtlı bir 'UPDATE … FROM' sorgusuna aittir — platform_guide('entity') §'Toplu ve türetilmiş satır'. Çeviri güncellemek için 'localizations: { tr: {…}, en: {…} }' verin — ana satır hiç değişmese de yalnız çeviriler yazılabilir. TOPLU güncellemede aynı nesne satırın KENDİ içine konur ('rows: [{ id: 1, localizations: { tr: {…} } }]'); 'row' içindeki düz çok dilli slug İSTEK DİLİNE yazılır.",
|
|
64
155
|
inputSchema: {
|
|
65
156
|
project: projectParam,
|
|
66
157
|
environment: environmentParam,
|
|
67
|
-
entityId:
|
|
158
|
+
entityId: entityRef,
|
|
68
159
|
row: rowValues.optional(),
|
|
69
|
-
rows: z.array(z.record(z.unknown())).optional()
|
|
160
|
+
rows: z.array(z.record(z.unknown())).optional(),
|
|
161
|
+
localizations: localizationsParam,
|
|
162
|
+
language: languageParam,
|
|
163
|
+
rowContext: z.record(z.unknown()).optional()
|
|
164
|
+
.describe("Satır olayının DIŞINDAN automation'a geçirilecek değerler. Tetiklenen automation bunları ifadelerinde `rowContext.<anahtar>` ile okur — zaman damgası, göç kaynağı, toplu iş kimliği gibi. Tarihsel (geçmiş tarihli) veri üretmenin yolu budur: automation `UtcNow()` yerine `rowContext.occurredAt`'i yazabilir. Verilmezse automation bağlamı değişmez.")
|
|
70
165
|
},
|
|
71
166
|
annotations: updates
|
|
72
|
-
}, handle(config, ({ project, environment, entityId, row, rows }) => {
|
|
167
|
+
}, handle(config, async ({ project, environment, entityId, row, rows, localizations, rowContext, language }) => {
|
|
73
168
|
if (row && rows)
|
|
74
169
|
throw new Error("'row' ve 'rows' birlikte verilemez.");
|
|
75
|
-
if (
|
|
76
|
-
|
|
77
|
-
if (row)
|
|
78
|
-
|
|
79
|
-
|
|
170
|
+
if (localizations && !row)
|
|
171
|
+
throw new Error("Üst düzey 'localizations' yalnız 'row' ile verilir: toplu uçta hangi satıra ait olduğu belirsizdir. Toplu güncellemede dil değerlerini satırın kendi içine koyun — rows: [{ id, localizations: { tr: {…} } }].");
|
|
172
|
+
if (!row && !rows)
|
|
173
|
+
throw new Error("'row' ya da 'rows' parametrelerinden biri zorunlu.");
|
|
174
|
+
const shapeFindings = enforcePolicy(evaluatePolicy(config, policyRules, 'row', rows ? { rows } : { row }, { operation: 'update' }));
|
|
175
|
+
const notes = [];
|
|
176
|
+
const headers = languageHeader(language, notes);
|
|
177
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
178
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
179
|
+
if (rows) {
|
|
180
|
+
const bulkBody = { rows, ...(rowContext ? { rowContext } : {}) };
|
|
181
|
+
const bulkDrift = unmappedMembers('UpdateBulkRowCommand', bulkBody);
|
|
182
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row/bulk`, { environment, headers, body: bulkBody }), resolver.resolved), notes), bulkDrift), [...shapeFindings, ...derivedRowFindings(rows)]);
|
|
183
|
+
}
|
|
184
|
+
const body = { row, ...(localizations ? { localizations } : {}), ...(rowContext ? { rowContext } : {}) };
|
|
185
|
+
const drift = unmappedMembers('UpdateRowCommand', body);
|
|
186
|
+
return attachFindings(withUnmappedMembers(withNormalization(withResolved(await platform.tenant('PUT', project, `/entities/${resolvedEntityId}/row`, { environment, headers, body }), resolver.resolved), notes), drift), shapeFindings);
|
|
80
187
|
}));
|
|
81
188
|
server.registerTool('row_delete', {
|
|
82
189
|
title: 'Satır sil',
|
|
83
|
-
description: "Tek satır için 'id', çoklu silme için 'ids' verin.",
|
|
190
|
+
description: "Tek satır için 'id', çoklu silme için 'ids' verin. Çok dilli entity'de dil satırlarını platform ana satırdan ÖNCE kendisi siler; eski platform sürümünde yabancı anahtar hatası dönerse önce companion entity'nin satırlarını silin.",
|
|
84
191
|
inputSchema: {
|
|
85
192
|
project: projectParam,
|
|
86
193
|
environment: environmentParam,
|
|
87
|
-
entityId:
|
|
194
|
+
entityId: entityRef,
|
|
88
195
|
id: z.string().optional(),
|
|
89
196
|
ids: z.array(z.union([z.string(), z.number()])).optional()
|
|
90
197
|
},
|
|
91
198
|
annotations: destructive
|
|
92
|
-
}, handle(config, ({ project, environment, entityId, id, ids }) => {
|
|
199
|
+
}, handle(config, async ({ project, environment, entityId, id, ids }) => {
|
|
93
200
|
if (id && ids)
|
|
94
201
|
throw new Error("'id' ve 'ids' birlikte verilemez.");
|
|
202
|
+
if (!id && !ids)
|
|
203
|
+
throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
|
|
204
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
205
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
95
206
|
if (ids)
|
|
96
|
-
return platform.tenant('POST', project, `/entities/${
|
|
97
|
-
|
|
98
|
-
return platform.tenant('DELETE', project, `/entities/${entityId}/row/${encodeURIComponent(id)}`, { environment });
|
|
99
|
-
throw new Error("'id' ya da 'ids' parametrelerinden biri zorunlu.");
|
|
207
|
+
return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/bulk/delete`, { environment, body: { rowIds: ids } }), resolver.resolved);
|
|
208
|
+
return withResolved(await platform.tenant('DELETE', project, `/entities/${resolvedEntityId}/row/${encodeURIComponent(String(id))}`, { environment }), resolver.resolved);
|
|
100
209
|
}));
|
|
101
210
|
server.registerTool('row_next_code', {
|
|
102
211
|
title: 'Sıradaki kodu üret',
|
|
103
|
-
description:
|
|
212
|
+
description: "Incremental tipli alan için sıradaki kodu döner ve sayacı İLERLETMEZ (persist:false) — kodun nasıl görüneceğini kaydetmeden görmek için. Gövde row_create'in Incremental alanına yazdığınız nesneyle aynıdır: { prefix, separator, counter, digitCount }. 'counter' ve 'digitCount' platform tarafında ZORUNLUDUR (GetNextIncrementalCodeQueryValidator: NotEmpty) — eksik verilirse istek 400 döner, bu yüzden gateway de ikisini zorunlu tutar.",
|
|
104
213
|
inputSchema: {
|
|
105
214
|
project: projectParam,
|
|
106
215
|
environment: environmentParam,
|
|
107
|
-
entityId:
|
|
216
|
+
entityId: entityRef,
|
|
108
217
|
fieldId: guid,
|
|
109
|
-
prefix: z.string().optional(),
|
|
110
|
-
separator: z.string().optional(),
|
|
111
|
-
|
|
218
|
+
prefix: z.string().optional().describe("Kodun önüne gelen sabit ('EMP')"),
|
|
219
|
+
separator: z.string().optional().describe("Önek ile sayaç arasındaki ayraç ('-'); verilmezse boş"),
|
|
220
|
+
counter: z.number().int().min(1).describe('Sayacın BAŞLANGIÇ değeri — platform zorunlu tutar (NotEmpty, yani 0 kabul edilmez). Alanın kayıtlı sayacını bilmiyorsanız entity_get ile alanın tanımına bakın.'),
|
|
221
|
+
digitCount: z.number().int().min(1).describe("Sayacın basamak sayısı (4 → '0006'); platform zorunlu tutar (NotEmpty)")
|
|
112
222
|
},
|
|
113
223
|
annotations: readOnly
|
|
114
|
-
}, handle(config, ({ project, environment, entityId, ...body }) =>
|
|
224
|
+
}, handle(config, async ({ project, environment, entityId, ...body }) => {
|
|
225
|
+
const resolver = new RefResolver(platform, project, environment);
|
|
226
|
+
const resolvedEntityId = await resolveEntityId(resolver, entityId);
|
|
227
|
+
return withResolved(await platform.tenant('POST', project, `/entities/${resolvedEntityId}/row/next-code`, { environment, body }), resolver.resolved);
|
|
228
|
+
}));
|
|
115
229
|
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { invalidScreenEventNames, invalidWidgetTypeNodes, nestedRepeaterNodes, nestedRepeaterMessage, blockSizeNote, heightTypeNote, sizeStringNote } from '../knowledge/normalize.js';
|
|
2
|
+
import { applyCatalogLayoutRules } from './components.js';
|
|
3
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
4
|
+
const GUARD_CODES = {
|
|
5
|
+
page: { events: 'PAGE-011', widgets: 'PAGE-012', noun: 'sayfa', tree: 'schema' },
|
|
6
|
+
layout: { events: 'LAY-004', widgets: 'LAY-005', noun: 'layout', tree: 'views' }
|
|
7
|
+
};
|
|
8
|
+
/** JS tanımlayıcısı olmayan `name` alanlarını toplar; ad `const <ad> = Components["<ad>"]` üretimine girer. */
|
|
9
|
+
export function invalidComponentNames(nodes) {
|
|
10
|
+
const found = [];
|
|
11
|
+
for (const node of nodes ?? []) {
|
|
12
|
+
const name = node.name;
|
|
13
|
+
if (typeof name === 'string' && name.length > 0 && !/^[A-Za-z_$][A-Za-z0-9_$]*$/.test(name.replace(/-/g, ''))) {
|
|
14
|
+
found.push(`'${name}' (${node.elementId ?? '?'})`);
|
|
15
|
+
}
|
|
16
|
+
found.push(...invalidComponentNames(node.children));
|
|
17
|
+
}
|
|
18
|
+
return found;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Sayfa ve layout yazma yollarının ORTAK kapısı: olay adı (PAGE-011 · LAY-004), widget düğümü
|
|
22
|
+
* (PAGE-012 · LAY-005), komponent adı (PAGE-009) ve iç içe repeater (PAGE-013) denetimleri —
|
|
23
|
+
* hepsi istek gitmeden 400.
|
|
24
|
+
*
|
|
25
|
+
* PAGE-009 ve PAGE-013'ün layout ayağı 2026-09-09'da eklendi: ikisi de yalnız sayfa yollarında
|
|
26
|
+
* koşuyordu, oysa layout `views`'ü aynı runtime'dan geçer — bozuk ad o layout'a bağlı HER
|
|
27
|
+
* sayfanın ifadelerini öldürür, iç içe repeater'ın geri yazma kırılması ise yapısaldır ve
|
|
28
|
+
* barındıranın sayfa mı layout mu olduğuna bakmaz.
|
|
29
|
+
*/
|
|
30
|
+
export function assertScreenWriteGuards(kind, screen) {
|
|
31
|
+
const codes = GUARD_CODES[kind];
|
|
32
|
+
const badEvents = invalidScreenEventNames(screen.events, codes.events);
|
|
33
|
+
if (badEvents.length > 0)
|
|
34
|
+
throw new PlatformApiError(`Geçersiz ${codes.noun} olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
|
|
35
|
+
const badWidgets = invalidWidgetTypeNodes(screen.schema, codes.widgets);
|
|
36
|
+
if (badWidgets.length > 0)
|
|
37
|
+
throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
|
|
38
|
+
const badNames = invalidComponentNames(screen.schema);
|
|
39
|
+
if (badNames.length > 0) {
|
|
40
|
+
throw new PlatformApiError(`PAGE-009 — komponent adı JS tanımlayıcısı değil (istek gönderilmedi): ${badNames.join(' · ')}. `
|
|
41
|
+
+ 'Runtime her komponent için `const <ad> = Components["<ad>"]` üretir; boşluklu ya da geçersiz bir ad '
|
|
42
|
+
+ 'SAYFADAKİ TÜM ifadeleri "Missing initializer in const declaration" ile öldürür (layout adları her sayfaya sızar). '
|
|
43
|
+
+ `name alanını elementId ile aynı yaz; okunabilir Türkçe başlık için props.title / props.text / props.label kullan (${codes.noun} ${codes.tree}).`, 400);
|
|
44
|
+
}
|
|
45
|
+
const nested = nestedRepeaterNodes(screen.schema);
|
|
46
|
+
if (nested.length > 0)
|
|
47
|
+
throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Katalog kurallarını (blok sınırı · auto-height · boyut string'i) ağaca uygular ve
|
|
51
|
+
* `withNormalization` kuyruğuna giren üç notu döndürür. Boş/verilmemiş ağaçta katalog
|
|
52
|
+
* okunmaz — uygulanacak düğüm yoktur, notlar da boş çıkardı.
|
|
53
|
+
*/
|
|
54
|
+
export async function screenCatalogNotes(platform, project, environment, applicationId, nodes) {
|
|
55
|
+
if (!Array.isArray(nodes) || nodes.length === 0)
|
|
56
|
+
return [];
|
|
57
|
+
const catalog = await applyCatalogLayoutRules(platform, project, environment, applicationId, nodes);
|
|
58
|
+
return [...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...sizeStringNote(catalog.sizes)];
|
|
59
|
+
}
|
package/dist/tools/screens.js
CHANGED
|
@@ -31,6 +31,33 @@ export function summarize(node, styleSource) {
|
|
|
31
31
|
}
|
|
32
32
|
return summary;
|
|
33
33
|
}
|
|
34
|
+
/**
|
|
35
|
+
* Yazma yanıtındaki `appliedPatch` gövdesini iskelete indirger (B-55 · S-9).
|
|
36
|
+
*
|
|
37
|
+
* Alanın KANIT değeri path'indedir: "gerçekten yalnız o komponente mi dokunuldu" sorusunu
|
|
38
|
+
* JSON Pointer cevaplar (`page.md` Kural 2). `value` ise ajanın aynı turda KENDİ gönderdiği
|
|
39
|
+
* gövdenin yankısıdır — `add_component`'te eklenen alt ağacın tamamı, `replace_prop`'ta
|
|
40
|
+
* yazılan Prop'un tamamı (`AppliedJsonPatch(Op, Path, Value)`). 2026-09-06·2 koşusunda on
|
|
41
|
+
* dört yazma çağrısının yanıtı bağlamın en büyük tek kalemiydi.
|
|
42
|
+
*
|
|
43
|
+
* Kanıt kaybı yok: yamanın KAYDEDİLDİĞİNİ `appliedPatch` zaten söylemiyordu (G-23). Onu
|
|
44
|
+
* yamadan sonra okunan şemayla karşılaştıran `schemaOpDrops` söylüyor ve sonucu yanıtta
|
|
45
|
+
* `silentlyDropped` olarak duruyor. Tam gövde `view: 'full'` ile alınır.
|
|
46
|
+
*/
|
|
47
|
+
export function outlineAppliedPatch(applied) {
|
|
48
|
+
if (!Array.isArray(applied))
|
|
49
|
+
return applied;
|
|
50
|
+
return applied.map(entry => {
|
|
51
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry))
|
|
52
|
+
return entry;
|
|
53
|
+
const record = entry;
|
|
54
|
+
const value = record.value;
|
|
55
|
+
const node = value && typeof value === 'object' && !Array.isArray(value) && typeof value.elementId === 'string'
|
|
56
|
+
? summarize(value)
|
|
57
|
+
: undefined;
|
|
58
|
+
return { op: record.op, path: record.path, ...(node ? { component: node } : {}) };
|
|
59
|
+
});
|
|
60
|
+
}
|
|
34
61
|
/** elementId ya da id ile ağaçta düğüm bulur. */
|
|
35
62
|
export function findNode(nodes, target) {
|
|
36
63
|
for (const node of nodes ?? []) {
|
|
@@ -77,6 +104,7 @@ export function pageUpdateBody(page, overrides = {}) {
|
|
|
77
104
|
name: page.name,
|
|
78
105
|
slug: page.slug,
|
|
79
106
|
browserTitle: page.browserTitle ?? '',
|
|
107
|
+
browserTitleKey: page.browserTitleKey ?? '',
|
|
80
108
|
schema: page.schema ?? [],
|
|
81
109
|
events: asArray(page.events).filter(event => event?.value),
|
|
82
110
|
middlewareIds: page.middlewareIds ?? [],
|
|
@@ -1,22 +1,38 @@
|
|
|
1
1
|
import { guid, projectParam, environmentParam, readOnly, updates } from './common.js';
|
|
2
2
|
import { handle } from '../platform/responses.js';
|
|
3
3
|
import { z } from 'zod';
|
|
4
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
5
|
+
import { readProjectLanguages, translationLanguageFindings } from '../knowledge/languages.js';
|
|
6
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
4
7
|
const translationKeyInput = z.object({
|
|
5
8
|
key: z.string().min(1),
|
|
6
|
-
values: z.record(z.string()).describe("Dil kodu (ISO 639-1) → çeviri metni
|
|
9
|
+
values: z.record(z.string().regex(/^[a-z]{2}$/, 'ISO 639-1 iki harfli küçük dil kodu (tr, en, de …); tr-TR gibi bölge ekli kod reddedilir'), z.string()).describe("Dil kodu (ISO 639-1, iki küçük harf) → çeviri metni. Platform kodu yalnız 184 kodluk ISO sözlüğüne göre doğrular (TR-001); PROJENİN languages listesine göre doğrulamaz — o bağı gateway kurar (TR-002)"),
|
|
7
10
|
componentId: guid.optional().describe("Çeviriyi belirli bir komponente bağlar; o komponent silinince kaydı da silinir")
|
|
8
11
|
});
|
|
12
|
+
/**
|
|
13
|
+
* Yazma öncesi projenin dil durumunu okur. Proje okunamazsa (uç düşer, yetki yoksa) kontrol
|
|
14
|
+
* ATLANIR ve karar platforma kalır — `project_create`'in slug ön aramasındaki (PRJ-001) davranışın
|
|
15
|
+
* aynısı. Uydurma bulgu üretilmez.
|
|
16
|
+
*/
|
|
17
|
+
async function languageFindings(platform, project, keys) {
|
|
18
|
+
const slug = platform.resolveProject(project);
|
|
19
|
+
const codes = keys.flatMap(entry => Object.keys(entry.values ?? {}));
|
|
20
|
+
if (codes.length === 0)
|
|
21
|
+
return [];
|
|
22
|
+
const payload = await platform.org('GET', `/p/${slug}`).catch(() => undefined);
|
|
23
|
+
return translationLanguageFindings(readProjectLanguages(payload), codes, slug);
|
|
24
|
+
}
|
|
9
25
|
export function registerTranslationTools(server, platform) {
|
|
10
26
|
const config = platform.config;
|
|
11
27
|
server.registerTool('translation_list', {
|
|
12
28
|
title: 'Çevirileri listele',
|
|
13
|
-
description: "Application'ın tüm çeviri kayıtlarını (dil × anahtar × değer) düz liste olarak döner. Proje dilleri/varsayılan dil için project_get; sayfada bir prop'u çeviriye bağlamak için platform_guide('translation').",
|
|
29
|
+
description: "Application'ın tüm çeviri kayıtlarını (dil × anahtar × değer) düz liste olarak döner. Proje dilleri/varsayılan dil için project_get (yanıtındaki localization bloğu runtime'ın bu çevirileri çözüp çözemeyeceğini söyler); sayfada bir prop'u çeviriye bağlamak için platform_guide('translation').",
|
|
14
30
|
inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid },
|
|
15
31
|
annotations: readOnly
|
|
16
32
|
}, handle(config, ({ project, environment, applicationId }) => platform.tenant('GET', project, `/translations/${applicationId}`, { environment })));
|
|
17
33
|
server.registerTool('translation_update', {
|
|
18
34
|
title: 'Çevirileri güncelle',
|
|
19
|
-
description: "Bir ya da daha çok çeviri anahtarını toplu ekler/günceller (keys) ve/veya siler (deletedKeys) — tek çağrıda ikisi de verilebilir. 'keys' üzerinden yeni bir anahtar da açılabilir (upsert).
|
|
35
|
+
description: "Bir ya da daha çok çeviri anahtarını toplu ekler/günceller (keys) ve/veya siler (deletedKeys) — tek çağrıda ikisi de verilebilir. 'keys' üzerinden yeni bir anahtar da açılabilir (upsert; verilmeyen diller silinmez, korunur). DİKKAT: çeviri yazmak tek başına YETMEZ — runtime bir etiketi ancak projenin languages listesi ve defaultLanguage kuruluysa çözer, aksi hâlde ekrana ham anahtar basar ve yazma yine 200 döner (ölçüldü 2026-09-09, S-15). Gateway yazmadan önce projeyi okur ve bağ kopuksa TR-002 caution'ı düşer; dilleri project_update(languages, defaultLanguage) ile kurun. Backend ön kontrolü (TR-001, block): anahtarlar boş olamaz ve tekil, dil kodları ISO 639-1 listesinden, deletedKeys boş olamaz.",
|
|
20
36
|
inputSchema: {
|
|
21
37
|
project: projectParam,
|
|
22
38
|
environment: environmentParam,
|
|
@@ -25,13 +41,13 @@ export function registerTranslationTools(server, platform) {
|
|
|
25
41
|
deletedKeys: z.array(z.string().min(1)).optional().describe('Silinecek anahtar adları')
|
|
26
42
|
},
|
|
27
43
|
annotations: updates
|
|
28
|
-
}, handle(config, ({ project, environment, applicationId, keys, deletedKeys }) => {
|
|
44
|
+
}, handle(config, async ({ project, environment, applicationId, keys, deletedKeys }) => {
|
|
29
45
|
if (!keys?.length && !deletedKeys?.length) {
|
|
30
46
|
throw new Error("'keys' ya da 'deletedKeys' parametrelerinden en az biri dolu olmalı.");
|
|
31
47
|
}
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
});
|
|
48
|
+
const body = { keys: keys ?? [], deletedKeys: deletedKeys ?? [] };
|
|
49
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'translation', body, { operation: 'update' }));
|
|
50
|
+
const languages = await languageFindings(platform, project, keys ?? []);
|
|
51
|
+
return attachFindings(await platform.tenant('PUT', project, `/translations/${applicationId}`, { environment, body }), [...findings, ...languages]);
|
|
36
52
|
}));
|
|
37
53
|
}
|
package/dist/tools/views.js
CHANGED
|
@@ -3,6 +3,12 @@ import { guid, projectParam, environmentParam, definitionParam, pagingParams, re
|
|
|
3
3
|
import { PlatformApiError } from '../platform/errors.js';
|
|
4
4
|
import { handle } from '../platform/responses.js';
|
|
5
5
|
import { z } from 'zod';
|
|
6
|
+
import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
7
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
8
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
9
|
+
import { constraintSummary } from '../knowledge/backendConstraints.js';
|
|
10
|
+
const policyOverrideParam = z.boolean().optional()
|
|
11
|
+
.describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
|
|
6
12
|
export function registerViewTools(server, platform) {
|
|
7
13
|
const config = platform.config;
|
|
8
14
|
const normalizeSource = (definition) => {
|
|
@@ -19,29 +25,33 @@ export function registerViewTools(server, platform) {
|
|
|
19
25
|
}, handle(config, ({ project, environment, ...query }) => platform.tenant('GET', project, '/views', { environment, query })));
|
|
20
26
|
server.registerTool('view_get', {
|
|
21
27
|
title: 'View getir',
|
|
22
|
-
description:
|
|
28
|
+
description: "Tek view tanımını getirir; view_create için en iyi gövde şablonudur. Hatırlatma: view TAM METİN ARAMA indeksidir, agregasyon/raporlama aracı değildir — özet için query_create (09-02 M-3).",
|
|
23
29
|
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
24
30
|
annotations: readOnly
|
|
25
31
|
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/views/${id}`, { environment })));
|
|
26
32
|
server.registerTool('view_create', {
|
|
27
33
|
title: 'View oluştur',
|
|
28
|
-
description: "TAM METİN ARAMA indeksi tanımlar (definition = CreateViewCommand; gövde şeması ngapps://schemas/CreateViewCommand, analizör sözlüğü platform_guide('view')) — raporlama/agregasyon aracı DEĞİLDİR. Seçilen tablo/alanlar analizörden (tokenizer/dil) geçirilip slug adlı projeksiyon tablosuna indekslenir; tüketimi source:'view' microservice'idir ve o uç ZORUNLU 'searchText' parametresi ister (+limit/offset). Rapor/özet için raw query (query_create) + query kaynaklı microservice kullanın. Birden çok entity verilebilir (entities: [{entityId, alias}], fields alias'la bağlanır: viewEntityAlias) ama bu JOIN DEĞİLDİR: her tablo ayrı ayrı indekslenir, aralarında ilişki tanımı yoktur ve eşleşme hangi tablodan geldiyse dönen kayıt o tablonun satırıdır. İlişkili içeriği tek kayıtta aratmak için denormalize bir alan kullanın (yorum metnini talebe yazan automation + tek entity'li view).",
|
|
29
|
-
inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
|
|
34
|
+
description: "TAM METİN ARAMA indeksi tanımlar (definition = CreateViewCommand; gövde şeması ngapps://schemas/CreateViewCommand, analizör sözlüğü platform_guide('view')) — raporlama/agregasyon aracı DEĞİLDİR. Seçilen tablo/alanlar analizörden (tokenizer/dil) geçirilip slug adlı projeksiyon tablosuna indekslenir; tüketimi source:'view' microservice'idir ve o uç ZORUNLU 'searchText' parametresi ister (+limit/offset). Rapor/özet için raw query (query_create) + query kaynaklı microservice kullanın. Birden çok entity verilebilir (entities: [{entityId, alias}], fields alias'la bağlanır: viewEntityAlias) ama bu JOIN DEĞİLDİR: her tablo ayrı ayrı indekslenir, aralarında ilişki tanımı yoktur ve eşleşme hangi tablodan geldiyse dönen kayıt o tablonun satırıdır. İlişkili içeriği tek kayıtta aratmak için denormalize bir alan kullanın (yorum metnini talebe yazan automation + tek entity'li view). " + constraintSummary('CreateViewCommand'),
|
|
35
|
+
inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
30
36
|
annotations: writes
|
|
31
|
-
}, handle(config, async ({ project, environment, definition }) => {
|
|
37
|
+
}, handle(config, async ({ project, environment, definition, policyOverride }) => {
|
|
32
38
|
const fixes = normalizeSource(definition);
|
|
39
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'view', definition, { operation: 'create' }, policyOverride));
|
|
40
|
+
const drift = unmappedMembers('CreateViewCommand', definition);
|
|
33
41
|
const response = await platform.tenant('POST', project, '/views', { environment, body: definition });
|
|
34
|
-
return withNormalization(response, viewAliasNote(fixes));
|
|
42
|
+
return attachFindings(withUnmappedMembers(withNormalization(response, viewAliasNote(fixes)), drift), findings);
|
|
35
43
|
}));
|
|
36
44
|
server.registerTool('view_update', {
|
|
37
45
|
title: 'View güncelle',
|
|
38
|
-
description: "View tanımını günceller (definition = UpdateViewCommand, şema ngapps://schemas/UpdateViewCommand); mevcut hâli view_get ile alın. UpdateViewCommand'da datasourceId YOKTUR (create'te bağlanır). DİKKAT: view_get alanları 'viewEntityId' ile döndürür, yazma tarafı 'viewEntityAlias' ister — okuduğunuzu birebir geri yazarsanız gateway id'yi alias'a çevirir, çeviremezse isteği reddeder.",
|
|
39
|
-
inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam },
|
|
46
|
+
description: "View tanımını günceller (definition = UpdateViewCommand, şema ngapps://schemas/UpdateViewCommand); mevcut hâli view_get ile alın. UpdateViewCommand'da datasourceId YOKTUR (create'te bağlanır). DİKKAT: view_get alanları 'viewEntityId' ile döndürür, yazma tarafı 'viewEntityAlias' ister — okuduğunuzu birebir geri yazarsanız gateway id'yi alias'a çevirir, çeviremezse isteği reddeder. " + constraintSummary('UpdateViewCommand'),
|
|
47
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
|
|
40
48
|
annotations: updates
|
|
41
|
-
}, handle(config, async ({ project, environment, id, definition }) => {
|
|
49
|
+
}, handle(config, async ({ project, environment, id, definition, policyOverride }) => {
|
|
42
50
|
const fixes = normalizeSource(definition);
|
|
51
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'view', definition, { operation: 'update' }, policyOverride));
|
|
52
|
+
const drift = unmappedMembers('UpdateViewCommand', definition);
|
|
43
53
|
const response = await platform.tenant('PUT', project, `/views/${id}`, { environment, body: definition });
|
|
44
|
-
return withNormalization(response, viewAliasNote(fixes));
|
|
54
|
+
return attachFindings(withUnmappedMembers(withNormalization(response, viewAliasNote(fixes)), drift), findings);
|
|
45
55
|
}));
|
|
46
56
|
server.registerTool('view_delete', {
|
|
47
57
|
title: 'View sil',
|
|
@@ -51,7 +61,7 @@ export function registerViewTools(server, platform) {
|
|
|
51
61
|
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/views/${id}`, { environment })));
|
|
52
62
|
server.registerTool('view_rebuild', {
|
|
53
63
|
title: 'View yeniden kur',
|
|
54
|
-
description:
|
|
64
|
+
description: "View projeksiyon tablosunu yeniden inşa eder (veri değişikliklerinden sonra senkron için). Rebuild ARAMA indeksini tazeler; sayım/özet üretmez — raporlama için query_create (09-02 M-3).",
|
|
55
65
|
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
56
66
|
annotations: writes
|
|
57
67
|
}, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/views/${id}/rebuild`, { environment })));
|