@appaflytech/zeyrek-ai-mcp 0.0.8 → 0.0.9
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/README.md +27 -21
- package/dist/config.js +2 -0
- package/dist/knowledge/componentCatalog.js +122 -27
- package/dist/knowledge/componentEvents.js +35 -4
- package/dist/knowledge/errorPath.js +1 -1
- package/dist/knowledge/normalize.js +15 -0
- package/dist/knowledge/rules.js +3 -1
- package/dist/knowledge/styleResolve.js +46 -4
- package/dist/knowledge/themeResolve.js +936 -0
- package/dist/knowledge/themeRules.js +221 -0
- package/dist/knowledge/themeSchema.js +335 -0
- package/dist/platform/domSummary.js +144 -0
- package/dist/platform/screenshot.js +414 -23
- package/dist/platform/screenshotMemory.js +55 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/theme-brand.json +162 -0
- package/dist/resources/guides/component.md +7 -1
- package/dist/resources/guides/getting-started.md +4 -0
- package/dist/resources/guides/layout.md +30 -26
- package/dist/resources/guides/microservice.md +55 -1
- package/dist/resources/guides/page.md +87 -44
- package/dist/resources/guides/theme.md +210 -0
- package/dist/resources/guides/translation.md +6 -4
- package/dist/resources/guides/workflow.md +91 -6
- package/dist/resources/schema-fingerprint.json +2 -2
- package/dist/server.js +52 -43
- package/dist/tools/components.js +8 -0
- package/dist/tools/designSchemaPatch.js +152 -0
- package/dist/tools/flows.js +15 -18
- package/dist/tools/layouts.js +4 -4
- package/dist/tools/microservices.js +5 -5
- package/dist/tools/pages.js +196 -323
- package/dist/tools/projects.js +66 -9
- package/dist/tools/schemaOps.js +280 -0
- package/dist/tools/screenWrite.js +2 -1
- package/dist/tools/screens.js +0 -7
- package/dist/tools/themes.js +163 -0
- package/dist/tools/workflows.js +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { orphanedFlowWarnings, orphanedFlowPlan, orphanedFlowMessage, staleSetterWarnings, normalizeSchemaOps, validationNote, componentDefaultsNote, replacePropNote, flexSizeWarnings, nestedRepeaterNodes, nestedRepeaterMessage, stringStyleNodes, stringStyleWarning, inertFlexPropNodes, inertFlexPropWarning, verticalGapWarnings, withNormalization, FLEX_LAYOUT_PROP_NAMES } from '../knowledge/normalize.js';
|
|
2
|
+
import { opsParam, writeViewParam, outlineWriteResponse, unknownEventOps, collectTypes, simulateLayoutOps, removeNodeFromTree, planEventRemovals, planResizes, planPropTypes, assertOpBodies } from './schemaOps.js';
|
|
3
|
+
import { guid, projectParam, environmentParam, updates, microOps } from './common.js';
|
|
4
|
+
import { fetchHost, hostPath } from './screens.js';
|
|
5
|
+
import { assertScreenWriteGuards, screenCatalogNotes } from './screenWrite.js';
|
|
6
|
+
import { schemaOpDrops, withWriteDiff } from '../platform/writeDiff.js';
|
|
7
|
+
import { fetchComponentEventNames } from './components.js';
|
|
8
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
9
|
+
import { handle } from '../platform/responses.js';
|
|
10
|
+
import { slotWarning } from './layouts.js';
|
|
11
|
+
import { z } from 'zod';
|
|
12
|
+
const HOSTS = {
|
|
13
|
+
layout: {
|
|
14
|
+
tool: 'layout_schema_patch',
|
|
15
|
+
readTool: 'layout_schema_get',
|
|
16
|
+
tree: 'views',
|
|
17
|
+
kind: 'layout',
|
|
18
|
+
codes: { gap: 'LAY-003 (uyarı)', flexMismatch: 'LAY-007 (uyarı)', flexInvalid: 'LAY-008 (uyarı)' },
|
|
19
|
+
title: "Layout şemasına micro-op uygula",
|
|
20
|
+
description: "page_schema_patch'in layout karşılığı: micro-op'ları platformun PATCH /layouts/{app}/{id}/schema ucuna TEK istekle yazar; sunucu sayfa ile AYNI op sözlüğünü ve doğrulama kapsamını kullanır (dokunulan düğümler tam doğrulanır, geri kalan ağaç yapısal). İstek atomiktir: bir op reddedilirse hiçbiri yazılmaz. Op sözlüğü page_schema_patch ile birebir aynıdır; farklar: kök ekleme 'root' parent'ına gider (page_container yoktur), remove_event target:'page' layout düzeyi olayları hedefler, baseVersion yoktur (son yazan kazanır — hedefleri layout_schema_get ile okuyun). Slot'u silen ya da ikinci slot ekleyen yama LAY-002/LAY-006 uyarısı alır. Layout'un adı, parametreleri ve düz JSON Patch değişiklikleri için layout_patch; tam ağaç için layout_update."
|
|
21
|
+
},
|
|
22
|
+
component: {
|
|
23
|
+
tool: 'component_schema_patch',
|
|
24
|
+
readTool: 'component_get',
|
|
25
|
+
tree: 'schema',
|
|
26
|
+
kind: 'widget',
|
|
27
|
+
codes: { gap: 'PAGE-010 (uyarı)', flexMismatch: 'PAGE-017 (uyarı)', flexInvalid: 'PAGE-018 (uyarı)' },
|
|
28
|
+
title: 'Widget şemasına micro-op uygula',
|
|
29
|
+
description: "page_schema_patch'in widget karşılığı: widget kaydının (kind:'widget') kendi ağacına micro-op'ları platformun PATCH /components/{app}/{id}/schema ucuna TEK istekle yazar; sunucu sayfa ile AYNI op sözlüğünü ve doğrulama kapsamını kullanır. İstek atomiktir: bir op reddedilirse hiçbiri yazılmaz. Op sözlüğü page_schema_patch ile aynıdır; farklar: kök ekleme 'root' parent'ına gider, widget'ın düzey olayı olmadığı için remove_event yalnız komponent hedefler, baseVersion yoktur. id widget kaydının id'sidir (component_list / component_get). component_update tam gövde PUT'tur ve slug alır; ağaçta hedefli değişiklik için bu aracı kullanın."
|
|
30
|
+
}
|
|
31
|
+
};
|
|
32
|
+
/**
|
|
33
|
+
* Layout ve widget ağaçlarının micro-op yazması. Sayfa hattıyla (`page_schema_patch`) aynı istek
|
|
34
|
+
* öncesi kapılar koşar; sayfaya özgü olanlar (page_container koruması, kökten page_container'a
|
|
35
|
+
* yönlendirme, baseVersion) düşer. Kayıt her yamada BİR kez okunur: PAGE-008, G-21 ve boyut/tip
|
|
36
|
+
* planlaması okunan ağaca bakar, platformda sürüm koşulu olmadığı için okuma bir kilit değildir.
|
|
37
|
+
*/
|
|
38
|
+
export function registerDesignSchemaPatchTool(server, platform, host) {
|
|
39
|
+
const config = platform.config;
|
|
40
|
+
const spec = HOSTS[host];
|
|
41
|
+
server.registerTool(spec.tool, {
|
|
42
|
+
title: spec.title,
|
|
43
|
+
description: spec.description,
|
|
44
|
+
inputSchema: {
|
|
45
|
+
project: projectParam,
|
|
46
|
+
environment: environmentParam,
|
|
47
|
+
applicationId: guid,
|
|
48
|
+
id: guid.describe(host === 'layout' ? "Layout id'si" : "Widget kaydının id'si (slug değil)"),
|
|
49
|
+
ops: opsParam.optional(),
|
|
50
|
+
operations: opsParam.optional().describe("'ops' ile aynı alan (eşanlamlı)"),
|
|
51
|
+
allowFlowDeletion: z.boolean().optional().describe(`true: bu yama bir akışı referanssız bırakacaksa yine de uygulanır. Varsayılan false — platform ${host === 'layout' ? 'layout' : 'widget'} kaydında da referanssız akışı KALICI siler ve gateway isteği önden reddeder (G-21).`),
|
|
52
|
+
view: writeViewParam
|
|
53
|
+
},
|
|
54
|
+
annotations: updates
|
|
55
|
+
}, handle(config, async ({ project, environment, applicationId, id, ops, operations, allowFlowDeletion, view }) => {
|
|
56
|
+
const allOps = microOps(spec.tool, ops, operations).map(op => op);
|
|
57
|
+
const removeEventOps = allOps.filter(entry => entry.op === 'remove_event');
|
|
58
|
+
const resizeOps = allOps.filter(entry => entry.op === 'resize_component');
|
|
59
|
+
const propTypeOps = allOps.filter(entry => entry.op === 'set_prop_type');
|
|
60
|
+
const opRecords = allOps.filter(entry => !['remove_event', 'resize_component', 'set_prop_type'].includes(String(entry.op)));
|
|
61
|
+
const normalized = normalizeSchemaOps(opRecords);
|
|
62
|
+
assertOpBodies(opRecords, propTypeOps);
|
|
63
|
+
const record = await fetchHost(platform, host, project, environment, applicationId, id);
|
|
64
|
+
// Platform ucu kaydın türüne bakmaz: sıradan bir komponentin id'si verilirse kataloğundaki
|
|
65
|
+
// tanımın yanına bir şema yazılırdı.
|
|
66
|
+
if (host === 'component' && record.kind !== 'widget' && !record.widgetType) {
|
|
67
|
+
throw new PlatformApiError(`component_schema_patch yalnız widget kaydına yazar; '${String(record.slug ?? id)}' bir widget değil (kind: ${String(record.kind ?? 'yok')}). Komponent kaydını component_update ile güncelleyin.`, 400);
|
|
68
|
+
}
|
|
69
|
+
const nodes = (record[spec.tree] ?? []);
|
|
70
|
+
const events = host === 'layout' ? record.events : undefined;
|
|
71
|
+
if (opRecords.some(entry => entry.op === 'set_event')) {
|
|
72
|
+
const typesByElementId = new Map();
|
|
73
|
+
collectTypes(nodes, typesByElementId);
|
|
74
|
+
collectTypes(opRecords.map(entry => entry.component).filter(Boolean), typesByElementId);
|
|
75
|
+
const eventsBySlug = await fetchComponentEventNames(platform, project, environment, applicationId).catch(() => new Map());
|
|
76
|
+
const unknown = unknownEventOps(opRecords, typesByElementId, eventsBySlug);
|
|
77
|
+
if (unknown.length > 0)
|
|
78
|
+
throw new PlatformApiError(`PAGE-008 — geçersiz olay adı (istek gönderilmedi): ${unknown.join(' · ')}`, 400);
|
|
79
|
+
}
|
|
80
|
+
const deletedFlows = orphanedFlowPlan(nodes, events, [...opRecords, ...removeEventOps]);
|
|
81
|
+
if (deletedFlows.length > 0 && allowFlowDeletion !== true)
|
|
82
|
+
throw new PlatformApiError(orphanedFlowMessage(deletedFlows), 400);
|
|
83
|
+
const added = opRecords.filter(entry => entry.op === 'add_component').map(entry => entry.component);
|
|
84
|
+
assertScreenWriteGuards(spec.kind, { schema: added });
|
|
85
|
+
const catalogNotes = await screenCatalogNotes(platform, project, environment, applicationId, added);
|
|
86
|
+
const flowWarnings = opRecords.some(entry => entry.op === 'remove_component')
|
|
87
|
+
? [...orphanedFlowWarnings(nodes, events, opRecords), ...staleSetterWarnings(Array.isArray(record.flows) ? record.flows : [], opRecords, nodes)]
|
|
88
|
+
: [];
|
|
89
|
+
const simulated = structuredClone(nodes);
|
|
90
|
+
for (const entry of opRecords.filter(entry => entry.op === 'remove_component'))
|
|
91
|
+
removeNodeFromTree(simulated, String(entry.target ?? ''));
|
|
92
|
+
const touched = simulateLayoutOps(simulated, opRecords);
|
|
93
|
+
// Sayfadaki `simulatable` ile aynı küme: iç içe repeater ancak ekleme/taşıma ile doğar, kontrol
|
|
94
|
+
// her yamada koşsaydı kayıtlı ağacında zaten iç içe repeater olan layout hiç yamalanamazdı.
|
|
95
|
+
const simulatable = opRecords.some(entry => ['add_component', 'move_component'].includes(String(entry.op))
|
|
96
|
+
|| (entry.op === 'replace_prop' && FLEX_LAYOUT_PROP_NAMES.has(String(entry.prop ?? ''))));
|
|
97
|
+
const nested = simulatable ? nestedRepeaterNodes(simulated) : [];
|
|
98
|
+
if (nested.length > 0)
|
|
99
|
+
throw new PlatformApiError(nestedRepeaterMessage(nested), 400);
|
|
100
|
+
const reshaped = simulatable || opRecords.some(entry => entry.op === 'remove_component');
|
|
101
|
+
const layoutWarnings = reshaped
|
|
102
|
+
? [
|
|
103
|
+
...verticalGapWarnings(simulated, spec.codes.gap, touched),
|
|
104
|
+
...stringStyleWarning(stringStyleNodes(simulated)),
|
|
105
|
+
...inertFlexPropWarning(inertFlexPropNodes(simulated)),
|
|
106
|
+
...flexSizeWarnings(simulated, spec.codes.flexMismatch, spec.codes.flexInvalid),
|
|
107
|
+
...(host === 'layout' ? slotWarning(simulated) : [])
|
|
108
|
+
]
|
|
109
|
+
: [];
|
|
110
|
+
const removedEvents = planEventRemovals(removeEventOps);
|
|
111
|
+
const resized = resizeOps.length > 0 ? planResizes(nodes, resizeOps, spec.readTool) : { serverOps: [], applied: [], warnings: [] };
|
|
112
|
+
const propTypes = propTypeOps.length > 0 ? planPropTypes(nodes, propTypeOps, spec.readTool) : { serverOps: [], applied: [] };
|
|
113
|
+
const serverOps = [...removeEventOps, ...resized.serverOps, ...propTypes.serverOps, ...opRecords];
|
|
114
|
+
const response = ((await platform.tenant('PATCH', project, `${hostPath(host, applicationId, id)}/schema`, {
|
|
115
|
+
environment,
|
|
116
|
+
body: { ops: serverOps }
|
|
117
|
+
})) ?? {});
|
|
118
|
+
if (view !== 'full')
|
|
119
|
+
outlineWriteResponse(response);
|
|
120
|
+
if (removedEvents.length > 0)
|
|
121
|
+
response.removedEvents = removedEvents;
|
|
122
|
+
if (propTypes.applied.length > 0)
|
|
123
|
+
response.propTypes = propTypes.applied;
|
|
124
|
+
if (resized.applied.length > 0)
|
|
125
|
+
response.resized = resized.applied;
|
|
126
|
+
if (resized.warnings.length > 0)
|
|
127
|
+
response.resizeWarnings = resized.warnings;
|
|
128
|
+
if (deletedFlows.length > 0)
|
|
129
|
+
response.deletedFlows = deletedFlows;
|
|
130
|
+
if (flowWarnings.length > 0)
|
|
131
|
+
response.flowWarnings = flowWarnings;
|
|
132
|
+
if (layoutWarnings.length > 0)
|
|
133
|
+
response.layoutWarnings = layoutWarnings;
|
|
134
|
+
const contentOps = opRecords.filter(entry => entry.op === 'add_component' || entry.op === 'replace_prop');
|
|
135
|
+
let opDrops = [];
|
|
136
|
+
if (contentOps.length > 0) {
|
|
137
|
+
try {
|
|
138
|
+
const saved = await fetchHost(platform, host, project, environment, applicationId, id);
|
|
139
|
+
opDrops = schemaOpDrops(saved[spec.tree], contentOps);
|
|
140
|
+
}
|
|
141
|
+
catch {
|
|
142
|
+
opDrops = [];
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
return withWriteDiff(withNormalization(response, [
|
|
146
|
+
...componentDefaultsNote(normalized.components),
|
|
147
|
+
...validationNote(normalized.validations),
|
|
148
|
+
...catalogNotes,
|
|
149
|
+
...replacePropNote(normalized.props)
|
|
150
|
+
]), opDrops);
|
|
151
|
+
}));
|
|
152
|
+
}
|
package/dist/tools/flows.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { fetchHost, flowPath, hostParam,
|
|
1
|
+
import { fetchHost, flowPath, hostParam, unwrap } from './screens.js';
|
|
2
2
|
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive, microOps } from './common.js';
|
|
3
3
|
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
4
|
import { normalizeFlowSteps, targetlessSetterWarnings, unknownSetterTargetWarnings, withNormalization } from '../knowledge/normalize.js';
|
|
@@ -42,10 +42,6 @@ function flowSummary(flow) {
|
|
|
42
42
|
errorSteps: Array.isArray(flow.errorSteps) ? flow.errorSteps.length : 0
|
|
43
43
|
};
|
|
44
44
|
}
|
|
45
|
-
async function hostEnvelope(platform, host, project, environment, applicationId, hostId) {
|
|
46
|
-
const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
47
|
-
return { record, envelope: { [host]: hostUpdateBody(host, { ...record, applicationId }) } };
|
|
48
|
-
}
|
|
49
45
|
/**
|
|
50
46
|
* FLOW-024'ün arayacağı düğüm kümesi. Ham kaydın TAMAMI gezilmez: kayıt kendi `flows`'unu da
|
|
51
47
|
* taşıyabilir ve oradaki bayat element id'leri kümeyi kirletip aramayı sessizce boşa çıkarır.
|
|
@@ -255,7 +251,7 @@ export function registerFlowTools(server, platform) {
|
|
|
255
251
|
}));
|
|
256
252
|
server.registerTool('flow_create', {
|
|
257
253
|
title: 'Akış oluştur',
|
|
258
|
-
description: "Sayfa/layout/komponent için olay akışı oluşturur.
|
|
254
|
+
description: "Sayfa/layout/komponent için olay akışı oluşturur. İstek yalnız akışı yazar, barındırıcının şemasına dokunmaz; gateway barındırıcıyı yalnız setter hedeflerini denetlemek için okur (FLOW-024). Akışı bir komponent olayına bağlamak ayrı adımdır ve akış kaydedildikten SONRA yapılır: set_event {target, event, eventValue: <flow id>} — sayfada page_schema_patch, layout'ta layout_schema_patch, widget'ta component_schema_patch.",
|
|
259
255
|
inputSchema: {
|
|
260
256
|
project: projectParam,
|
|
261
257
|
environment: environmentParam,
|
|
@@ -271,7 +267,7 @@ export function registerFlowTools(server, platform) {
|
|
|
271
267
|
},
|
|
272
268
|
annotations: writes
|
|
273
269
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
|
|
274
|
-
const
|
|
270
|
+
const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
275
271
|
const resolvedFlowId = flowId ?? randomUUID();
|
|
276
272
|
const flow = {
|
|
277
273
|
id: resolvedFlowId,
|
|
@@ -289,7 +285,10 @@ export function registerFlowTools(server, platform) {
|
|
|
289
285
|
...flowBodyWarnings(steps),
|
|
290
286
|
...unknownSetterTargetWarnings(steps, errorSteps ?? [], hostNodes(hostRecord))
|
|
291
287
|
];
|
|
292
|
-
|
|
288
|
+
// Gövde yalnız akıştır (designer-partial-update K1): barındırıcı zarfı platformda hâlâ isteğe bağlı
|
|
289
|
+
// ama gönderilirse okunan tam kaydı yeniden yazar, açık Studio designer'ının kayıtlı tabanını
|
|
290
|
+
// eskitir. Kayıt yalnız FLOW-024 hedef araması için okunur.
|
|
291
|
+
await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow } });
|
|
293
292
|
// Diff YALNIZ çağıranın yazdığı alanları kapsar; `id` ve barındırıcı bağı (`pageId` vb.)
|
|
294
293
|
// gövdeye gateway koyar, kullanıcının düzeltebileceği bir şey değildir.
|
|
295
294
|
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, resolvedFlowId, { name: flow.name, slug: flow.slug, steps: flow.steps, errorSteps: flow.errorSteps });
|
|
@@ -298,12 +297,12 @@ export function registerFlowTools(server, platform) {
|
|
|
298
297
|
hostId,
|
|
299
298
|
flow: { id: resolvedFlowId, name, slug, steps: steps.length },
|
|
300
299
|
...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
|
|
301
|
-
hint: "Akışı bir olaya bağlamak için: page_schema_patch ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
300
|
+
hint: "Akışı bir olaya bağlamak için: page_schema_patch (layout'ta layout_schema_patch, widget'ta component_schema_patch) ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
|
|
302
301
|
}, normalized), drops), findings);
|
|
303
302
|
}));
|
|
304
303
|
server.registerTool('flow_update', {
|
|
305
304
|
title: 'Akış güncelle',
|
|
306
|
-
description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir.
|
|
305
|
+
description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir. Yalnız akış yazılır; barındırıcının şeması değişmez.',
|
|
307
306
|
inputSchema: {
|
|
308
307
|
project: projectParam,
|
|
309
308
|
environment: environmentParam,
|
|
@@ -322,7 +321,7 @@ export function registerFlowTools(server, platform) {
|
|
|
322
321
|
const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
|
|
323
322
|
if (!current.id)
|
|
324
323
|
throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
|
|
325
|
-
const
|
|
324
|
+
const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
326
325
|
const flow = {
|
|
327
326
|
id: flowId,
|
|
328
327
|
name: name ?? current.name,
|
|
@@ -342,7 +341,7 @@ export function registerFlowTools(server, platform) {
|
|
|
342
341
|
...flowBodyWarnings(flow.steps),
|
|
343
342
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
344
343
|
];
|
|
345
|
-
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow
|
|
344
|
+
await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow } });
|
|
346
345
|
// Kısmi güncellemede yalnız GÖNDERİLEN alanlar karşılaştırılır; verilmeyen alan için
|
|
347
346
|
// "düştü" demek yanlış alarm olurdu.
|
|
348
347
|
const drops = await flowWriteDrops(platform, host, project, environment, applicationId, hostId, flowId, {
|
|
@@ -376,7 +375,7 @@ export function registerFlowTools(server, platform) {
|
|
|
376
375
|
const document = structuredClone(current);
|
|
377
376
|
const resolved = resolvePatchOperations(document, operations, 'flow_get');
|
|
378
377
|
const patched = fastJsonPatch.applyPatch(document, resolved.operations, true, false).newDocument;
|
|
379
|
-
const
|
|
378
|
+
const hostRecord = await fetchHost(platform, host, project, environment, applicationId, hostId);
|
|
380
379
|
const flow = {
|
|
381
380
|
...patched,
|
|
382
381
|
id: flowId,
|
|
@@ -392,8 +391,7 @@ export function registerFlowTools(server, platform) {
|
|
|
392
391
|
...flowBodyWarnings(flow.steps),
|
|
393
392
|
...unknownSetterTargetWarnings(flow.steps, flow.errorSteps, hostNodes(hostRecord))
|
|
394
393
|
];
|
|
395
|
-
// Gövde değil FARK gönderilir
|
|
396
|
-
// PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
|
|
394
|
+
// Gövde değil FARK gönderilir; platform PATCH ucu akışı tek başına yazar. Net diff okunan kayıt ile son hâl arasından hesaplanır.
|
|
397
395
|
const netOps = fastJsonPatch.compare(current, flow);
|
|
398
396
|
const saved = await platform.tenant('PATCH', project, flowPath(host, applicationId, hostId, flowId), {
|
|
399
397
|
environment,
|
|
@@ -413,12 +411,11 @@ export function registerFlowTools(server, platform) {
|
|
|
413
411
|
}));
|
|
414
412
|
server.registerTool('flow_delete', {
|
|
415
413
|
title: 'Akış sil',
|
|
416
|
-
description: "Akışı siler.
|
|
414
|
+
description: "Akışı siler; gövde yalnız akış id'sidir ve barındırıcının şemasına dokunmaz, yani olay bağı kendiliğinden KALKMAZ. Doğru sıra: önce bağı remove_event ile kaldırın (sayfada page_schema_patch, layout'ta layout_schema_patch, widget'ta component_schema_patch). Bağı kalmayan akışı platform o yamada zaten kalıcı siler (yama allowFlowDeletion:true ister); ardından flow_delete no-op'tur. Önce flow_delete çağırırsanız şemada ölü bağ kalır.",
|
|
417
415
|
inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
|
|
418
416
|
annotations: destructive
|
|
419
417
|
}, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
|
|
420
|
-
|
|
421
|
-
await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId, ...envelope } });
|
|
418
|
+
await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId } });
|
|
422
419
|
return { deleted: flowId, host, hostId };
|
|
423
420
|
}));
|
|
424
421
|
}
|
package/dist/tools/layouts.js
CHANGED
|
@@ -12,7 +12,7 @@ import { handle } from '../platform/responses.js';
|
|
|
12
12
|
import { z } from 'zod';
|
|
13
13
|
const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
|
|
14
14
|
.describe("JSON Patch listesi (RFC 6902): { op, path, value }. Yol seçici alabilir — '/views/0/children[elementId=baslik]/props/content/data'.");
|
|
15
|
-
const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir
|
|
15
|
+
const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir; ağaçta hedefli değişiklik için tam ağaç yerine layout_schema_patch micro-op'larını kullanın. İç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. 2026-09-08'den beri views, page_schema_patch'in doğrulama duvarından geçer: tanınmayan prop adı ve geçersiz enum değeri 422 ile REDDEDİLİR (kabul edilen değer listesiyle) ve hiçbir şey kaydedilmez — eskiden sessizce kaydediliyordu (P1-2).");
|
|
16
16
|
function countSlots(nodes) {
|
|
17
17
|
if (!Array.isArray(nodes))
|
|
18
18
|
return 0;
|
|
@@ -23,7 +23,7 @@ function countSlots(nodes) {
|
|
|
23
23
|
return total + (record.type === 'slot' ? 1 : 0) + countSlots(record.children);
|
|
24
24
|
}, 0);
|
|
25
25
|
}
|
|
26
|
-
function slotWarning(views) {
|
|
26
|
+
export function slotWarning(views) {
|
|
27
27
|
const slots = countSlots(views);
|
|
28
28
|
if (slots === 0) {
|
|
29
29
|
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."];
|
|
@@ -49,7 +49,7 @@ export function registerLayoutTools(server, platform) {
|
|
|
49
49
|
}, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
|
|
50
50
|
server.registerTool('layout_schema_get', {
|
|
51
51
|
title: "Layout komponent ağacını oku",
|
|
52
|
-
description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı.
|
|
52
|
+
description: "Layout'un komponent ağacını (views) okur — page_schema_get'in layout karşılığı. Ağaçta hedefli değişiklik için layout_schema_patch (sayfa ile aynı micro-op sözlüğü, PATCH /layouts/{app}/{id}/schema). Ad/parametre/olay alanlarında ya da düz JSON Patch yolu gerektiğinde layout_patch — o araç buradaki updatedAt'i expectedUpdatedAt olarak kabul eder. Tam ağacı kendiniz yazacaksanız layout_update.",
|
|
53
53
|
inputSchema: {
|
|
54
54
|
project: projectParam,
|
|
55
55
|
environment: environmentParam,
|
|
@@ -134,7 +134,7 @@ export function registerLayoutTools(server, platform) {
|
|
|
134
134
|
}));
|
|
135
135
|
server.registerTool('layout_patch', {
|
|
136
136
|
title: "Layout'a patch uygula",
|
|
137
|
-
description: "
|
|
137
|
+
description: "RFC 6902 JSON Patch ile hedefli değişiklik: gateway layout'u çeker, JSON Patch'i ve kendi normalizasyonlarını uygular, doğrulamadan geçirir ve FARKI platformun PATCH /layouts/{app}/{id} ucuna `{ baseVersion, ops }` olarak yazar — tam gövde PUT'u ATILMAZ (09-04 Y-25). Ağaçta düğüm ekleme/silme/taşıma, prop, olay ve boyut değişikliği için önce layout_schema_patch'i düşünün: sayfa ile aynı micro-op sözlüğüdür ve dokunulan düğümleri platform doğrular; bu araç ad/parametre/olay alanları ve düz yol gerektiren durumlar içindir. Yol İNDEKS yerine SEÇİCİ alabilir: '/views/0/children[elementId=baslik]/props/content/data'. Sürüm kontrolü platformda: okunan kaydın `version` değeri `baseVersion` olarak gider, araya başka bir yazma girdiyse 409 döner ve HİÇBİR op uygulanmaz. expectedUpdatedAt verirseniz gateway ayrıca istek gitmeden önce yerel kontrol yapar. Ayrıca 'set_prop_type' micro-op'unu tanır ({target, prop, propType: 'static'|'code'|'translation', ref?, data?}); JSON Patch ile aynı listede karışık verilebilir, micro-op'lar önce uygulanır. Yalnız etiket çeviriye bağlanacaksa aynı op layout_schema_patch'te platformun kendi op'u olarak çalışır.",
|
|
138
138
|
inputSchema: {
|
|
139
139
|
project: projectParam,
|
|
140
140
|
environment: environmentParam,
|
|
@@ -385,7 +385,7 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
385
385
|
}));
|
|
386
386
|
server.registerTool('microservice_debug', {
|
|
387
387
|
title: 'Microservice debug',
|
|
388
|
-
description: "Microservice'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı). executionItems YALNIZ ucun kendi adımlarını kapsar — ucun ÖNÜNDE koşan middleware'ler ayrıca 'middlewares' alanında adım özetiyle listelenir (09-04 Y-04). Değeri NULL bağlanan query parametreleri 'unboundParameters' alanında adıyla listelenir (ağaçta bu satırların value anahtarı görünmez).",
|
|
388
|
+
description: "Microservice'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı). executionItems YALNIZ ucun kendi adımlarını kapsar — ucun ÖNÜNDE koşan middleware'ler ayrıca 'middlewares' alanında adım özetiyle listelenir (09-04 Y-04). Değeri NULL bağlanan query parametreleri 'unboundParameters' alanında adıyla listelenir (ağaçta bu satırların value anahtarı görünmez). Preview semantiğiyle koşar: tek transaction + sonunda rollback, kalıcı satır yazmaz. httpStatus akışın kendi sonucudur (4xx/5xx'te runtimeErrors eklenir); ağaç düğümlerinde hata bayrağı YOKTUR — patlayan adımın output'u boştur, hata gövdesi hata dalındaki response adımının çıktısındadır. Zamanlanmış (schedule-job) uçlar da slug yoluyla debug edilir (method: 'POST', parametresiz): job geçmişi hatayı yalnız mesaj olarak yazar, adım adım girdi/çıktı burada görülür (platform_guide('microservice') §Zamanlanmış işin gerçek sonucunu görmek).",
|
|
389
389
|
inputSchema: executeInput,
|
|
390
390
|
annotations: writes
|
|
391
391
|
}, handle(config, async ({ project, environment, application, microservice, method, routeParams, query, body, parameters, headers, anonymous, asUser, cookies, language }) => {
|
|
@@ -407,7 +407,7 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
407
407
|
}));
|
|
408
408
|
server.registerTool('microservice_preview', {
|
|
409
409
|
title: 'Microservice önizle',
|
|
410
|
-
description: "Microservice tanımını çalıştırır: kaydedilmemiş gövde için definition, KAYITLI bir uç için yalnız id verin (gateway tanımı kendisi çeker — tam gövdeyi bağlama almanız gerekmez). previewParameters ayrı alandır ve ikisiyle de çalışır. Preview tek transaction'da koşar ve sonunda ROLLBACK edilir — kalıcı satır yazmaz; code step'teki dış HTTP çağrıları istisnadır. Davranış kuralları: platform_guide('microservice').",
|
|
410
|
+
description: "Microservice tanımını çalıştırır: kaydedilmemiş gövde için definition, KAYITLI bir uç için yalnız id verin (gateway tanımı kendisi çeker — tam gövdeyi bağlama almanız gerekmez). previewParameters ayrı alandır ve ikisiyle de çalışır. Preview tek transaction'da koşar ve sonunda ROLLBACK edilir — kalıcı satır yazmaz; code step'teki dış HTTP çağrıları istisnadır. Yanıt httpStatus (akışın KENDİ sonucu; 4xx/5xx'te gateway runtimeErrors ekler) + gövdeden ibarettir, adım ağacı DÖNMEZ — adım adım girdi/çıktı için microservice_debug, adım başına hata alanı için workflow_preview. Zamanlanmış (schedule-job) ucu id ile parametresiz önizleyin: zamanlanmış koşu da girdisiz koşar. Davranış kuralları: platform_guide('microservice').",
|
|
411
411
|
inputSchema: {
|
|
412
412
|
project: projectParam,
|
|
413
413
|
environment: environmentParam,
|
|
@@ -447,7 +447,7 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
447
447
|
}));
|
|
448
448
|
server.registerTool('microservice_job_history', {
|
|
449
449
|
title: 'Job geçmişi',
|
|
450
|
-
description:
|
|
450
|
+
description: "Zamanlanmış (schedule-job) microservice'in çalışma geçmişini listeler: satır başına { status: running | completed | failed, triggeredAt, completedAt, durationMs, errorMessage }. failed satırın errorMessage'ı patlayan adımı ve sebebini taşır: bağlanmamış fail dalında \"Step 'x' failed: <sebep>\", bağlı global-error dalıyla 2xx dışı sonuçta \"The job's workflow returned 500 (<code>): <mesaj>\"; iki durumda da yazmalar geri alınmıştır (2026-09-16 öncesi imajlar bu ikisini completed yazardı). completed = akış 2xx ile bitti ve yazmalar commit edildi. Adım adım girdi/çıktı burada YOK: aynı akışı workflow_preview (adım başına hata alanı) ya da microservice_debug (executionItems) ile koşturun — platform_guide('microservice') §Zamanlanmış işin gerçek sonucunu görmek. status ile yalnız running | completed | failed koşular süzülür; order TriggeredAt | CompletedAt | ExecutionStatus | DurationMs.",
|
|
451
451
|
inputSchema: {
|
|
452
452
|
project: projectParam,
|
|
453
453
|
environment: environmentParam,
|
|
@@ -459,7 +459,7 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
459
459
|
}, handle(config, ({ project, environment, id, ...query }) => platform.tenant('GET', project, `/microservices/${id}/job-history`, { environment, query })));
|
|
460
460
|
server.registerTool('microservice_schedule_get', {
|
|
461
461
|
title: 'Zamanlama durumu',
|
|
462
|
-
description: 'Zamanlanmış (schedule-job) microservice\'in canlı zamanlama durumunu döner: state (scheduled | paused | notScheduled | error | ended), isRunning, nextFireTimeUtc, previousFireTimeUtc, sıradaki 5 koşu (upcomingFireTimesUtc), cron + timeZoneId, serverTimeUtc ve son koşunun özeti (lastExecution). "Bir sonraki sefer ne zaman koşacak?" sorusunu cron\'u yorumlamadan buradan cevaplayın. paused iken nextFireTimeUtc null ve upcoming boştur. notScheduled: kayıt var ama zamanlayıcıda trigger yok — en geç 5 dk içinde uzlaştırma kurar ya da servisi kaydedin (microservice_update). ended: tanımın sonraki koşusu yok (once geçti, end on/after doldu). schedule alanı tanımın kendisidir. Schedule tipli olmayan servis 400 döner.',
|
|
462
|
+
description: 'Zamanlanmış (schedule-job) microservice\'in canlı zamanlama durumunu döner: state (scheduled | paused | notScheduled | error | ended), isRunning, nextFireTimeUtc, previousFireTimeUtc, sıradaki 5 koşu (upcomingFireTimesUtc), cron + timeZoneId, serverTimeUtc ve son koşunun özeti (lastExecution — geçmiş satırının aynısı: failed ise errorMessage patlayan adımı ve sebebini taşır; bkz. microservice_job_history). "Bir sonraki sefer ne zaman koşacak?" sorusunu cron\'u yorumlamadan buradan cevaplayın. paused iken nextFireTimeUtc null ve upcoming boştur. notScheduled: kayıt var ama zamanlayıcıda trigger yok — en geç 5 dk içinde uzlaştırma kurar ya da servisi kaydedin (microservice_update). ended: tanımın sonraki koşusu yok (once geçti, end on/after doldu). schedule alanı tanımın kendisidir. Schedule tipli olmayan servis 400 döner.',
|
|
463
463
|
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
464
464
|
annotations: readOnly
|
|
465
465
|
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/microservices/${id}/schedule`, { environment })));
|
|
@@ -475,7 +475,7 @@ export function registerMicroserviceTools(server, platform) {
|
|
|
475
475
|
}, handle(config, ({ project, environment, schedule }) => platform.tenant('POST', project, '/microservices/schedule/preview', { environment, body: { schedule } })));
|
|
476
476
|
server.registerTool('microservice_schedule_control', {
|
|
477
477
|
title: 'Zamanlamayı yönet',
|
|
478
|
-
description: 'Zamanlanmış microservice\'i duraklatır (pause), sürdürür (resume) ya da cron\'u beklemeden bir kez koşturur (trigger). pause/resume yalnız zamanlayıcıdaki durumu değiştirir, tanımı değil; duraklatma cron güncellemesi ve deployment senkronundan sonra da korunur. resume: duraklatılmışken bir koşu kaçırıldıysa iş bir kez hemen koşar. trigger KUYRUĞA ALIR, sonucu beklemez: deployment ya da template kurulumu sürüyorsa koşu veto edilir ve geçmişe hiç düşmez — sonucu microservice_job_history ile doğrulayın. Zamanlayıcıda kaydı olmayan iş 400 (ScheduleJobNotRegisteredException) döner. Production ortamında üç eylem de 400 CantWriteInProductionException ile reddedilir; trigger ayrıca deployment/template kurulumu sürerken reddedilir.',
|
|
478
|
+
description: 'Zamanlanmış microservice\'i duraklatır (pause), sürdürür (resume) ya da cron\'u beklemeden bir kez koşturur (trigger). pause/resume yalnız zamanlayıcıdaki durumu değiştirir, tanımı değil; duraklatma cron güncellemesi ve deployment senkronundan sonra da korunur. resume: duraklatılmışken bir koşu kaçırıldıysa iş bir kez hemen koşar. trigger KUYRUĞA ALIR, sonucu beklemez: deployment ya da template kurulumu sürüyorsa koşu veto edilir ve geçmişe hiç düşmez — sonucu microservice_job_history ile doğrulayın. Geçmiş failed satırında patlayan adımı ve sebebini yazar ama adım izi vermez; tetiklemeden ÖNCE akışı workflow_preview / microservice_debug ile koşturun. Zamanlayıcıda kaydı olmayan iş 400 (ScheduleJobNotRegisteredException) döner. Production ortamında üç eylem de 400 CantWriteInProductionException ile reddedilir; trigger ayrıca deployment/template kurulumu sürerken reddedilir.',
|
|
479
479
|
inputSchema: {
|
|
480
480
|
project: projectParam,
|
|
481
481
|
environment: environmentParam,
|