@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
|
@@ -0,0 +1,199 @@
|
|
|
1
|
+
import { unstoredKeys } from '../platform/writeDiff.js';
|
|
2
|
+
import { logStep } from '../observability/trace.js';
|
|
3
|
+
import { fileURLToPath } from 'node:url';
|
|
4
|
+
import { readFileSync } from 'node:fs';
|
|
5
|
+
import { join, dirname } from 'node:path';
|
|
6
|
+
const schemasDirectory = join(dirname(fileURLToPath(import.meta.url)), '..', 'resources', 'schemas');
|
|
7
|
+
const documents = new Map();
|
|
8
|
+
/**
|
|
9
|
+
* Yazma gövdesindeki TANINMAYAN anahtarı platform sessizce yok sayar: `isUnique` gönderilen bir
|
|
10
|
+
* index UNIQUE OLMADAN kurulur, istek 200 döner (ölçüldü 2026-09-07, `ENT-013`).
|
|
11
|
+
*
|
|
12
|
+
* Backend bunun için `X-Unmapped-Members` başlığını yazıyor ama kanal olarak KULLANILAMAZ:
|
|
13
|
+
* yalnız İLK eşleşmeyen anahtarı bildiriyor ve gateway'in GET→PUT gidiş-dönüşü gövdeye yedi
|
|
14
|
+
* yapısal anahtar koyuyor (`slug` · `datasourceId` · `primaryKey` · `type` · `applications` ·
|
|
15
|
+
* `automationCount` · `audit`). Sırada `slug` önce geldiği için başlık sıradan bir `entity_patch`
|
|
16
|
+
* çağrısında hep `slug` der ve ajanın yazdığı `indexes[0].isUnique` ASLA görünmez — kanal
|
|
17
|
+
* sinyali maskeler (g-03 Adım 0 ölçümü).
|
|
18
|
+
*
|
|
19
|
+
* Bu yüzden denetim gateway'in kendi taşıdığı şemalardan yapılır: çevrimdışı çalışır, TÜM
|
|
20
|
+
* anahtarları görür ve `introducedUnmappedMembers` sayesinde ajanın yazdığını gateway'in
|
|
21
|
+
* taşıdığından ayırabilir.
|
|
22
|
+
*/
|
|
23
|
+
function schemaDocument(command) {
|
|
24
|
+
if (documents.has(command))
|
|
25
|
+
return documents.get(command);
|
|
26
|
+
// Ad dosya yoluna giriyor: dizin dışına çıkan ya da uzantı taşıyan bir değer okunmaz.
|
|
27
|
+
const document = /^[A-Za-z0-9_]+$/.test(command)
|
|
28
|
+
? (() => {
|
|
29
|
+
try {
|
|
30
|
+
return JSON.parse(readFileSync(join(schemasDirectory, `${command}.json`), 'utf8'));
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
})()
|
|
36
|
+
: undefined;
|
|
37
|
+
documents.set(command, document);
|
|
38
|
+
return document;
|
|
39
|
+
}
|
|
40
|
+
/** `$ref` ya da tek elemanlı `allOf: [{ $ref }]` sarmalının hedef adı; ikincisi Swashbuckle'ın kısıt/nullable eklenmiş referans için ürettiği biçimdir. */
|
|
41
|
+
function referenceName(node) {
|
|
42
|
+
if (!node || typeof node !== 'object')
|
|
43
|
+
return undefined;
|
|
44
|
+
const record = node;
|
|
45
|
+
if (typeof record.$ref === 'string')
|
|
46
|
+
return record.$ref.replace('#/components/schemas/', '');
|
|
47
|
+
const allOf = record.allOf;
|
|
48
|
+
if (Array.isArray(allOf) && allOf.length === 1)
|
|
49
|
+
return referenceName(allOf[0]);
|
|
50
|
+
return undefined;
|
|
51
|
+
}
|
|
52
|
+
function resolve(document, node) {
|
|
53
|
+
if (!node || typeof node !== 'object')
|
|
54
|
+
return undefined;
|
|
55
|
+
const record = node;
|
|
56
|
+
const reference = record.$ref;
|
|
57
|
+
if (typeof reference === 'string') {
|
|
58
|
+
// Üretilen şemalarda tek biçim var: '#/components/schemas/<tam ad>'.
|
|
59
|
+
const name = reference.replace('#/components/schemas/', '');
|
|
60
|
+
const components = document.components?.schemas;
|
|
61
|
+
const target = components?.[name];
|
|
62
|
+
return target && typeof target === 'object' ? target : undefined;
|
|
63
|
+
}
|
|
64
|
+
// FluentValidation kısıtları swagger'a aktığından beri (2026-09-08) referanslı bir özellik
|
|
65
|
+
// `allOf: [{ $ref }]` + kendi anahtarları (nullable, minLength…) şeklinde gelebiliyor; hedef
|
|
66
|
+
// çözülür, özelliğin kendi anahtarları üstüne yazılır.
|
|
67
|
+
const allOf = record.allOf;
|
|
68
|
+
if (Array.isArray(allOf) && allOf.length === 1) {
|
|
69
|
+
const inner = resolve(document, allOf[0]);
|
|
70
|
+
const { allOf: _dropped, ...own } = record;
|
|
71
|
+
return inner ? { ...inner, ...own } : undefined;
|
|
72
|
+
}
|
|
73
|
+
return record;
|
|
74
|
+
}
|
|
75
|
+
function walk(document, schema, value, path, into) {
|
|
76
|
+
if (!schema || !value || typeof value !== 'object')
|
|
77
|
+
return;
|
|
78
|
+
if (Array.isArray(value)) {
|
|
79
|
+
const items = resolve(document, schema.items);
|
|
80
|
+
value.forEach((entry, index) => walk(document, items, entry, `${path}[${index}]`, into));
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const properties = schema.properties;
|
|
84
|
+
if (!properties)
|
|
85
|
+
return;
|
|
86
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
87
|
+
const child = path === '' ? key : `${path}.${key}`;
|
|
88
|
+
// `additionalProperties: false` olmayan bir düğümde serbest anahtar meşrudur (ör. `properties`
|
|
89
|
+
// alanı gibi şemasız sözlükler) — orada susmak yanlış alarmı önler.
|
|
90
|
+
if (!Object.hasOwn(properties, key)) {
|
|
91
|
+
// `writeDiff`'in gerekçeli istisnaları burada da geçerli: gateway'in bilerek gönderdiği,
|
|
92
|
+
// platformun `fieldId`'den çözüp saklamadığı kolaylık alanları hata değildir. Tek küme
|
|
93
|
+
// tutulur — ikiye ayrılırsa ayrışırlar.
|
|
94
|
+
if (schema.additionalProperties === false && !unstoredKeys.has(key))
|
|
95
|
+
into.push(child);
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
walk(document, resolve(document, properties[key]), entry, child, into);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* `command` üretilen şema dosyasının adıdır (`ngapps://schemas/<command>`); bilinmeyen ad
|
|
103
|
+
* sessizce boş liste döner — şema yoksa iddia da yoktur.
|
|
104
|
+
*/
|
|
105
|
+
export function unmappedMembers(command, body) {
|
|
106
|
+
const document = schemaDocument(command);
|
|
107
|
+
if (!document)
|
|
108
|
+
return [];
|
|
109
|
+
const found = [];
|
|
110
|
+
walk(document, document.schema, body, '', found);
|
|
111
|
+
return found;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Yamanın GETİRDİĞİ eşleşmeyen anahtarları döner. Gateway'in GET'ten taşıdığı yapısal alanlar iki
|
|
115
|
+
* tarafta da bulunduğu için elenir; geriye yalnız çağıranın eklediği kalır.
|
|
116
|
+
*/
|
|
117
|
+
export function introducedUnmappedMembers(command, before, after) {
|
|
118
|
+
const existing = new Set(unmappedMembers(command, before));
|
|
119
|
+
return unmappedMembers(command, after).filter(member => !existing.has(member));
|
|
120
|
+
}
|
|
121
|
+
/**
|
|
122
|
+
* Yanıt yan kanalı (`runtimeErrors` · `gatewayMasked` · `silentlyDropped` ile aynı desen).
|
|
123
|
+
* Alan adı `secretKeyPattern` ile eşleşmemeli, yoksa değeri sessizce `***` olur — GW-001 dersi;
|
|
124
|
+
* `entities.test.ts` bunu yanıt üzerinde doğruluyor.
|
|
125
|
+
*/
|
|
126
|
+
export function withUnmappedMembers(payload, members) {
|
|
127
|
+
if (members.length === 0 || !payload || typeof payload !== 'object')
|
|
128
|
+
return payload;
|
|
129
|
+
logStep('şema ✕', `${members.length} anahtar komut şemasında yok — platform onları sessizce yok sayar`, members);
|
|
130
|
+
return {
|
|
131
|
+
...payload,
|
|
132
|
+
unmappedMembers: members,
|
|
133
|
+
unmappedMembersNote: "Bu anahtarlar gönderdiğiniz komutun şemasında YOK: platform onları hata vermeden YOK SAYAR, yani o alanlar kaydedilmedi. Doğru adlar için ngapps://schemas kaynağına bakın."
|
|
134
|
+
};
|
|
135
|
+
}
|
|
136
|
+
const numericKinds = ['minLength', 'maxLength', 'minimum', 'maximum', 'minItems'];
|
|
137
|
+
function limitOf(path, schema, required) {
|
|
138
|
+
const limit = { path };
|
|
139
|
+
if (required)
|
|
140
|
+
limit.required = true;
|
|
141
|
+
for (const kind of numericKinds) {
|
|
142
|
+
if (typeof schema[kind] === 'number')
|
|
143
|
+
limit[kind] = schema[kind];
|
|
144
|
+
}
|
|
145
|
+
if (typeof schema.pattern === 'string' && schema.pattern !== '')
|
|
146
|
+
limit.pattern = schema.pattern;
|
|
147
|
+
return Object.keys(limit).length > 1 ? limit : undefined;
|
|
148
|
+
}
|
|
149
|
+
function collectSchemaLimits(document, schema, path, chain, into) {
|
|
150
|
+
const properties = schema?.properties;
|
|
151
|
+
if (!schema || !properties)
|
|
152
|
+
return;
|
|
153
|
+
const required = new Set(Array.isArray(schema.required) ? schema.required.map(String) : []);
|
|
154
|
+
for (const [key, raw] of Object.entries(properties)) {
|
|
155
|
+
const childPath = path === '' ? key : `${path}.${key}`;
|
|
156
|
+
const child = resolve(document, raw);
|
|
157
|
+
if (!child)
|
|
158
|
+
continue;
|
|
159
|
+
const limit = limitOf(childPath, child, required.has(key));
|
|
160
|
+
if (limit)
|
|
161
|
+
into.push(limit);
|
|
162
|
+
if (child.type === 'array') {
|
|
163
|
+
const items = resolve(document, child.items);
|
|
164
|
+
if (!items)
|
|
165
|
+
continue;
|
|
166
|
+
const itemLimit = limitOf(`${childPath}[]`, items, false);
|
|
167
|
+
if (itemLimit)
|
|
168
|
+
into.push(itemLimit);
|
|
169
|
+
const itemsName = referenceName(child.items);
|
|
170
|
+
// Özyineli tip (FieldDto.fields → FieldDto) bir kez yürünür; katalog da yalnız ilk seviyeyi tanır.
|
|
171
|
+
if (itemsName !== undefined && chain.includes(itemsName))
|
|
172
|
+
continue;
|
|
173
|
+
collectSchemaLimits(document, items, `${childPath}[]`, itemsName !== undefined ? [...chain, itemsName] : chain, into);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
const childName = referenceName(raw);
|
|
177
|
+
if (childName !== undefined && chain.includes(childName))
|
|
178
|
+
continue;
|
|
179
|
+
collectSchemaLimits(document, child, childPath, childName !== undefined ? [...chain, childName] : chain, into);
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
/** Şema dokümanındaki (`{ schema, components }`) bütün alan kısıtlarını katalog yol notasyonuyla (`fields[].name`) döner. */
|
|
183
|
+
export function schemaLimitsOf(document) {
|
|
184
|
+
if (!document)
|
|
185
|
+
return [];
|
|
186
|
+
const found = [];
|
|
187
|
+
const rootName = referenceName(document.schema);
|
|
188
|
+
collectSchemaLimits(document, resolve(document, document.schema), '', rootName !== undefined ? [rootName] : [], found);
|
|
189
|
+
return found;
|
|
190
|
+
}
|
|
191
|
+
/**
|
|
192
|
+
* Paketlenmiş swagger şemasından FluentValidation kaynaklı kısıtlar (`MicroElements.Swashbuckle.FluentValidation`,
|
|
193
|
+
* backend 2026-09-08): NotEmpty → required (+ minLength 1), MaximumLength → maxLength, Matches → pattern,
|
|
194
|
+
* InclusiveBetween → minimum/maximum. `npm run generate-schemas` yenilenmeden eski şemada kısıt yoktur ve
|
|
195
|
+
* bu liste boş döner — o durumda `backendConstraints.ts` kataloğu tek başına konuşur.
|
|
196
|
+
*/
|
|
197
|
+
export function schemaLimits(command) {
|
|
198
|
+
return schemaLimitsOf(schemaDocument(command));
|
|
199
|
+
}
|
|
@@ -156,7 +156,8 @@ export const STYLE_GROUP_FIELDS = {
|
|
|
156
156
|
'borderBottom',
|
|
157
157
|
'borderLeft',
|
|
158
158
|
'shadow',
|
|
159
|
-
'contentOverflow'
|
|
159
|
+
'contentOverflow',
|
|
160
|
+
'opacity'
|
|
160
161
|
],
|
|
161
162
|
typographyStyle: [
|
|
162
163
|
'textColor',
|
|
@@ -171,6 +172,8 @@ export const STYLE_GROUP_FIELDS = {
|
|
|
171
172
|
],
|
|
172
173
|
hoverStyle: ['background', 'borderColor', 'shadow', 'lift', 'transitionMs']
|
|
173
174
|
};
|
|
175
|
+
/** `overflowTarget` için "kökün kendisi" işareti (frontend `ROOT_TARGET` kopyası). */
|
|
176
|
+
export const ROOT_TARGET = '&';
|
|
174
177
|
export function composeTypedStyle(input, ext) {
|
|
175
178
|
const groups = {};
|
|
176
179
|
if (isPlainObject(input.surfaceStyle)) {
|
|
@@ -198,8 +201,19 @@ export function composeTypedStyle(input, ext) {
|
|
|
198
201
|
groups.border = `1px ${surface.borderStyle || 'solid'} ${surface.borderColor || DEFAULT_DIVIDER_COLOR}`;
|
|
199
202
|
}
|
|
200
203
|
}
|
|
204
|
+
// Opaklık yüzdedir (eski Surface bloğuyla aynı sözleşme): 100 ve boş
|
|
205
|
+
// yazılmaz, aralık dışı yok sayılır.
|
|
206
|
+
const opacity = asNumber(surface.opacity);
|
|
207
|
+
if (opacity !== undefined && opacity >= 0 && opacity < 100) {
|
|
208
|
+
groups.opacity = String(opacity / 100);
|
|
209
|
+
}
|
|
210
|
+
// `ROOT_TARGET` (`&`) kökün kendisidir: kendi derleyicisi olmayan kaplarda
|
|
211
|
+
// yüzey düğüm kutusuna yazılır ve kaydırılan da odur.
|
|
201
212
|
if (isSet(surface.contentOverflow) && ext?.overflowTarget) {
|
|
202
|
-
|
|
213
|
+
if (ext.overflowTarget === ROOT_TARGET)
|
|
214
|
+
groups.overflow = surface.contentOverflow;
|
|
215
|
+
else
|
|
216
|
+
nest(groups, ext.overflowTarget, { overflow: surface.contentOverflow });
|
|
203
217
|
}
|
|
204
218
|
}
|
|
205
219
|
if (isPlainObject(input.typographyStyle)) {
|
|
@@ -414,6 +428,44 @@ export const TYPED_STYLE_COMPONENTS = {
|
|
|
414
428
|
omit: { hoverStyle: ['borderColor'] }
|
|
415
429
|
}
|
|
416
430
|
};
|
|
431
|
+
/**
|
|
432
|
+
* Kendi derleyicisi OLMAYAN kap bileşenleri (2026-09-04): şemalarında
|
|
433
|
+
* `surfaceStyle` grubu var ama bileşen prop'u okumaz — render katmanı yüzeyi
|
|
434
|
+
* düğüm kutusuna (`.ng-app__node-item`) genel yolla yazar, taşma kökün
|
|
435
|
+
* kendisine gider (`packages/ng-apps-ui/src/style-groups/node-surface.ts`).
|
|
436
|
+
* Bu tipler `TYPED_STYLE_COMPONENTS`'a GİRMEZ: `style` kaçış kanalı onlarda
|
|
437
|
+
* eski birleşik kaskadla (tema+preset+own) çözülmeye devam eder.
|
|
438
|
+
*/
|
|
439
|
+
export const NODE_SURFACE_TYPES = new Set([
|
|
440
|
+
'table',
|
|
441
|
+
'datatable',
|
|
442
|
+
'form',
|
|
443
|
+
'collapsible-container',
|
|
444
|
+
'drawer',
|
|
445
|
+
'modal',
|
|
446
|
+
'popover',
|
|
447
|
+
'tabs',
|
|
448
|
+
'stepper',
|
|
449
|
+
'repeater',
|
|
450
|
+
'html',
|
|
451
|
+
'file-viewer',
|
|
452
|
+
'document-editor',
|
|
453
|
+
'layout',
|
|
454
|
+
'layout-region',
|
|
455
|
+
'layout-shell',
|
|
456
|
+
'qm-content',
|
|
457
|
+
'qm-sidebar',
|
|
458
|
+
'auth-layout'
|
|
459
|
+
]);
|
|
460
|
+
const NODE_SURFACE_EXTENSION = { overflowTarget: ROOT_TARGET };
|
|
461
|
+
/** Düğüm kutusuna inen yüzey; kap tipi değilse ya da grup boşsa yok. */
|
|
462
|
+
export function composeNodeSurfaceStyle(type, surfaceStyle) {
|
|
463
|
+
if (!type || !NODE_SURFACE_TYPES.has(type))
|
|
464
|
+
return undefined;
|
|
465
|
+
if (!isPlainObject(surfaceStyle))
|
|
466
|
+
return undefined;
|
|
467
|
+
return composeTypedStyle({ surfaceStyle }, NODE_SURFACE_EXTENSION);
|
|
468
|
+
}
|
|
417
469
|
/** text derlemesi — testler ve dış tüketiciler için sarmalayıcı. */
|
|
418
470
|
export function composeTextStyle(input) {
|
|
419
471
|
return composeTypedStyle(input, TYPED_STYLE_COMPONENTS.text.ext);
|
|
@@ -467,9 +519,16 @@ export function resolveNodeStyleReport(node, source) {
|
|
|
467
519
|
own: isPlainObject(ownStyle) ? Object.keys(ownStyle).filter(key => key !== STYLE_PRESET_KEY).length > 0 : Boolean(ownStyle)
|
|
468
520
|
};
|
|
469
521
|
if (!config) {
|
|
470
|
-
// Kayıtsız tip: eski birleşik kaskad.
|
|
522
|
+
// Kayıtsız tip: eski birleşik kaskad. Kap tiplerinde tipli yüzey grubu
|
|
523
|
+
// düğüm kutusuna ayrıca iner; rapor onu kaskadın ÜSTÜNE koyar (kutu,
|
|
524
|
+
// bileşenin kökünü sarar).
|
|
471
525
|
const cascaded = resolveNodeStyle(ownStyle, source, node.type);
|
|
472
|
-
|
|
526
|
+
const nodeSurface = composeNodeSurfaceStyle(node.type, rawProp(props, 'surfaceStyle'));
|
|
527
|
+
const typedLayers = nodeSurface ? { ...layers, typedGroups: ['surfaceStyle'] } : layers;
|
|
528
|
+
const resolved = cascaded === undefined && nodeSurface === undefined
|
|
529
|
+
? undefined
|
|
530
|
+
: deepMergeStyle((cascaded ?? {}), nodeSurface ?? {});
|
|
531
|
+
return resolved === undefined ? { styleLayers: typedLayers } : { resolvedStyle: resolved, styleLayers: typedLayers };
|
|
473
532
|
}
|
|
474
533
|
// Tipli komponent gruplar boşken de tipli yoldan geçer: uzantı emekli
|
|
475
534
|
// prop'ları okuyabilir (text'in isBold/align/color takma adı) ve K4 sırası
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { AsyncLocalStorage } from 'node:async_hooks';
|
|
2
2
|
import { appendFileSync, existsSync, mkdirSync } from 'node:fs';
|
|
3
3
|
import { dirname, join } from 'node:path';
|
|
4
|
+
import { secretKeyPattern } from '../platform/secrets.js';
|
|
4
5
|
const defaultDirectory = join(process.cwd(), 'logs');
|
|
5
6
|
const activeTrace = new AsyncLocalStorage();
|
|
6
7
|
const activeRequest = new AsyncLocalStorage();
|
|
7
8
|
const inFlight = new Set();
|
|
8
|
-
const secretKeyPattern = /pass|secret|token|authorization|api[-_]?key|credential|connectionstring/i;
|
|
9
9
|
const detailIndent = 26;
|
|
10
10
|
let level = 'off';
|
|
11
11
|
let logFile;
|
|
@@ -180,6 +180,25 @@ function renderTrace(trace, outcome) {
|
|
|
180
180
|
lines.push('');
|
|
181
181
|
return `${lines.join('\n')}\n`;
|
|
182
182
|
}
|
|
183
|
+
/**
|
|
184
|
+
* `runtimeErrors` kayıtlarını DÜZ METNE çevirir. Eskiden `runtimeErrors.map(String)` yazılıyordu
|
|
185
|
+
* ve nesne `[object Object]`e düşüyordu: 2026-09-06 koşusunda tool mesajı hangi adımın neden
|
|
186
|
+
* düştüğünü söylemiyordu, oysa doğru cümle aynı yanıtın içinde duruyordu (`S-4`).
|
|
187
|
+
*
|
|
188
|
+
* Yalnız İLK hata yazılır ve kalan sayılır — mesaj bir özet satırıdır, liste değil. Kullanıcı
|
|
189
|
+
* verisi taşıyabildiği için cümle 300 karakterde kesilir; tamamı günlüktedir.
|
|
190
|
+
*/
|
|
191
|
+
export function describeRuntimeErrors(errors) {
|
|
192
|
+
const first = (errors[0] ?? {});
|
|
193
|
+
const where = [first.step?.name, first.element ? `${first.element}${first.property ? `.${first.property}` : ''}` : undefined]
|
|
194
|
+
.filter(Boolean)
|
|
195
|
+
.join(' → ');
|
|
196
|
+
const message = typeof first.message === 'string' && first.message.length > 0
|
|
197
|
+
? first.message
|
|
198
|
+
: JSON.stringify(first);
|
|
199
|
+
const rest = errors.length > 1 ? ` (+${errors.length - 1} hata daha)` : '';
|
|
200
|
+
return `${where ? `${where} — ` : ''}${clip(message, 300)}${rest} · tamamı günlükte`;
|
|
201
|
+
}
|
|
183
202
|
function describeResult(result) {
|
|
184
203
|
const call = result;
|
|
185
204
|
const text = Array.isArray(call?.content)
|
|
@@ -187,9 +206,14 @@ function describeResult(result) {
|
|
|
187
206
|
: '';
|
|
188
207
|
if (call?.isError)
|
|
189
208
|
return { ok: false, summary: clip(text, 400) };
|
|
190
|
-
const
|
|
209
|
+
const structured = call?.structuredContent;
|
|
210
|
+
const runtimeErrors = structured?.runtimeErrors;
|
|
191
211
|
if (Array.isArray(runtimeErrors) && runtimeErrors.length > 0) {
|
|
192
|
-
|
|
212
|
+
// Kasıtlı negatif test bir kusur değil bir KANIT: çağıran beklediğini söylediyse satır
|
|
213
|
+
// başarılı sayılır ve ölçüm aracının plansız hata oranını şişirmez (S-4).
|
|
214
|
+
return structured?.expectedFailure === true
|
|
215
|
+
? { ok: true, summary: `beklenen çalışma anı hatası (negatif test): ${describeRuntimeErrors(runtimeErrors)}` }
|
|
216
|
+
: { ok: false, summary: `çalışma anı hatası (platform HTTP 200 döndü): ${describeRuntimeErrors(runtimeErrors)}` };
|
|
193
217
|
}
|
|
194
218
|
const effect = summarizeEffect(text);
|
|
195
219
|
return { ok: true, summary: `${Buffer.byteLength(text, 'utf8')} B yanıt${effect ? ` · ${effect}` : ''}` };
|
package/dist/platform/errors.js
CHANGED
|
@@ -45,8 +45,12 @@ function collectValidationMessages(payload) {
|
|
|
45
45
|
* Sayfa şeması patch'inin üç reddi de "hiçbir op uygulanmadı" anlamına gelir (uygulayıcı önce tüm
|
|
46
46
|
* op'ları bellekte işler, tek hata bile tüm komutu düşürür) — ajanın kısmi uygulama varsayıp
|
|
47
47
|
* planın kalanını göndermesi bu yüzden yanlış olur.
|
|
48
|
+
*
|
|
49
|
+
* Aynı duvar 2026-09-08'den beri sayfa/layout KURMA yolunda da koşuyor (P1-2: layout hattı aynı
|
|
50
|
+
* hatalı prop'ları sessizce kaydediyordu). Orada op listesi yoktur ve `page_schema_validate` de
|
|
51
|
+
* uygulanmaz — ipucu bu yüzden yola göre ayrışır.
|
|
48
52
|
*/
|
|
49
|
-
function pageSchemaHint(code) {
|
|
53
|
+
function pageSchemaHint(code, path) {
|
|
50
54
|
if (code === 'PageSchemaVersionConflictException') {
|
|
51
55
|
return ' Sayfa siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı. page_schema_get ile yeniden okuyup planı tazeleyin, yeni `version` değerini `baseVersion` olarak gönderin.';
|
|
52
56
|
}
|
|
@@ -54,10 +58,42 @@ function pageSchemaHint(code) {
|
|
|
54
58
|
return ' Sayfa şeması başka bir aktörün kilidinde (kilit sahibi mesajda; kilit 60 sn sonra kendiliğinden düşer). Bekleyip tekrar deneyin — aynı anda iki yazar olduğunda kaybeden taraf sessizce ezilmesin diye kilit var.';
|
|
55
59
|
}
|
|
56
60
|
if (code === 'PageSchemaValidationException') {
|
|
57
|
-
return '
|
|
61
|
+
return path.endsWith('/schema')
|
|
62
|
+
? ' Op listesi doğrulama duvarına takıldı ve HİÇBİR op uygulanmadı (hepsi ya da hiçbiri). Alan hatalarını düzeltip tek çağrıda tekrar gönderin; planı önce page_schema_validate ile kuru koşturabilirsiniz.'
|
|
63
|
+
: ' Gövde doğrulama duvarına takıldı ve HİÇBİR ŞEY KAYDEDİLMEDİ. Yukarıdaki her satır bir düğüm/prop adresi ve kabul edilen değer listesi taşır — prop adlarını component_get ile doğrulayıp gövdeyi tek çağrıda düzeltin.';
|
|
58
64
|
}
|
|
59
65
|
return '';
|
|
60
66
|
}
|
|
67
|
+
/**
|
|
68
|
+
* Toplu satır yazma tek transaction: platform hangi satırın düştüğünü mesajda söyler
|
|
69
|
+
* (`Row {n} could not be written: …`), ama "geri kalanı yazıldı mı" sorusunu söylemez. Ajan
|
|
70
|
+
* kısmi yazma varsayıp yalnız düşen satırı yeniden göndermesin diye all-or-nothing burada yazılı.
|
|
71
|
+
*/
|
|
72
|
+
function bulkRowHint(code) {
|
|
73
|
+
if (code === 'BulkRowFailedException') {
|
|
74
|
+
return " Toplu yazma TEK TRANSACTION: bu çağrıdan HİÇBİR satır yazılmadı (yalnız id sekansı ilerledi). Mesajdaki satır numarası 0'dan başlar ve gönderdiğiniz `rows` dizisinin indeksidir. O satırı düzeltip TÜM diziyi yeniden gönderin.";
|
|
75
|
+
}
|
|
76
|
+
if (code === 'BulkRowLimitException') {
|
|
77
|
+
return ' Diziyi 1000 satırlık parçalara bölüp sırayla gönderin; her parça kendi transaction\'ında yazılır.';
|
|
78
|
+
}
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
/**
|
|
82
|
+
* PATCH uçlarında `baseVersion` zorunlu ve kayıt okumanızdan beri değiştiyse HİÇBİR op uygulanmaz.
|
|
83
|
+
* Ajanın "tekrar dene" refleksi burada yanlış: aynı ops'u yeni sürümle göndermek başkasının
|
|
84
|
+
* yazdığını ezer, önce yeniden okunmalı.
|
|
85
|
+
*/
|
|
86
|
+
function versionConflictHint(code, path) {
|
|
87
|
+
if (code !== 'AggregateVersionConflictException')
|
|
88
|
+
return '';
|
|
89
|
+
const readTool = /\/entities(\/|$)/.test(path) ? 'entity_get'
|
|
90
|
+
: /\/workflows(\/|$)/.test(path) ? 'workflow_get'
|
|
91
|
+
: /\/flows(\/|$)/.test(path) ? 'flow_get'
|
|
92
|
+
: /\/layouts(\/|$)/.test(path) ? 'layout_get'
|
|
93
|
+
: /\/microservices(\/|$)/.test(path) ? 'microservice_get'
|
|
94
|
+
: 'ilgili *_get';
|
|
95
|
+
return ` Kayıt siz okuduktan sonra değişti ve HİÇBİR op uygulanmadı (mesajdaki beklenen/mevcut sürüm). ${readTool} ile yeniden okuyup patch'i güncel hâle göre kurun, yeni \`version\` değerini \`baseVersion\` olarak gönderin — aynı ops'u eski sürümle tekrar göndermek aradaki değişikliği ezer.`;
|
|
96
|
+
}
|
|
61
97
|
/**
|
|
62
98
|
* Bilinen platform bug'larını ajanın tool yüzeyinden teşhis edebilmesi için desen eşlemesi
|
|
63
99
|
* (klinik turu dersi: teşhis DB'ye/koda inmeden yapılabilmeli — görev G-09).
|
|
@@ -65,6 +101,12 @@ function pageSchemaHint(code) {
|
|
|
65
101
|
function diagnoseKnownFailure(status, message, code, context) {
|
|
66
102
|
if (status !== 500)
|
|
67
103
|
return '';
|
|
104
|
+
// 2026-09-08'e kadar ExceptionHandler bu istisnayı eşlemiyordu ve slug çakışması 500 dönüyordu
|
|
105
|
+
// (3. koşu S-1 / P3-7); yeni build 409 verir, gateway de POST'tan önce project_list ile arar
|
|
106
|
+
// (PRJ-001). Bu dal eski build'e karşı kalır.
|
|
107
|
+
if (code === 'DatabaseNameAlreadyExistsException' || message.includes('already has this database name')) {
|
|
108
|
+
return " Sebep: proje slug'ı bu organizasyonda zaten kullanımda (veritabanı adı zeyrek_<org>_<slug> tekildir). Çalışan backend bu durumu 409 yerine 500 ile bildiren eski bir build; başka bir slug seçin ya da project_list ile mevcut projeyi bulun (PRJ-001).";
|
|
109
|
+
}
|
|
68
110
|
if (context.path.includes('/row') && message.includes('Sequence contains no matching element')) {
|
|
69
111
|
return " Muhtemel sebep: entity bir application'a bağlı değil — entity_patch ile applicationIds ekleyin (PLT-004).";
|
|
70
112
|
}
|
|
@@ -72,7 +114,7 @@ function diagnoseKnownFailure(status, message, code, context) {
|
|
|
72
114
|
return " Bilinen sınır (K-005/P-01): m2m, çoklu dosya ya da çoklu enum alanı entity_create gövdesindeyken platform junction KAYDINI açar ama fiziksel tabloyu kurmaz. Alanı entity_patch ile yeniden ekleyin; gateway'in entity_create'i bunu artık kendisi iki aşamaya bölüyor, elle kurulmuş eski kayıtlar etkilenir.";
|
|
73
115
|
}
|
|
74
116
|
if (message.includes('cannot be tracked') || message.includes('already being tracked')) {
|
|
75
|
-
return " Bilinen sınır (K-006/P-02): operation:'get' tipli microservice'te PUT, izlenen Application grafiği yüzünden 500
|
|
117
|
+
return " Bilinen sınır (K-006/P-02): operation:'get' tipli microservice'te PUT, izlenen Application grafiği yüzünden 500 veriyordu. Regresyon 2026-08-16'da düzeltildi ve 2026-09-01'de canlı doğrulandı (İK koşusu M-9) — bu hatayı hâlâ görüyorsanız çalışan sürüm düzeltmeden eskidir. O sürümde geçici yol: microservice_delete + microservice_create ile yeniden kurun (gövdeyi önce microservice_get ile alın).";
|
|
76
118
|
}
|
|
77
119
|
if (code === 'RowValidationFailed' || message.includes('RowValidationFailed')) {
|
|
78
120
|
return ' Gövdedeki alanlar doğru görünüyorsa muhtemel sebep istemcinin body alanını düşürmesidir (K-002): microservice_execute/debug çağrısında body bir NESNE ya da nesne DİZİSİ olmalı; boş gövdeyle gönderildiyse istemci şemayı desteklemiyor olabilir.';
|
|
@@ -117,7 +159,7 @@ export function mapPlatformError(status, payload, context) {
|
|
|
117
159
|
: '';
|
|
118
160
|
const validationMessages = collectValidationMessages(body);
|
|
119
161
|
const detail = validationMessages.length > 0 ? ` → ${validationMessages.join(' ; ')}` : '';
|
|
120
|
-
return new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code)}`, status, code);
|
|
162
|
+
return new PlatformApiError(`İş kuralı reddetti (${location}): ${apiMessage ?? 'çakışma'}${code ? ` [${code}]` : ''}${detail}${projectConflictHint}${pageSchemaHint(code, context.path)}${bulkRowHint(code)}${versionConflictHint(code, context.path)}`, status, code);
|
|
121
163
|
}
|
|
122
164
|
const fallbackValidationMessages = collectValidationMessages(body);
|
|
123
165
|
const fallbackDetail = fallbackValidationMessages.length > 0 ? ` → ${fallbackValidationMessages.join(' ; ')}` : '';
|
package/dist/platform/http.js
CHANGED
|
@@ -5,6 +5,17 @@ import { TokenProvider } from './token.js';
|
|
|
5
5
|
import { randomUUID } from 'node:crypto';
|
|
6
6
|
import { request } from 'undici';
|
|
7
7
|
export const requestAuthStorage = new AsyncLocalStorage();
|
|
8
|
+
const hiddenResponseHeaders = new Set(['set-cookie', 'authorization', 'proxy-authorization']);
|
|
9
|
+
/**
|
|
10
|
+
* Sayfalı tablo akışları `Total-Count` header'ına bağlı ama execute ailesi yalnız gövdeyi
|
|
11
|
+
* döndürüyordu; sözleşme hiçbir tool'dan doğrulanamıyordu (İK koşusu M-8). Gizli header'lar
|
|
12
|
+
* her koşulda elenir.
|
|
13
|
+
*/
|
|
14
|
+
export function responseHeadersForTool(raw) {
|
|
15
|
+
return Object.fromEntries(Object.entries(raw)
|
|
16
|
+
.map(([name, value]) => [name.toLowerCase(), Array.isArray(value) ? value.join(', ') : value])
|
|
17
|
+
.filter((entry) => typeof entry[1] === 'string' && !hiddenResponseHeaders.has(entry[0])));
|
|
18
|
+
}
|
|
8
19
|
function buildMultipart(parts, boundary) {
|
|
9
20
|
const chunks = [];
|
|
10
21
|
for (const part of parts) {
|
|
@@ -70,6 +81,18 @@ export class Platform {
|
|
|
70
81
|
});
|
|
71
82
|
return outcome.body;
|
|
72
83
|
}
|
|
84
|
+
/**
|
|
85
|
+
* `tenant` gibi davranır (4xx/5xx atar) ama yanıt başlıklarını da verir. `tenantRaw` bu iş için
|
|
86
|
+
* kullanılamaz: o hata durumunda atmaz, yani satır yazma yolunda 400'ü başarı gibi döndürürdü.
|
|
87
|
+
* Satır araçları başlığa bakmak zorunda — `X-Unmapped-Members` platformun `localizations`
|
|
88
|
+
* alanını tanımadığını söyleyen tek işaret (sözleşme D6, gateway eski platformda companion'a düşer).
|
|
89
|
+
*/
|
|
90
|
+
async tenantWithHeaders(method, project, path, options = {}) {
|
|
91
|
+
return this.send('tenant', method, `/${this.resolveProject(project)}${path}`, {
|
|
92
|
+
...options,
|
|
93
|
+
environment: this.resolveEnvironment(options.environment)
|
|
94
|
+
});
|
|
95
|
+
}
|
|
73
96
|
async tenantRaw(method, project, path, options = {}) {
|
|
74
97
|
return this.send('tenant', method, `/${this.resolveProject(project)}${path}`, {
|
|
75
98
|
...options,
|
|
@@ -95,12 +118,16 @@ export class Platform {
|
|
|
95
118
|
const url = new URL(`${baseUrl}${path}`);
|
|
96
119
|
appendQuery(url, options.query);
|
|
97
120
|
const callerHeaders = Object.fromEntries(Object.entries(options.headers ?? {}).map(([name, value]) => [name.toLowerCase(), value]));
|
|
121
|
+
const authorization = callerHeaders.authorization ?? (options.anonymous ? undefined : await this.authorizationHeader());
|
|
98
122
|
const headers = {
|
|
99
123
|
host: `${this.config.organization}.${this.config.hostSuffix}`,
|
|
100
124
|
accept: 'application/json',
|
|
101
|
-
...(
|
|
125
|
+
...(authorization === undefined ? {} : { authorization }),
|
|
102
126
|
...(options.environment ? { 'x-environment': options.environment } : {}),
|
|
103
|
-
...callerHeaders
|
|
127
|
+
...callerHeaders,
|
|
128
|
+
...(options.mirrorAuthorizationCookie && authorization !== undefined && callerHeaders.cookie === undefined
|
|
129
|
+
? { cookie: `NG_AccessToken=${authorization.replace(/^Bearer /, '')}` }
|
|
130
|
+
: {})
|
|
104
131
|
};
|
|
105
132
|
let body;
|
|
106
133
|
if (options.multipart) {
|
|
@@ -127,6 +154,7 @@ export class Platform {
|
|
|
127
154
|
let statusCode;
|
|
128
155
|
let payload;
|
|
129
156
|
let responseBytes = 0;
|
|
157
|
+
let responseHeaders = {};
|
|
130
158
|
try {
|
|
131
159
|
const response = await request(url, {
|
|
132
160
|
method: method,
|
|
@@ -135,6 +163,7 @@ export class Platform {
|
|
|
135
163
|
signal: AbortSignal.timeout(this.config.requestTimeoutMs)
|
|
136
164
|
});
|
|
137
165
|
statusCode = response.statusCode;
|
|
166
|
+
responseHeaders = responseHeadersForTool(response.headers);
|
|
138
167
|
if (options.expectBinary && statusCode < 400) {
|
|
139
168
|
const bytes = Buffer.from(await response.body.arrayBuffer());
|
|
140
169
|
responseBytes = bytes.byteLength;
|
|
@@ -164,9 +193,9 @@ export class Platform {
|
|
|
164
193
|
return this.send(api, method, path, options, raw, true);
|
|
165
194
|
}
|
|
166
195
|
if (raw)
|
|
167
|
-
return { httpStatus: statusCode, body: payload ?? null };
|
|
196
|
+
return { httpStatus: statusCode, body: payload ?? null, headers: responseHeaders };
|
|
168
197
|
if (statusCode >= 400)
|
|
169
198
|
throw mapPlatformError(statusCode, payload, context);
|
|
170
|
-
return { httpStatus: statusCode, body: payload ?? { success: true } };
|
|
199
|
+
return { httpStatus: statusCode, body: payload ?? { success: true }, headers: responseHeaders };
|
|
171
200
|
}
|
|
172
201
|
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export function patchResult(response, fallback) {
|
|
2
|
+
const envelope = response && typeof response === 'object'
|
|
3
|
+
? (response.data ?? response)
|
|
4
|
+
: {};
|
|
5
|
+
const written = envelope.data && typeof envelope.data === 'object'
|
|
6
|
+
? envelope.data
|
|
7
|
+
: fallback;
|
|
8
|
+
return {
|
|
9
|
+
// Sayı olarak gelen bir newVersion JSON.parse sırasında ZATEN yuvarlanmıştır
|
|
10
|
+
// (6.4e16 > MAX_SAFE_INTEGER): geri gönderilirse her patch 409 verir, boş bırakmak dürüst.
|
|
11
|
+
newVersion: typeof envelope.newVersion === 'string' ? envelope.newVersion : '',
|
|
12
|
+
applied: typeof envelope.applied === 'number' ? envelope.applied : 0,
|
|
13
|
+
data: written
|
|
14
|
+
};
|
|
15
|
+
}
|
|
16
|
+
/**
|
|
17
|
+
* `baseVersion` PATCH gövdesinde ZORUNLU. Okunan kayıtta `version` yoksa platform 422 döndürür;
|
|
18
|
+
* ajanın hatayı gateway'e değil eksik alana bağlayabilmesi için burada erken ve açık patlıyoruz.
|
|
19
|
+
*/
|
|
20
|
+
export function baseVersionOf(definition, readTool) {
|
|
21
|
+
const version = definition.version;
|
|
22
|
+
if (typeof version === 'string' && version.length > 0)
|
|
23
|
+
return version;
|
|
24
|
+
if (typeof version === 'number')
|
|
25
|
+
return String(version);
|
|
26
|
+
throw new Error(`Okunan kayıtta 'version' alanı yok; PATCH 'baseVersion' olmadan gönderilemez. ${readTool} çıktısında 'version' görünmüyorsa platform sürümü bu ucu desteklemiyordur.`);
|
|
27
|
+
}
|