@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7
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 +2 -2
- package/.env.production.example +9 -1
- package/README.md +14 -38
- package/dist/config.js +36 -3
- package/dist/knowledge/backendConstraints.js +982 -0
- package/dist/knowledge/componentCatalog.js +549 -92
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/functionGuards.js +134 -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 +500 -16
- package/dist/knowledge/outlines.js +167 -12
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +961 -44
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +107 -6
- package/dist/platform/http.js +34 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +25 -6
- package/dist/platform/screenshot.js +544 -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/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +3 -3
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +4 -3
- package/dist/resources/guides/component.md +81 -8
- package/dist/resources/guides/entity.md +712 -20
- package/dist/resources/guides/expressions.md +47 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +82 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +287 -15
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +402 -49
- package/dist/resources/guides/translation.md +150 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +199 -28
- 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/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +36 -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/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
- 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 +106 -14
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +6 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +34 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
- 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 +106 -14
- package/dist/server.js +17 -1
- package/dist/tools/access.js +3 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +76 -34
- package/dist/tools/common.js +55 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/environments.js +52 -0
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +57 -42
- package/dist/tools/flows.js +205 -13
- package/dist/tools/folders.js +7 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +270 -40
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +421 -104
- package/dist/tools/platformInfo.js +155 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +484 -50
- package/dist/tools/screenWrite.js +68 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/templates.js +32 -5
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +56 -21
- package/package.json +7 -2
|
@@ -6,10 +6,17 @@ export const componentEventContracts = {
|
|
|
6
6
|
pagination: { payload: { offset: 'number', limit: 'number' } },
|
|
7
7
|
filter: { payload: { filters: 'Array<{ field, operator, value, filterType, conditionType }>', offset: 'number' } },
|
|
8
8
|
sort: { payload: { sortField: 'string (kolonun name alanı)', sortType: "'asc' | 'desc' | '' (temizlendi)", offset: 'number' } },
|
|
9
|
-
|
|
10
|
-
|
|
9
|
+
search: {
|
|
10
|
+
payload: {
|
|
11
|
+
searchText: 'string (kutuya yazılan metin, KIRPILMAZ; kutu temizlenince "")',
|
|
12
|
+
offset: 'number (0’a döner)'
|
|
13
|
+
},
|
|
14
|
+
reads: 'isSearchable:true iken araç çubuğundaki kutudan: yazmaya ara verince (400 ms), Enter’da ve kutu boşaltılınca hemen yayılır; aynı metin ikinci kez yayılmaz (Enter dahil). Enter formu göndermez. Tablo satırları kendisi SÜZMEZ — akış self.searchText ile sorguyu yeniden çalıştırıp rows’u yazar'
|
|
15
|
+
},
|
|
16
|
+
select: { payload: {}, reads: 'self.selectedRow — satır tıklaması rowSelection:none dahil HER kipte yazar ve bu olayı çalıştırır; isReadonly:true iken hiç çalışmaz' },
|
|
17
|
+
selectionChange: { payload: {}, reads: 'self.selectedRows (dizi) + self.selectedRow — yalnız multi kipindeki onay kutularından (satır tıklaması tetiklemez)' },
|
|
11
18
|
doubleClick: { payload: {}, reads: 'self.selectedRow' },
|
|
12
|
-
rowChange: { payload: {}, reads: 'self.
|
|
19
|
+
rowChange: { payload: {}, reads: 'self.selectedRow (düzenlenen satır, YENİ değeriyle) — satır nesnesi self.rows içinde de yerinde güncellenir; değer gerçekten değişmediyse olay çalışmaz' }
|
|
13
20
|
},
|
|
14
21
|
datatable: {
|
|
15
22
|
mount: { payload: {}, reads: 'self.filters · self.sortField · self.sortType · self.offset · self.limit' }
|
|
@@ -32,8 +39,23 @@ export const componentEventContracts = {
|
|
|
32
39
|
},
|
|
33
40
|
phoneinput: {
|
|
34
41
|
mount: NO_DATA,
|
|
35
|
-
change: {
|
|
36
|
-
|
|
42
|
+
change: {
|
|
43
|
+
payload: {
|
|
44
|
+
value: 'string (E.164, ör. +905321234567)',
|
|
45
|
+
formattedValue: 'string (kullanıcının gördüğü satır: "+90 532 123 45 67"; boş alanda "")',
|
|
46
|
+
isValid: 'boolean (numara seçili ülke için TAM mı — boş alan false; zorunluluk kararı validations.required’ın işi)'
|
|
47
|
+
},
|
|
48
|
+
reads: "self’e yazılan tek anahtar `value`dur; `formattedValue` ve `isValid` düğüme YAZILMAZ, yalnız event.data’dadır (params). DEĞER DEĞİŞMEDİYSE YAYILMAZ — hane sınırına çarpan tuşlar ve süzülen harfler akış tetiklemez"
|
|
49
|
+
},
|
|
50
|
+
enter: {
|
|
51
|
+
payload: { value: 'string (E.164)', formattedValue: 'string', isValid: 'boolean' },
|
|
52
|
+
reads: 'Enter tuşu; change ile aynı yük. self.value’yu bu da yazar (platform emisyonun data’sını koşulsuz yazıyor, ARC-067) ama değer zaten change’in yazdığıdır — "gönder" kancası olarak kullan'
|
|
53
|
+
},
|
|
54
|
+
focus: NO_DATA,
|
|
55
|
+
blur: {
|
|
56
|
+
payload: { value: 'string (E.164)', formattedValue: 'string', isValid: 'boolean' },
|
|
57
|
+
reads: 'focus/blur SATIR düzeyindedir: numara alanı ile ülke seçicisi arasında gezinmek olay yaymaz, yalnız alanın tamamına girip çıkmak yayar. blur change’in aksine değer aynı olsa da yayılır ve yayılmış bir focus’un karşılığı alan sonradan donsa bile gelir; yükü self.value’yu yine yazar (ARC-067)'
|
|
58
|
+
}
|
|
37
59
|
},
|
|
38
60
|
filterbuilder: {
|
|
39
61
|
mount: NO_DATA,
|
|
@@ -62,24 +84,81 @@ export const componentEventContracts = {
|
|
|
62
84
|
},
|
|
63
85
|
'layout-region': { mount: NO_DATA },
|
|
64
86
|
navigations: { mount: NO_DATA },
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
87
|
+
// `change` her tuşta (input), `changed` değer kesinleşince (native change: blur/Enter) yayılır;
|
|
88
|
+
// ikisi de setter'dır — pahalı akışı (sorgu, kayıt) `changed`'e bağla.
|
|
89
|
+
textinput: {
|
|
90
|
+
mount: NO_DATA,
|
|
91
|
+
change: valueChange('string'),
|
|
92
|
+
changed: valueChange('string (blur/Enter ile kesinleşen değer)'),
|
|
93
|
+
enter: { payload: { value: 'string (Enter anındaki değer)' }, reads: "form içindeyse Enter formun submit akışını da çalıştırır — kaydı forma bağla, enter'a değil" },
|
|
94
|
+
focus: NO_DATA,
|
|
95
|
+
blur: NO_DATA
|
|
96
|
+
},
|
|
97
|
+
textarea: { mount: NO_DATA, change: valueChange('string'), changed: valueChange('string (blur ile kesinleşen değer)'), focus: NO_DATA, blur: NO_DATA },
|
|
98
|
+
password: {
|
|
99
|
+
mount: NO_DATA,
|
|
100
|
+
change: valueChange('string'),
|
|
101
|
+
changed: valueChange('string (blur/Enter ile kesinleşen değer)'),
|
|
102
|
+
enter: { payload: { value: 'string (Enter anındaki değer)' }, reads: "form içindeyse Enter formun submit akışını da çalıştırır — girişi forma bağla, enter'a değil" },
|
|
103
|
+
focus: NO_DATA,
|
|
104
|
+
blur: NO_DATA
|
|
105
|
+
},
|
|
106
|
+
// `change` her tuşta ve artı/eksi düğmesinde, `changed` değer kesinleşince (blur/Enter, düğme)
|
|
107
|
+
// yayılır; ikisi de setter'dır — pahalı akışı `changed`'e bağla. Boşaltılan alan 0 yayar (K-3).
|
|
108
|
+
numberinput: {
|
|
109
|
+
mount: NO_DATA,
|
|
110
|
+
change: valueChange('number (aralığa kırpılmış)'),
|
|
111
|
+
changed: valueChange('number (blur/Enter/artı-eksi ile kesinleşen değer)'),
|
|
112
|
+
enter: { payload: { value: 'number (Enter anında kesinleşen, kırpılmış değer)' }, reads: "form içindeyse Enter formun submit akışını da çalıştırır — kaydı forma bağla, enter'a değil" },
|
|
113
|
+
focus: NO_DATA,
|
|
114
|
+
blur: NO_DATA
|
|
115
|
+
},
|
|
71
116
|
incrementalinput: { mount: NO_DATA, change: valueChange('string'), blur: NO_DATA },
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
117
|
+
// Adlar 2026-09-11'de `on` önekinden kurtuldu: şema onMount/onChange diyordu, runtime
|
|
118
|
+
// mount/change yayıyordu ve flow-machine eşleşmeyi ADLA yaptığı için bu bileşene bağlanan
|
|
119
|
+
// hiçbir akış çalışmıyordu.
|
|
120
|
+
texteditor: {
|
|
121
|
+
mount: NO_DATA,
|
|
122
|
+
change: { payload: { value: "string (HTML; boş editörde boş metin)" }, reads: "setter: self.value da yazılır. HER TUŞTA yayılır — pahalı akışı changed'e bağla" },
|
|
123
|
+
changed: { payload: { value: "string (HTML; boş editörde boş metin)" }, reads: "setter: self.value da yazılır; yalnız odak çıkışında ve değer gerçekten değiştiyse yayılır" },
|
|
124
|
+
focus: NO_DATA,
|
|
125
|
+
blur: NO_DATA
|
|
126
|
+
},
|
|
127
|
+
checkbox: {
|
|
128
|
+
mount: NO_DATA,
|
|
129
|
+
change: { payload: { value: 'boolean' }, reads: "setter: self.value da yazılır. Her tıkta İKİ olay yayılır — change ve (yeni duruma göre) checked ya da unchecked; ikisini birden bağlarsan akış iki kez koşar" },
|
|
130
|
+
checked: { payload: { value: 'boolean (true)' }, reads: 'setter: self.value da yazılır; change ile AYNI tıkta yayılır' },
|
|
131
|
+
unchecked: { payload: { value: 'boolean (false)' }, reads: 'setter: self.value da yazılır; change ile AYNI tıkta yayılır' },
|
|
132
|
+
focus: NO_DATA,
|
|
133
|
+
blur: NO_DATA
|
|
134
|
+
},
|
|
135
|
+
switch: {
|
|
136
|
+
mount: NO_DATA,
|
|
137
|
+
change: { payload: { value: 'boolean' }, reads: "setter: self.value da yazılır. Her flip'te İKİ olay yayılır — change ve (yeni duruma göre) checked ya da unchecked; ikisini birden bağlarsan akış iki kez koşar" },
|
|
138
|
+
checked: { payload: { value: 'boolean (true)' }, reads: 'setter: self.value da yazılır; change ile AYNI flip’te yayılır' },
|
|
139
|
+
unchecked: { payload: { value: 'boolean (false)' }, reads: 'setter: self.value da yazılır; change ile AYNI flip’te yayılır' },
|
|
140
|
+
focus: NO_DATA,
|
|
141
|
+
blur: NO_DATA
|
|
142
|
+
},
|
|
75
143
|
radio: { change: valueChange('string') },
|
|
76
|
-
dropdown: { mount: NO_DATA, change:
|
|
77
|
-
'multi-select': { mount: NO_DATA, change:
|
|
78
|
-
'tag-input': { mount: NO_DATA, change:
|
|
144
|
+
dropdown: { mount: NO_DATA, change: { payload: { value: 'seçilen option değeri', selectedLabel: "seçilen option'ın label metni (temizlemede '')" } }, search: { payload: { searchText: 'string' } }, focus: NO_DATA, blur: NO_DATA },
|
|
145
|
+
'multi-select': { mount: NO_DATA, change: { payload: { value: "seçilen değerlerin dizisi: [{ value, label, id, text }] — id/text, value/label'ın eski adlı ikizleridir" } }, search: { payload: { searchText: 'string' } }, focus: NO_DATA, blur: NO_DATA },
|
|
146
|
+
'tag-input': { mount: NO_DATA, change: { payload: { value: 'etiket dizisi: [{ value, color? }] — renk verilmemiş etikette color anahtarı HİÇ bulunmaz' } }, search: { payload: { searchText: 'string' } }, focus: NO_DATA, blur: NO_DATA },
|
|
79
147
|
datepicker: { mount: NO_DATA, change: { payload: { value: 'string (tarih)', HH: 'string', mm: 'string' } } },
|
|
80
148
|
datetimepicker: { mount: NO_DATA, change: { payload: {}, reads: 'self.value' } },
|
|
81
149
|
daterangepicker: { mount: NO_DATA, change: { payload: { valueFrom: 'string', valueTo: 'string', HH: 'string', mm: 'string' } } },
|
|
82
|
-
datetimerangepicker: {
|
|
150
|
+
datetimerangepicker: {
|
|
151
|
+
mount: NO_DATA,
|
|
152
|
+
change: {
|
|
153
|
+
payload: {
|
|
154
|
+
valueFrom: "string 'yyyy-MM-ddTHH:mm:ss' | null (yerel duvar saati, dilim eki yok; yarı boşaltılınca null)",
|
|
155
|
+
valueTo: "string 'yyyy-MM-ddTHH:mm:ss' | null"
|
|
156
|
+
},
|
|
157
|
+
reads: "ikisi de düğümün prop'una yazılır: self.valueFrom / self.valueTo. Bir yarı yalnız tarih+saat ikisi de seçilince yayar. HH/mm anahtarları YOKTUR (2026-09-12'de kayıttan çıkarıldı — runtime hiç göndermiyordu)."
|
|
158
|
+
},
|
|
159
|
+
focus: NO_DATA,
|
|
160
|
+
blur: NO_DATA
|
|
161
|
+
},
|
|
83
162
|
fileUpload: { change: { payload: {}, reads: 'self.value' } },
|
|
84
163
|
fileInput: { change: { payload: { value: 'Array<{ type, value }>', fileName: 'string' } } },
|
|
85
164
|
filebutton: { change: { payload: { value: 'Array<{ type, value }>', fileName: 'string' } } },
|
|
@@ -118,9 +197,18 @@ export const componentEventContracts = {
|
|
|
118
197
|
// 2026-08-25c, `docs/20-komponent-bilgi-katmani.md` §9).
|
|
119
198
|
autocomplete: {
|
|
120
199
|
mount: NO_DATA,
|
|
121
|
-
change:
|
|
122
|
-
|
|
123
|
-
|
|
200
|
+
change: {
|
|
201
|
+
payload: { value: 'string (YAZILAN metin — option değeri değil)' },
|
|
202
|
+
reads: "setter: self.value da yazılır. Temizleme (×) ve strict geri alma aynı olayı { value: '', selectedValue: '', selectedLabel: '' } ile yayar — alan boşalınca seçim durumu da boşalır"
|
|
203
|
+
},
|
|
204
|
+
select: {
|
|
205
|
+
payload: {
|
|
206
|
+
selectedValue: "seçilen option'ın değeri",
|
|
207
|
+
selectedLabel: "seçilen option'ın etiketi"
|
|
208
|
+
},
|
|
209
|
+
reads: "self.value etiketi taşır; option'ın değeri için self.selectedValue (2026-09-11'e kadar yükün adları value/label idi ve alanın kendi prop'larını eziyordu)"
|
|
210
|
+
},
|
|
211
|
+
search: { payload: { searchText: 'string' }, reads: 'self.searchText' },
|
|
124
212
|
enter: { payload: { value: 'string (Enter anındaki metin; vurgulu öneri yokken)' } },
|
|
125
213
|
focus: NO_DATA,
|
|
126
214
|
blur: NO_DATA
|
|
@@ -191,13 +279,20 @@ export const componentEventContracts = {
|
|
|
191
279
|
blur: NO_DATA
|
|
192
280
|
},
|
|
193
281
|
statcard: { mount: NO_DATA, click: NO_DATA, footerClick: { payload: {}, reads: "footerFlow zaten doğrudan çalışır; footerHref varsa tıklama ona gider" } },
|
|
194
|
-
switcher: {
|
|
282
|
+
switcher: {
|
|
283
|
+
mount: NO_DATA,
|
|
284
|
+
change: { payload: { value: 'boolean' }, reads: "setter: self.value da yazılır. Her geçişte İKİ olay yayılır — change ve (yeni duruma göre) checked ya da unchecked; ikisini birden bağlarsan akış iki kez koşar" },
|
|
285
|
+
checked: { payload: { value: 'boolean (true)' }, reads: 'setter: self.value da yazılır; change ile AYNI geçişte yayılır' },
|
|
286
|
+
unchecked: { payload: { value: 'boolean (false)' }, reads: 'setter: self.value da yazılır; change ile AYNI geçişte yayılır' },
|
|
287
|
+
focus: NO_DATA,
|
|
288
|
+
blur: NO_DATA
|
|
289
|
+
},
|
|
195
290
|
timeline: {
|
|
196
291
|
mount: NO_DATA,
|
|
197
292
|
clickItem: { payload: { currentItem: 'tıklanan olay öğesi', index: 'number' }, reads: "öğenin kendi flow'u da doğrudan çalışır" },
|
|
198
293
|
showMore: NO_DATA
|
|
199
294
|
},
|
|
200
|
-
timepicker: { mount: NO_DATA, change:
|
|
295
|
+
timepicker: { mount: NO_DATA, change: { payload: { value: "string 'HH:mm' (temizlenince '')", selectedLabel: "seçilen saatin gösterilen metni — 12h kipinde '01:00 PM' (temizlenince '')" } }, focus: NO_DATA, blur: NO_DATA },
|
|
201
296
|
toast: {
|
|
202
297
|
open: NO_DATA,
|
|
203
298
|
close: NO_DATA,
|
|
@@ -270,9 +365,9 @@ export const componentEventContracts = {
|
|
|
270
365
|
mount: NO_DATA,
|
|
271
366
|
change: {
|
|
272
367
|
payload: { value: "string (PNG data-URL ya da HAM SVG işaretlemesi; temizlemede '')" },
|
|
273
|
-
reads:
|
|
368
|
+
reads: "setter: self.value da güncellenir, event.data.value da dolu gelir; her tamamlanan çizgide bir kez yayılır. Ayrıca outputFormat/penColor/penWidth değişince mevcut vuruşlar yeniden dışa aktarılır ve change bir kez daha yayılır. Akış value'yu setter ile yazdığında ped sıfırlanır ama change YAYILMAZ — kendi yazdığın değer geri gelmez"
|
|
274
369
|
},
|
|
275
|
-
clear: { payload: {}, reads: "
|
|
370
|
+
clear: { payload: {}, reads: "yalnız kullanıcı Clear düğmesine bastığında yayılır; önce change('') gelir, sonra clear — değeri change'ten oku. Akıştan value'yu '' yapmak pedi temizler ama hiçbir olay yaymaz" }
|
|
276
371
|
},
|
|
277
372
|
stepper: {
|
|
278
373
|
mount: NO_DATA,
|
|
@@ -20,6 +20,49 @@ export function formBindingWarnings(ops) {
|
|
|
20
20
|
.filter(elementId => !boundForms.has(elementId))
|
|
21
21
|
.map(elementId => `FORM-001: '${elementId}' formuna 'submit' olayı bağlanmadı — form kendi başına kayıt YAPMAZ, gönderimi bir flow yürütür. flow_create ile submit akışını kurup set_event { target: '${elementId}', event: 'submit', eventValue: <flow id> } ile bağlayın (örnek: ngapps://examples/page-form-upsert). Bağlamayı sonraki patch'e bıraktıysanız bu uyarıyı yok sayın.`);
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* FORM-003 — formun `submit` olayı bağlı ama form içindeki butonun `click` olayı bağlı değil.
|
|
25
|
+
* ÖLÇÜLDÜ 2026-09-05 (Corecruiter koşusu, `S-18`): `ngapps://examples/page-sign-in` örneği akışı
|
|
26
|
+
* forma bağlıyor ve içine düz bir `button` koyuyor; runtime o butonu `<button type="button">`
|
|
27
|
+
* çiziyor, yani HTML form submit'i HİÇ doğmuyor. Butona tıklamak da alanda Enter da uca istek
|
|
28
|
+
* göndermedi, hata da çıkmadı — teşhis yalnız gerçek tıklamayla mümkün oldu. Örneğin kendi notu
|
|
29
|
+
* "kimlik middleware'i kuran her projede bu sayfa ZORUNLUDUR" dediği için tuzak, örneği izleyerek
|
|
30
|
+
* kurulan her uygulamanın giriş kapısında duruyor.
|
|
31
|
+
*/
|
|
32
|
+
export function formSubmitButtonWarnings(ops) {
|
|
33
|
+
const operations = asObjects(ops);
|
|
34
|
+
const boundClicks = new Set(operations
|
|
35
|
+
.filter(op => op.op === 'set_event' && String(op.event ?? '') === 'click')
|
|
36
|
+
.map(op => String(op.target ?? '')));
|
|
37
|
+
const submitForms = new Set(operations
|
|
38
|
+
.filter(op => op.op === 'set_event' && String(op.event ?? '') === 'submit')
|
|
39
|
+
.map(op => String(op.target ?? '')));
|
|
40
|
+
const submitsNatively = (node) => {
|
|
41
|
+
const prop = (node.props ?? {}).type;
|
|
42
|
+
return String(prop?.data ?? prop ?? '') === 'submit';
|
|
43
|
+
};
|
|
44
|
+
const buttonsIn = (node) => {
|
|
45
|
+
const found = [];
|
|
46
|
+
for (const child of asObjects(node.children)) {
|
|
47
|
+
if (String(child.type ?? '') === 'button' && !submitsNatively(child))
|
|
48
|
+
found.push(String(child.elementId ?? '?'));
|
|
49
|
+
found.push(...buttonsIn(child));
|
|
50
|
+
}
|
|
51
|
+
return found;
|
|
52
|
+
};
|
|
53
|
+
return operations
|
|
54
|
+
.filter(op => op.op === 'add_component' && FORM_TYPES.has(String(op.component?.type ?? '')))
|
|
55
|
+
.flatMap(op => {
|
|
56
|
+
const component = (op.component ?? {});
|
|
57
|
+
const elementId = String(component.elementId ?? '?');
|
|
58
|
+
if (!submitForms.has(elementId))
|
|
59
|
+
return [];
|
|
60
|
+
const loose = buttonsIn(component).filter(buttonId => !boundClicks.has(buttonId));
|
|
61
|
+
if (loose.length === 0)
|
|
62
|
+
return [];
|
|
63
|
+
return [`FORM-003 (uyarı): '${elementId}' formunun 'submit' olayı bağlı ama içindeki buton(lar) ne props.type:'submit' taşıyor ne de 'click' olayı bağlı: ${loose.join(', ')}. Buton varsayılan olarak type="button" çizilir ve HTML form submit'i HİÇ doğmaz — tıklamak da Enter da uca istek göndermez, hata da çıkmaz (ölçüldü 2026-09-05). İki çözümden biri: butona props.type = 'submit' verin, ya da aynı akışı butonun click olayına da bağlayın (set_event { target: '${loose[0]}', event: 'click', eventValue: <aynı flow id> }).`];
|
|
64
|
+
});
|
|
65
|
+
}
|
|
23
66
|
/**
|
|
24
67
|
* FORM-002 — akıştaki yazma ucu (post/put/patch) hiç `source:'body'` parametresi taşımıyor:
|
|
25
68
|
* istek gider, gövde boş olduğu için kayıt ya boş yazılır ya doğrulamadan döner. Form alanları uca
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* FLOW-011'in guard tanıması: lint `Steps.x.headers && Steps.x.headers["total-count"]` formunu
|
|
3
|
+
* guard'sız sanıp uyarıyordu ve aynı ifade için İKİ bulgu üretiyordu — regex her geçişi bağımsız
|
|
4
|
+
* saydığı, guard formu da kalıbı iki kez içerdiği için (İK koşusu E-6).
|
|
5
|
+
*/
|
|
6
|
+
function stepRef(name) {
|
|
7
|
+
const escaped = name.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
8
|
+
return `Steps\\s*(?:\\.\\s*${escaped}|\\[\\s*['"]${escaped}['"]\\s*\\])`;
|
|
9
|
+
}
|
|
10
|
+
const anyHeadersAccess = /Steps\s*(?:\.\s*([A-Za-z_$][\w$]*)|\[\s*['"]([^'"]+)['"]\s*\])\s*\??\.\s*headers\b/g;
|
|
11
|
+
export function headersAccessNames(expression) {
|
|
12
|
+
const names = new Set();
|
|
13
|
+
let match;
|
|
14
|
+
anyHeadersAccess.lastIndex = 0;
|
|
15
|
+
while ((match = anyHeadersAccess.exec(expression)) !== null) {
|
|
16
|
+
names.add(match[1] ?? match[2] ?? '');
|
|
17
|
+
}
|
|
18
|
+
return [...names].filter(name => name !== '');
|
|
19
|
+
}
|
|
20
|
+
export function isHeadersAccessGuarded(expression, name) {
|
|
21
|
+
const ref = stepRef(name);
|
|
22
|
+
const unguardedRead = new RegExp(`${ref}\\s*\\.\\s*headers\\s*(?:\\.|\\[)`, 'g');
|
|
23
|
+
const guardedRead = new RegExp(`${ref}\\s*(?:\\?\\.\\s*headers|\\.\\s*headers\\s*\\?\\.)`, 'g');
|
|
24
|
+
const precondition = new RegExp(`${ref}\\s*(?:\\.\\s*headers\\s*)?&&`);
|
|
25
|
+
if (precondition.test(expression))
|
|
26
|
+
return true;
|
|
27
|
+
const unguarded = expression.match(unguardedRead)?.length ?? 0;
|
|
28
|
+
const guarded = expression.match(guardedRead)?.length ?? 0;
|
|
29
|
+
return unguarded === 0 && guarded > 0;
|
|
30
|
+
}
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
export const FUNCTIONS_NAMESPACE = 'Functions';
|
|
2
|
+
const NAME_PATTERN = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
3
|
+
/** JavaScript ayrılmış sözcükleri — `GlobalFunctionRules.ReservedWordNames` ile aynı liste. */
|
|
4
|
+
export const RESERVED_WORDS = new Set([
|
|
5
|
+
'await', 'break', 'case', 'catch', 'class', 'const', 'continue', 'debugger', 'default', 'delete',
|
|
6
|
+
'do', 'else', 'enum', 'export', 'extends', 'false', 'finally', 'for', 'function', 'if',
|
|
7
|
+
'implements', 'import', 'in', 'instanceof', 'interface', 'let', 'new', 'null', 'package', 'private',
|
|
8
|
+
'protected', 'public', 'return', 'static', 'super', 'switch', 'this', 'throw', 'true', 'try',
|
|
9
|
+
'typeof', 'var', 'void', 'while', 'with', 'yield'
|
|
10
|
+
]);
|
|
11
|
+
/**
|
|
12
|
+
* Workflow motorunun global adları — `GlobalFunctionRules.WorkflowReservedNames` ile birebir.
|
|
13
|
+
* `source:'workflow'` fonksiyonu bu adı alırsa motorun kendi nesnesini ezerdi (platform 422).
|
|
14
|
+
*/
|
|
15
|
+
export const WORKFLOW_RESERVED_NAMES = new Set([
|
|
16
|
+
'Authorized', 'body', 'convert', 'ConvertToCsv', 'ConvertToPdf', 'cookie', 'crypto', 'DecodeQr', 'DeepEqual', 'error', 'file',
|
|
17
|
+
'GenerateAccessToken', 'GenerateGuid', 'GenerateJwt', 'GeneratePKCE', 'Hash', 'header', 'input', 'log', 'Log', 'Now',
|
|
18
|
+
'queryString', 'request', 'RequestPathAndQuery', 'RequestUrl', 'route', 'system', 'Today', 'ToTimeZone', 'UtcNow', 'ValidateAccessToken'
|
|
19
|
+
]);
|
|
20
|
+
export function sourceOf(definition) {
|
|
21
|
+
return definition.source === 'workflow' ? 'workflow' : 'studio';
|
|
22
|
+
}
|
|
23
|
+
export function isValidParameterName(name) {
|
|
24
|
+
return typeof name === 'string' && NAME_PATTERN.test(name) && !RESERVED_WORDS.has(name);
|
|
25
|
+
}
|
|
26
|
+
export function isValidFunctionName(name, source = 'studio') {
|
|
27
|
+
return isValidParameterName(name) && (source === 'workflow' ? !WORKFLOW_RESERVED_NAMES.has(name) : name !== FUNCTIONS_NAMESPACE);
|
|
28
|
+
}
|
|
29
|
+
/** Geçersiz adın NEDENİ — mesaj kullanıcıya hangi kuralı bozduğunu söylesin diye. */
|
|
30
|
+
export function nameProblem(name, source = 'studio') {
|
|
31
|
+
if (typeof name !== 'string' || name.trim() === '')
|
|
32
|
+
return 'boş';
|
|
33
|
+
if (!NAME_PATTERN.test(name))
|
|
34
|
+
return `JS tanımlayıcısı değil (${NAME_PATTERN.source})`;
|
|
35
|
+
if (RESERVED_WORDS.has(name))
|
|
36
|
+
return 'JavaScript ayrılmış sözcüğü';
|
|
37
|
+
if (source === 'workflow' && WORKFLOW_RESERVED_NAMES.has(name))
|
|
38
|
+
return `workflow motorunun global adı ('${name}' — system, input, Now… ezilemez)`;
|
|
39
|
+
if (source === 'studio' && name === FUNCTIONS_NAMESPACE)
|
|
40
|
+
return `'${FUNCTIONS_NAMESPACE}' ad alanının kendisi (ayrılmış ad, K1)`;
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
/** Çağrı biçimi: studio `Functions.<ad>`, workflow çıplak `<ad>`. */
|
|
44
|
+
export function callPrefix(source) {
|
|
45
|
+
return source === 'workflow' ? '' : `${FUNCTIONS_NAMESPACE}.`;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Studio'nun canlı imza önizlemesiyle aynı biçim: `Functions.toplamHesapla(fiyat, adet = 1)`;
|
|
49
|
+
* workflow kaynağında öneksiz `toplamHesapla(fiyat, adet = 1)`. Varsayılanı olmayan ve zorunlu
|
|
50
|
+
* olmayan parametre `ad?` yazılır.
|
|
51
|
+
*/
|
|
52
|
+
export function functionSignature(definition) {
|
|
53
|
+
const parameters = Array.isArray(definition.parameters) ? definition.parameters : [];
|
|
54
|
+
const rendered = parameters
|
|
55
|
+
.filter(parameter => parameter && typeof parameter === 'object')
|
|
56
|
+
.map(parameter => {
|
|
57
|
+
const name = String(parameter.name ?? '?');
|
|
58
|
+
if (parameter.defaultValue !== undefined && parameter.defaultValue !== null)
|
|
59
|
+
return `${name} = ${JSON.stringify(parameter.defaultValue)}`;
|
|
60
|
+
return parameter.required === true ? name : `${name}?`;
|
|
61
|
+
});
|
|
62
|
+
return `${callPrefix(sourceOf(definition))}${String(definition.name ?? '?')}(${rendered.join(', ')})`;
|
|
63
|
+
}
|
|
64
|
+
const CALL_PATTERN = /\bFunctions\s*\.\s*([A-Za-z_$][\w$]*)\s*\(/g;
|
|
65
|
+
/**
|
|
66
|
+
* Bir ifadedeki `Functions.<ad>(` çağrılarını ve KABA argüman sayısını döner. Sayım parantez
|
|
67
|
+
* dengesiyle üst düzey virgülleri sayar; string/şablon ve iç içe çağrı içindeki virgül
|
|
68
|
+
* sayılmaz. Yine de yaklaşıktır — çağıran yalnız FAZLA argümanda uyarır, eksik argümanı
|
|
69
|
+
* `required` kuralına bırakır (K5: eşleme konumsaldır, `undefined` → varsayılan).
|
|
70
|
+
*/
|
|
71
|
+
export function functionCallRefs(expression) {
|
|
72
|
+
const refs = [];
|
|
73
|
+
let match;
|
|
74
|
+
CALL_PATTERN.lastIndex = 0;
|
|
75
|
+
while ((match = CALL_PATTERN.exec(expression)) !== null) {
|
|
76
|
+
refs.push({ name: match[1] ?? '', argCount: countArguments(expression, match.index + match[0].length), index: match.index });
|
|
77
|
+
}
|
|
78
|
+
return refs.filter(ref => ref.name !== '');
|
|
79
|
+
}
|
|
80
|
+
function countArguments(source, start) {
|
|
81
|
+
let depth = 0;
|
|
82
|
+
let commas = 0;
|
|
83
|
+
let quote;
|
|
84
|
+
let sawToken = false;
|
|
85
|
+
for (let index = start; index < source.length; index++) {
|
|
86
|
+
const char = source[index];
|
|
87
|
+
if (quote !== undefined) {
|
|
88
|
+
if (char === '\\') {
|
|
89
|
+
index++;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (char === quote)
|
|
93
|
+
quote = undefined;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
if (char === '\'' || char === '"' || char === '`') {
|
|
97
|
+
quote = char;
|
|
98
|
+
sawToken = true;
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
if (char === '(' || char === '[' || char === '{') {
|
|
102
|
+
depth++;
|
|
103
|
+
sawToken = true;
|
|
104
|
+
continue;
|
|
105
|
+
}
|
|
106
|
+
if (char === ')' || char === ']' || char === '}') {
|
|
107
|
+
if (depth === 0)
|
|
108
|
+
return sawToken ? commas + 1 : 0;
|
|
109
|
+
depth--;
|
|
110
|
+
continue;
|
|
111
|
+
}
|
|
112
|
+
if (char === ',' && depth === 0) {
|
|
113
|
+
commas++;
|
|
114
|
+
continue;
|
|
115
|
+
}
|
|
116
|
+
if (!/\s/.test(char))
|
|
117
|
+
sawToken = true;
|
|
118
|
+
}
|
|
119
|
+
return sawToken ? commas + 1 : 0;
|
|
120
|
+
}
|
|
121
|
+
function escapeRegex(value) {
|
|
122
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
123
|
+
}
|
|
124
|
+
/** Çıplak `<ad>(` çağrısı — backend `GlobalFunction.IsCalledIn`'in workflow kalıbıyla aynı sınırlar. */
|
|
125
|
+
export function callsBareName(name, expression) {
|
|
126
|
+
return name !== '' && new RegExp(`(?<![A-Za-z0-9_$.])${escapeRegex(name)}\\s*\\(`).test(expression);
|
|
127
|
+
}
|
|
128
|
+
/** Gövde kendi adını çağırıyor mu — studio `Functions.<ad>(`, workflow çıplak `<ad>(` (özyineleme, FN-003). */
|
|
129
|
+
export function callsItself(name, body, source = 'studio') {
|
|
130
|
+
return source === 'workflow' ? callsBareName(name, body) : functionCallRefs(body).some(ref => ref.name === name);
|
|
131
|
+
}
|
|
132
|
+
export function mentionsAwait(body) {
|
|
133
|
+
return /\bawait\b/.test(body);
|
|
134
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
const turkishMap = { ç: 'c', ğ: 'g', ı: 'i', ö: 'o', ş: 's', ü: 'u', â: 'a', î: 'i', û: 'u' };
|
|
2
|
+
export function sectionSlug(title) {
|
|
3
|
+
return title.toLocaleLowerCase('tr')
|
|
4
|
+
.replace(/[çğıöşüâîû]/g, letter => turkishMap[letter] ?? letter)
|
|
5
|
+
.replace(/[^a-z0-9]+/g, '-')
|
|
6
|
+
.replace(/^-+|-+$/g, '');
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Rehberler `## ` başlıklarıyla bölümlü; `page.md` 120 KB'ı aşınca tool çıktı sınırına takıldı ve
|
|
10
|
+
* MCP kanalından hiç okunamadı (09-04 koşusu). Bölümleme var olan başlıkları kullanır, dosyaların
|
|
11
|
+
* içeriğine dokunmaz.
|
|
12
|
+
*
|
|
13
|
+
* Satır sonu ÖNEMLİ: başlık ifadesindeki `.` satır sonu karakterlerini eşlemez ve `$` çok satırlı
|
|
14
|
+
* değildir, yani `\r` ile biten bir satırda hiçbir başlık görünmez. Windows çalışma ağacında
|
|
15
|
+
* (`.gitattributes` yokken) rehberlerin tamamı CRLF olduğu için bölümleyici tek "giriş" bölümü
|
|
16
|
+
* üretiyor, `platform_guide` de oversize dalında rehberin TAMAMINI döndürüp tool sınırını
|
|
17
|
+
* aşıyordu — ölçüldü 2026-09-05, `page.md` 22 başlık → 1 bölüm. Girdi bu yüzden bölünmeden önce
|
|
18
|
+
* satır sonlarından arındırılır.
|
|
19
|
+
*/
|
|
20
|
+
export function splitGuideSections(markdown) {
|
|
21
|
+
const sections = [];
|
|
22
|
+
const used = new Map();
|
|
23
|
+
let fence = '';
|
|
24
|
+
let title = '';
|
|
25
|
+
let lines = [];
|
|
26
|
+
const push = () => {
|
|
27
|
+
if (title === '' && lines.join('\n').trim() === '')
|
|
28
|
+
return;
|
|
29
|
+
const base = title === '' ? 'giris' : sectionSlug(title);
|
|
30
|
+
const seen = (used.get(base) ?? 0) + 1;
|
|
31
|
+
used.set(base, seen);
|
|
32
|
+
sections.push({ slug: seen === 1 ? base : `${base}-${seen}`, title: title === '' ? 'Giriş' : title, body: lines.join('\n').trim() });
|
|
33
|
+
};
|
|
34
|
+
for (const line of markdown.split(/\r\n|\r|\n/)) {
|
|
35
|
+
const marker = /^\s*(`{3,})/.exec(line)?.[1] ?? '';
|
|
36
|
+
if (marker.length > 0)
|
|
37
|
+
fence = fence === '' ? marker : (marker.length >= fence.length ? '' : fence);
|
|
38
|
+
const heading = fence === '' ? /^## (?!#)(.+)$/.exec(line)?.[1] : undefined;
|
|
39
|
+
if (heading === undefined) {
|
|
40
|
+
lines.push(line);
|
|
41
|
+
continue;
|
|
42
|
+
}
|
|
43
|
+
push();
|
|
44
|
+
title = heading.trim();
|
|
45
|
+
lines = [];
|
|
46
|
+
}
|
|
47
|
+
push();
|
|
48
|
+
return sections;
|
|
49
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { RefResolveError, isGuid } from '../platform/refs.js';
|
|
2
|
+
const fieldCarriers = ['fields', 'selectFields', 'returnFields'];
|
|
3
|
+
function entries(value) {
|
|
4
|
+
return Array.isArray(value) ? value.filter((entry) => Boolean(entry) && typeof entry === 'object') : [];
|
|
5
|
+
}
|
|
6
|
+
function collectJoinAliases(joins, into) {
|
|
7
|
+
for (const join of entries(joins)) {
|
|
8
|
+
const alias = String(join.alias ?? '').trim();
|
|
9
|
+
if (alias)
|
|
10
|
+
into.add(alias);
|
|
11
|
+
collectJoinAliases(join.joins, into);
|
|
12
|
+
}
|
|
13
|
+
return into;
|
|
14
|
+
}
|
|
15
|
+
function pendingEntries(parameters) {
|
|
16
|
+
const pending = [];
|
|
17
|
+
const walk = (list) => {
|
|
18
|
+
for (const entry of entries(list)) {
|
|
19
|
+
if (typeof entry.fieldSlug === 'string' && typeof entry.fieldId !== 'string')
|
|
20
|
+
pending.push(entry);
|
|
21
|
+
walk(entry.children);
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
for (const key of fieldCarriers)
|
|
25
|
+
walk(parameters[key]);
|
|
26
|
+
walk(parameters.filters);
|
|
27
|
+
return pending;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Hydration normalize'dan ÖNCE koşmak zorundadır: `normalizeTableParameters` join ve seçim
|
|
31
|
+
* yollarını `fieldId`'ye göre üretir (`normalize.ts` `fillJoinPaths`), boş `fieldId` gören
|
|
32
|
+
* normalize yolu hiç doldurmaz.
|
|
33
|
+
*
|
|
34
|
+
* GUID'li ve `fieldSlug` taşımayan tanımlar hiç ağ turu ödemez ve ağaç hiç okunmaz — ağaç
|
|
35
|
+
* uçtaki gizli entity'leri (Files/Folders, EnumValues) TAŞIMAZ, dolayısıyla koşulsuz çözüm
|
|
36
|
+
* bugüne kadar çalışan GUID'li çağrıları "ağaçta yok" diye düşürürdü.
|
|
37
|
+
*
|
|
38
|
+
* Kapsam bilinçli olarak KÖK entity ile sınırlı: `joins[]` girdileri şemada `fieldSlug`
|
|
39
|
+
* taşımaz (`TableJoin` = alias·entityId·fieldId·joins·path), join'lenen entity'nin alanları
|
|
40
|
+
* ayrı bir görev.
|
|
41
|
+
*/
|
|
42
|
+
export async function hydrateMicroserviceRefs(definition, resolver) {
|
|
43
|
+
if (definition.source !== 'table')
|
|
44
|
+
return [];
|
|
45
|
+
const rootRef = String(definition.entityId ?? '');
|
|
46
|
+
if (rootRef === '')
|
|
47
|
+
return [];
|
|
48
|
+
const parameters = (definition.sourceParameters ?? {});
|
|
49
|
+
const nestedRef = String(parameters.entityId ?? '');
|
|
50
|
+
const pending = pendingEntries(parameters);
|
|
51
|
+
if (pending.length === 0 && isGuid(rootRef) && (nestedRef === '' || isGuid(nestedRef)))
|
|
52
|
+
return [];
|
|
53
|
+
const fixes = [];
|
|
54
|
+
const root = await resolver.entity(rootRef);
|
|
55
|
+
if (!isGuid(rootRef)) {
|
|
56
|
+
definition.entityId = root.entityId;
|
|
57
|
+
fixes.push(`entityId '${rootRef}' → ${root.entityId}`);
|
|
58
|
+
}
|
|
59
|
+
if (nestedRef !== '' && !isGuid(nestedRef)) {
|
|
60
|
+
parameters.entityId = (await resolver.entity(nestedRef)).entityId;
|
|
61
|
+
fixes.push(`sourceParameters.entityId '${nestedRef}' → ${String(parameters.entityId)}`);
|
|
62
|
+
}
|
|
63
|
+
if (pending.length === 0)
|
|
64
|
+
return fixes;
|
|
65
|
+
const fields = await resolver.entityFields(root);
|
|
66
|
+
const idBySlug = new Map(fields.map(field => [field.slug, field.id]));
|
|
67
|
+
const joinAliases = collectJoinAliases(parameters.joins, new Set());
|
|
68
|
+
const declaredAlias = String(parameters.alias ?? '').trim();
|
|
69
|
+
const rootAlias = declaredAlias === '' ? root.slug : declaredAlias;
|
|
70
|
+
const fill = (entry, where) => {
|
|
71
|
+
const slug = typeof entry.fieldSlug === 'string' ? entry.fieldSlug : undefined;
|
|
72
|
+
const alias = String(entry.alias ?? '').trim();
|
|
73
|
+
if (slug === undefined || typeof entry.fieldId === 'string')
|
|
74
|
+
return;
|
|
75
|
+
// Kök girdi: alias yok, kök alias'ı, ya da hiçbir join'e ait değil — `normalize.fillRootAlias`
|
|
76
|
+
// kök alias'ı tam da bu çıkarımla buluyor; ikisi ayrışırsa filtre sessizce fieldId'siz gider.
|
|
77
|
+
if (alias !== '' && alias !== rootAlias && joinAliases.has(alias))
|
|
78
|
+
return;
|
|
79
|
+
const id = idBySlug.get(slug);
|
|
80
|
+
if (id === undefined) {
|
|
81
|
+
throw new RefResolveError(`'${slug}' diye bir alan '${root.slug}' entity'sinde yok (${where}). Mevcut alanlar: ${[...idBySlug.keys()].join(', ')}`);
|
|
82
|
+
}
|
|
83
|
+
entry.fieldId = id;
|
|
84
|
+
fixes.push(`${where}.fieldSlug '${slug}' → ${id}`);
|
|
85
|
+
};
|
|
86
|
+
for (const key of fieldCarriers) {
|
|
87
|
+
entries(parameters[key]).forEach((entry, index) => fill(entry, `${key}[${index}]`));
|
|
88
|
+
}
|
|
89
|
+
const walkFilters = (list, prefix) => {
|
|
90
|
+
entries(list).forEach((entry, index) => {
|
|
91
|
+
fill(entry, `${prefix}[${index}]`);
|
|
92
|
+
walkFilters(entry.children, `${prefix}[${index}].children`);
|
|
93
|
+
});
|
|
94
|
+
};
|
|
95
|
+
walkFilters(parameters.filters, 'filters');
|
|
96
|
+
return fixes;
|
|
97
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
2
|
+
const ISO_639_1 = /^[a-z]{2}$/;
|
|
3
|
+
/**
|
|
4
|
+
* Platformun tek dil kodu biçimi ISO 639-1 küçük harftir (sözleşme D2). Dışarıdan gelen BCP-47
|
|
5
|
+
* değer ('tr-TR', 'en_US') birincil alt etikete indirgenir ve bölge saklanmaz — backend de aynısını
|
|
6
|
+
* yapar (`LanguageContext.RequestedLanguages`). İndirgeme SESSİZ yapılmaz: 'pt-BR' gönderip 'pt'
|
|
7
|
+
* kaydedildiğini bilmeyen çağıran, bölge ayrımının platformda hiç olmadığını fark etmiyor.
|
|
8
|
+
*/
|
|
9
|
+
export function normalizeLanguageCode(input, field) {
|
|
10
|
+
const code = (input.trim().split(/[-_]/)[0] ?? '').toLowerCase();
|
|
11
|
+
if (!ISO_639_1.test(code)) {
|
|
12
|
+
throw new PlatformApiError(`'${field}' alanındaki '${input}' geçerli bir dil kodu değil. Platform ISO 639-1 küçük harf iki harfli kod tutar ('tr', 'en', 'de'); bölgeli değer ('tr-TR') kabul edilir ve indirgenir ama '${input}' indirgendiğinde de iki harfe inmiyor.`, 400);
|
|
13
|
+
}
|
|
14
|
+
return code === input ? { code } : { code, note: `${field}: '${input}' → '${code}' (platform ISO 639-1 küçük harf kod tutar, bölge bilgisi saklanmaz).` };
|
|
15
|
+
}
|
|
16
|
+
export function normalizeLanguageCodes(inputs, field) {
|
|
17
|
+
const results = inputs.map(input => normalizeLanguageCode(input, field));
|
|
18
|
+
return {
|
|
19
|
+
codes: results.map(result => result.code),
|
|
20
|
+
notes: results.flatMap(result => result.note ? [result.note] : [])
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
export const DATA_LANGUAGE_HEADER = 'X-Data-Language';
|
|
24
|
+
/**
|
|
25
|
+
* Yürütme araçlarının `language` parametresini backend'in istek dili başlığına çevirir. Başlık
|
|
26
|
+
* gönderilmezse backend `Accept-Language`'a, o da yoksa projenin varsayılan diline düşer
|
|
27
|
+
* (sözleşme D3) — yani parametreyi atlamak bir hata değil, "projenin varsayılanı" demektir.
|
|
28
|
+
*/
|
|
29
|
+
export function languageHeader(input, notes) {
|
|
30
|
+
if (input === undefined)
|
|
31
|
+
return {};
|
|
32
|
+
const { code, note } = normalizeLanguageCode(input, 'language');
|
|
33
|
+
if (note)
|
|
34
|
+
notes.push(note);
|
|
35
|
+
return { [DATA_LANGUAGE_HEADER]: code };
|
|
36
|
+
}
|