@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
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { FUNCTIONS_NAMESPACE, callPrefix, functionSignature, sourceOf } from '../knowledge/functionGuards.js';
|
|
2
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
3
|
+
import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
|
|
4
|
+
import { unmappedMembers, withUnmappedMembers } from '../knowledge/schemaGuard.js';
|
|
5
|
+
import { functionOutline } from '../knowledge/outlines.js';
|
|
6
|
+
import { writeDiff, withWriteDiff } from '../platform/writeDiff.js';
|
|
7
|
+
import { PlatformApiError } from '../platform/errors.js';
|
|
8
|
+
import { handle } from '../platform/responses.js';
|
|
9
|
+
import { policyRules } from '../knowledge/rules.js';
|
|
10
|
+
import { z } from 'zod';
|
|
11
|
+
/**
|
|
12
|
+
* Fonksiyonlar — backend aggregate'i `GlobalFunction` (`/global-functions` ucu, tek tablo), ajan
|
|
13
|
+
* sözlüğü `function_*`. Ayrım `source` bayrağıdır: `workflow` sunucuda (Jint) çıplak adla,
|
|
14
|
+
* `studio` tarayıcıda `Functions.<ad>(…)` ile çağrılır; kod ve parametre biçimi ikisinde aynıdır.
|
|
15
|
+
*/
|
|
16
|
+
const sourceParam = z.enum(['workflow', 'studio']).describe("Nerede çalışır: 'workflow' → SUNUCU (workflow/middleware/automation ifadeleri, Jint), tenant geneli, çıplak adla çağrılır (toplamHesapla(1, 2)), applicationId/scope/pageId VERİLMEZ, isAsync olamaz. 'studio' → TARAYICI ({{ }} bağlaması, UI flow code/function adımı), uygulamaya ait, Functions.toplamHesapla(1, 2) ile çağrılır, applicationId + scope zorunlu. Kaynak sonradan DEĞİŞMEZ");
|
|
17
|
+
const scopeParam = z.enum(['application', 'page']).describe("Yalnız source:'studio'. application: uygulamanın her sayfasında görünür. page: yalnız o sayfanın tarayıcı bağlamında, pageId zorunlu (K2). Ad uygulama içinde kapsamlar arası benzersizdir (K3)");
|
|
18
|
+
const nameParam = z.string().min(1).describe("Fonksiyon adı; studio'da Functions.<ad>(…), workflow'da <ad>(…) ile çağrılır. JS tanımlayıcısı olmalı, ayrılmış sözcük olamaz; studio'da 'Functions', workflow'da motor global adları (system, input, Now…) yasaktır (FN-001, istek gitmeden). Aynı ad iki kaynakta yan yana yaşayabilir (ayrı ad alanları); aynı kaynak içinde tekrarı platform 422 ile reddeder");
|
|
19
|
+
const bodyParam = z.string().min(1).max(65536).describe("Fonksiyon GÖVDESİ, iki kaynakta aynı biçim: düz JavaScript deyimleri, 'return' içerebilir — 'function' sarmalı ya da arrow YAZILMAZ ('return fiyat * adet'). Gövde çağrıldığı yerin bağlamını kapanış olarak görür: tarayıcıda Page/state/Steps/komponent adları, sunucuda input/adım çıktıları/system.* (K7). Parametre adları gövdede doğrudan kullanılır. Platform kaydetmeden önce SÖZDİZİMİNİ denetler (422 GlobalFunctionBodyInvalid, ayrıştırıcı mesajıyla). En çok 64 KB");
|
|
20
|
+
const parameterParam = z.object({
|
|
21
|
+
name: z.string().min(1).describe('Parametre adı — JS tanımlayıcısı, fonksiyon içinde benzersiz; gövdede bu adla okunur'),
|
|
22
|
+
type: z.enum(['text', 'number', 'boolean', 'date', 'object', 'array', 'any']).optional()
|
|
23
|
+
.describe("Yalnız Studio imzası ve autocomplete için; çalışma zamanı tipi DENETLEMEZ. Varsayılan 'any'"),
|
|
24
|
+
required: z.boolean().optional()
|
|
25
|
+
.describe("true: çağrıda undefined kalırsa fonksiyon '<ad>: \"<param>\" is required' ile fırlatır (defaultValue uygulandıktan SONRA denetlenir)"),
|
|
26
|
+
defaultValue: z.unknown().optional().describe('Çağrıda undefined kalan parametreye uygulanır'),
|
|
27
|
+
description: z.string().optional().describe('Studio autocomplete infobox satırı')
|
|
28
|
+
});
|
|
29
|
+
const parametersParam = z.array(parameterParam).optional().describe("Bildirimli parametre listesi, SIRALI: çağrı konumsaldır, f(a, b) bildirim sırasına eşlenir (K5). UI flow 'function' adımı parametreleri bu listeden ada göre eşler. Parametresiz fonksiyonda boş bırakın");
|
|
30
|
+
const isAsyncParam = z.boolean().optional().describe("Yalnız source:'studio': true → tarayıcıda 'async function', gövdede await serbest; {{ }} inline bağlamada dönüş Promise'dir ('[object Promise]'), yalnız UI flow 'function'/'code' adımından çağırın. source:'workflow' ile true → FN-004 block (motor senkron, platform 422)");
|
|
31
|
+
const descriptionParam = z.string().optional().describe('Ne işe yaradığı; Studio fonksiyon kartında ve autocomplete infobox başlığında görünür');
|
|
32
|
+
const policyOverrideParam = z.boolean().optional().describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
|
|
33
|
+
const FUNCTIONS_PATH = '/global-functions';
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return Boolean(value) && typeof value === 'object' && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
function unwrapData(payload) {
|
|
38
|
+
const record = isRecord(payload) ? payload : {};
|
|
39
|
+
return isRecord(record.data) ? record.data : record;
|
|
40
|
+
}
|
|
41
|
+
function listData(payload) {
|
|
42
|
+
const record = isRecord(payload) ? payload : {};
|
|
43
|
+
const list = Array.isArray(record.data) ? record.data : Array.isArray(payload) ? payload : [];
|
|
44
|
+
return list.filter(isRecord);
|
|
45
|
+
}
|
|
46
|
+
function parameterNames(record) {
|
|
47
|
+
return (Array.isArray(record.parameters) ? record.parameters : [])
|
|
48
|
+
.filter(isRecord)
|
|
49
|
+
.map(parameter => String(parameter.name ?? ''));
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Çağrı biçimini değiştiren fark: ad ya da parametre AD/SIRA listesi. Tip, varsayılan, `required`
|
|
53
|
+
* ve gövde imzayı değiştirmez — çağrı konumsaldır (K5), `function` adımı ada göre eşler.
|
|
54
|
+
*/
|
|
55
|
+
export function describeSignatureChange(current, next) {
|
|
56
|
+
const currentName = String(current.name ?? '');
|
|
57
|
+
const nextName = String(next.name ?? currentName);
|
|
58
|
+
const currentParameters = parameterNames(current);
|
|
59
|
+
const nextParameters = parameterNames(next);
|
|
60
|
+
const parametersChanged = currentParameters.length !== nextParameters.length || currentParameters.some((name, index) => name !== nextParameters[index]);
|
|
61
|
+
return {
|
|
62
|
+
name: currentName === nextName ? null : [currentName, nextName],
|
|
63
|
+
parameters: parametersChanged ? [currentParameters, nextParameters] : null
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
function signatureWarning(name, source, change, dependents) {
|
|
67
|
+
const callers = [dependents.flows.length ? `flows: ${dependents.flows.join(', ')}` : '', dependents.workflows.length ? `workflows: ${dependents.workflows.join(', ')}` : ''].filter(Boolean).join('; ');
|
|
68
|
+
const what = [
|
|
69
|
+
change.name ? `ad '${change.name[0]}' → '${change.name[1]}'` : '',
|
|
70
|
+
change.parameters ? `parametreler [${change.parameters[0].join(', ')}] → [${change.parameters[1].join(', ')}]` : ''
|
|
71
|
+
].filter(Boolean).join(', ');
|
|
72
|
+
const effect = source === 'workflow'
|
|
73
|
+
? `çıplak çağrı ${name}(…) konumsaldır, argümanlar yeni bildirim sırasına eşlenir`
|
|
74
|
+
: `${FUNCTIONS_NAMESPACE}.${name}(…) çağrıları konumsaldır ve 'function' adımları parametreyi ADA göre eşler — eski ad/sıra ile yazılmış çağrılar yanlış değer alır`;
|
|
75
|
+
return `İmza değişti (${what}) ve fonksiyonu ${dependents.total} kayıt kullanıyor (${callers}): ${effect}. Güncelleme yapıldı; çağıran ifadeleri gözden geçirin — dependency_graph(module:'global-function').`;
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* `GET global-functions/{id}/dependencies` KOVALI grafiği döner (`{ flows, workflows }`); düz dizi
|
|
79
|
+
* şekli de düzleştirilir — yapısal (`args.functionId`) ve metinsel (`Functions.<ad>(` / çıplak `<ad>(`)
|
|
80
|
+
* bağımlılıklar aynı listede gelir. Etiket Studio rozetleriyle aynıdır: komponent olayı `Sahip › Komponent · olay`,
|
|
81
|
+
* sayfa/layout olayı `Sahip · olay`, bağı olmayan akış `Sahip › akış adı` (sahip sayfa/layout/widget; eski yanıtta
|
|
82
|
+
* sahipsiz); automation düğümü `ad (automation)`.
|
|
83
|
+
*/
|
|
84
|
+
export function summarizeDependents(payload) {
|
|
85
|
+
const data = (() => {
|
|
86
|
+
const record = isRecord(payload) ? payload : undefined;
|
|
87
|
+
const inner = record?.data;
|
|
88
|
+
return inner === undefined ? payload : inner;
|
|
89
|
+
})();
|
|
90
|
+
const text = (value) => (typeof value === 'string' && value !== '' ? value : undefined);
|
|
91
|
+
const names = (list) => (Array.isArray(list) ? list : [])
|
|
92
|
+
.filter(isRecord)
|
|
93
|
+
.map(entry => {
|
|
94
|
+
const name = String(entry.name ?? entry.slug ?? entry.id ?? '?');
|
|
95
|
+
const host = text(entry.hostName);
|
|
96
|
+
const component = text(entry.componentName);
|
|
97
|
+
const event = text(entry.eventTitle) ?? text(entry.eventName);
|
|
98
|
+
const label = component
|
|
99
|
+
? [host, [component, event].filter(Boolean).join(' · ')].filter(Boolean).join(' › ')
|
|
100
|
+
: event
|
|
101
|
+
? [host, event].filter(Boolean).join(' · ')
|
|
102
|
+
: [host, name].filter(Boolean).join(' › ');
|
|
103
|
+
return String(entry.module ?? '').toLowerCase() === 'automation' ? `${label} (automation)` : label;
|
|
104
|
+
});
|
|
105
|
+
if (Array.isArray(data)) {
|
|
106
|
+
const entries = data.filter(isRecord);
|
|
107
|
+
const moduleOf = (entry) => String(entry.module ?? entry.type ?? entry.kind ?? '').toLowerCase();
|
|
108
|
+
const isWorkflow = (entry) => moduleOf(entry).includes('workflow') || moduleOf(entry) === 'automation';
|
|
109
|
+
const workflows = names(entries.filter(isWorkflow));
|
|
110
|
+
const flows = names(entries.filter(entry => !isWorkflow(entry)));
|
|
111
|
+
return { flows, workflows, total: flows.length + workflows.length };
|
|
112
|
+
}
|
|
113
|
+
const record = isRecord(data) ? data : {};
|
|
114
|
+
const flows = names(record.flows);
|
|
115
|
+
const workflows = names(record.workflows);
|
|
116
|
+
return { flows, workflows, total: flows.length + workflows.length };
|
|
117
|
+
}
|
|
118
|
+
function usageHint(name, source, scope) {
|
|
119
|
+
if (source === 'workflow') {
|
|
120
|
+
return `Çağrı: workflow/middleware/automation ifadesinde çıplak adla — code adımı 'return ${name}(…)', condition '${name}(row.a) > 1'. Tenant genelidir, her uygulamanın her workflow'unda görünür; tarayıcıda ({{ }}, UI flow) YOKTUR. workflow_preview ile doğrulayın. Rehber: platform_guide('functions').`;
|
|
121
|
+
}
|
|
122
|
+
const visibility = scope === 'application' ? 'uygulamanın her sayfasında' : 'yalnız kendi sayfasında (K2)';
|
|
123
|
+
return `Çağrı: {{ Functions.${name}(…) }} prop bağlaması · UI flow adımı { type:'function', args:{ functionId:'<id>', functionName:'${name}', parameters:[{ name, value:{ type, ref } }] } } → Steps.<adımAdı>.data · ${visibility}; SUNUCUDA yoktur (WF-030). Sayfa paketi Pages.Updated ile tazelenir; /view yolu birkaç saniye eski gövdeyi gösterebilir (B-11). Rehber: platform_guide('functions').`;
|
|
124
|
+
}
|
|
125
|
+
export function registerFunctionTools(server, platform) {
|
|
126
|
+
const config = platform.config;
|
|
127
|
+
server.registerTool('function_list', {
|
|
128
|
+
title: 'Fonksiyonları listele',
|
|
129
|
+
description: "Fonksiyonları imzalarıyla listeler; iki kaynak tek listede (source: 'workflow' sunucu, 'studio' tarayıcı). Süzgeçler birleşir: source · applicationId (studio kayıtları) · scope · pageId (yalnız o sayfanın sayfa kapsamlıları). Gövde listede YOKTUR (bodyLines sayılır) — function_get ile okuyun. Bir ifadeye Functions.x(…) ya da çıplak x(…) yazmadan önce adı, id'yi ve parametre SIRASINI buradan alın — platform_guide('functions').",
|
|
130
|
+
inputSchema: {
|
|
131
|
+
project: projectParam,
|
|
132
|
+
environment: environmentParam,
|
|
133
|
+
source: sourceParam.optional().describe("Süzgeç: 'workflow' yalnız sunucu fonksiyonları, 'studio' yalnız tarayıcı fonksiyonları"),
|
|
134
|
+
applicationId: guid.optional().describe("Süzgeç: bu uygulamanın studio fonksiyonları (uygulama kapsamlılar + tüm sayfaların sayfa kapsamlıları)"),
|
|
135
|
+
pageId: guid.optional().describe('Süzgeç: yalnız bu sayfanın sayfa kapsamlı fonksiyonları (applicationId ile birlikte)'),
|
|
136
|
+
scope: scopeParam.optional().describe("Süzgeç: 'application' yalnız uygulama kapsamlılar, 'page' yalnız sayfa kapsamlılar")
|
|
137
|
+
},
|
|
138
|
+
annotations: readOnly
|
|
139
|
+
}, handle(config, async ({ project, environment, source, applicationId, pageId, scope }) => {
|
|
140
|
+
const query = Object.fromEntries(Object.entries({ source, applicationId, pageId, scope }).filter(([, value]) => value !== undefined));
|
|
141
|
+
const payload = await platform.tenant('GET', project, FUNCTIONS_PATH, { environment, ...(Object.keys(query).length > 0 ? { query } : {}) });
|
|
142
|
+
const functions = listData(payload)
|
|
143
|
+
.filter(entry => source === undefined || sourceOf(entry) === source)
|
|
144
|
+
.filter(entry => scope === undefined || entry.scope === scope)
|
|
145
|
+
.map(functionOutline);
|
|
146
|
+
return {
|
|
147
|
+
...(source ? { source } : {}),
|
|
148
|
+
...(applicationId ? { applicationId } : {}),
|
|
149
|
+
...(pageId ? { pageId } : {}),
|
|
150
|
+
...(scope ? { scope } : {}),
|
|
151
|
+
count: functions.length,
|
|
152
|
+
functions,
|
|
153
|
+
note: functions.length === 0
|
|
154
|
+
? "Bu süzgeçle hiç fonksiyon yok; ifadeye yazmadan önce function_create ile tanımlayın (sunucu için source:'workflow', tarayıcı için source:'studio')."
|
|
155
|
+
: "Çağrı konumsaldır: signature'daki sıra bağlayıcıdır (workflow: çıplak ad, studio: Functions.<ad>). Gövde için function_get; nerede kullanıldığı için dependency_graph(module:'global-function')."
|
|
156
|
+
};
|
|
157
|
+
}));
|
|
158
|
+
server.registerTool('function_get', {
|
|
159
|
+
title: 'Fonksiyonu getir',
|
|
160
|
+
description: 'Tek fonksiyonun tam gövdesini (source, ad, kapsam, sayfa, parametreler, isAsync, body) getirir; function_update için şablon budur.',
|
|
161
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
162
|
+
annotations: readOnly
|
|
163
|
+
}, handle(config, async ({ project, environment, id }) => {
|
|
164
|
+
const payload = await platform.tenant('GET', project, `${FUNCTIONS_PATH}/${id}`, { environment });
|
|
165
|
+
const record = unwrapData(payload);
|
|
166
|
+
return isRecord(payload)
|
|
167
|
+
? { ...payload, signature: functionSignature(record), hint: usageHint(String(record.name ?? '?'), sourceOf(record), String(record.scope ?? 'application')) }
|
|
168
|
+
: payload;
|
|
169
|
+
}));
|
|
170
|
+
server.registerTool('function_create', {
|
|
171
|
+
title: 'Fonksiyon tanımla',
|
|
172
|
+
description: "Parametreli bir JavaScript fonksiyonu kaydeder; kod biçimi iki kaynakta aynıdır, fark nerede çalıştığıdır. source:'workflow' → SUNUCU: tenant geneli, workflow/middleware/automation ifadelerinde çıplak adla (toplamHesapla(10, 3)); applicationId/scope/pageId verilmez, isAsync olamaz. source:'studio' → TARAYICI: uygulamaya ait, {{ Functions.toplamHesapla(10, 3) }} bağlaması ve UI flow 'code'/'function' adımı; applicationId + scope zorunlu. Sunucu ifadesinde Functions.<ad>( yazılamaz (WF-030), tarayıcıda çıplak workflow adı yoktur. Ne zaman: aynı ifade 3+ yerde kopyalanıyorsa ya da parametreli bir hesap gerekiyorsa. Ad/parametre/kaynak kuralları istek gitmeden denetlenir (FN-001…FN-005). İlk kullanımdan önce platform_guide('functions').",
|
|
173
|
+
inputSchema: {
|
|
174
|
+
project: projectParam,
|
|
175
|
+
environment: environmentParam,
|
|
176
|
+
source: sourceParam,
|
|
177
|
+
applicationId: guid.optional().describe("source:'studio' için zorunlu — fonksiyonun ait olduğu uygulama; source:'workflow' için verilmez"),
|
|
178
|
+
scope: scopeParam.optional(),
|
|
179
|
+
pageId: guid.optional().describe("scope:'page' için zorunlu — fonksiyonun ait olduğu sayfa; scope:'application' ve source:'workflow' için verilmez"),
|
|
180
|
+
name: nameParam,
|
|
181
|
+
description: descriptionParam,
|
|
182
|
+
isAsync: isAsyncParam,
|
|
183
|
+
parameters: parametersParam,
|
|
184
|
+
body: bodyParam,
|
|
185
|
+
policyOverride: policyOverrideParam
|
|
186
|
+
},
|
|
187
|
+
annotations: writes
|
|
188
|
+
}, handle(config, async ({ project, environment, source, applicationId, scope, pageId, name, description, isAsync, parameters, body, policyOverride }) => {
|
|
189
|
+
const command = {
|
|
190
|
+
source,
|
|
191
|
+
...(applicationId ? { applicationId } : {}),
|
|
192
|
+
...(scope ? { scope } : {}),
|
|
193
|
+
...(pageId ? { pageId } : {}),
|
|
194
|
+
name,
|
|
195
|
+
description: description ?? null,
|
|
196
|
+
isAsync: isAsync === true,
|
|
197
|
+
parameters: parameters ?? [],
|
|
198
|
+
body
|
|
199
|
+
};
|
|
200
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'function', command, { operation: 'create' }, policyOverride));
|
|
201
|
+
const drift = unmappedMembers('CreateGlobalFunctionCommand', command);
|
|
202
|
+
const payload = await platform.tenant('POST', project, FUNCTIONS_PATH, { environment, body: command });
|
|
203
|
+
const saved = unwrapData(payload);
|
|
204
|
+
const response = {
|
|
205
|
+
...(isRecord(payload) ? payload : { data: payload }),
|
|
206
|
+
signature: functionSignature(saved.name ? saved : command),
|
|
207
|
+
hint: usageHint(name, source, scope ?? 'application')
|
|
208
|
+
};
|
|
209
|
+
return attachFindings(withUnmappedMembers(withWriteDiff(response, writeDiff(command, saved)), drift), findings);
|
|
210
|
+
}));
|
|
211
|
+
server.registerTool('function_update', {
|
|
212
|
+
title: 'Fonksiyonu güncelle',
|
|
213
|
+
description: "Fonksiyonu günceller. Platform ucu TAM gövde ister (PUT); gateway mevcut kaydı okuyup vermediğiniz alanları oradan doldurur — yalnız değişen alanı gönderebilirsiniz (ayrı patch aracı yoktur). source, applicationId, scope ve pageId DEĞİŞMEZ. Yanıt 'dependents' (kullanan flow/workflow adları) ve 'signatureChange' (ad ya da parametre ad/sırası değiştiyse eski→yeni) taşır; ikisi birden doluysa 'warnings' çağıranların etkileneceğini söyler — güncelleme yine YAPILIR (K12), silme ise engellenir. ADI değiştirmek: platform çağıranları aynı işlemde yeni ada TAŞIR ve yanıt 'renamedReferences' ile kaç kaydın yazıldığını söyler — source:'studio' → Functions.<eskiAd> başvuruları (akış adımları, 'function' adımının functionName'i, code bağlamaları, diğer fonksiyon gövdeleri); source:'workflow' → <eskiAd>(…) çağrıları (workflow ifadeleri, otomasyon koşulları, workflow fonksiyonu gövdeleri). Workflow kaynağında eski ad yeni adın yazılamadığı bir yerde kalırsa — çağrı dışı kullanım (değişken gibi) ya da ham sorgu ifadesi (isExpression:true rawQuery) — platform 400 ile REDDEDER (CantRenameUsedGlobalFunction) ve hiçbir şey yazmaz; düz SQL (isExpression:false) kullanım sayılmaz, yazılmaz. Fonksiyonun kendi gövdesindeki çağrılar (özyineleme) da yeni adı alır. Düz metin (static) prop'lar değişmez.",
|
|
214
|
+
inputSchema: {
|
|
215
|
+
project: projectParam,
|
|
216
|
+
environment: environmentParam,
|
|
217
|
+
id: guid,
|
|
218
|
+
name: nameParam.optional(),
|
|
219
|
+
description: descriptionParam,
|
|
220
|
+
isAsync: isAsyncParam,
|
|
221
|
+
parameters: parametersParam,
|
|
222
|
+
body: bodyParam.optional(),
|
|
223
|
+
policyOverride: policyOverrideParam
|
|
224
|
+
},
|
|
225
|
+
annotations: updates
|
|
226
|
+
}, handle(config, async ({ project, environment, id, name, description, isAsync, parameters, body, policyOverride }) => {
|
|
227
|
+
const current = unwrapData(await platform.tenant('GET', project, `${FUNCTIONS_PATH}/${id}`, { environment }));
|
|
228
|
+
if (!current.id)
|
|
229
|
+
throw new PlatformApiError(`'${id}' id'li fonksiyon bulunamadı; function_list ile doğrulayın.`, 404);
|
|
230
|
+
const source = sourceOf(current);
|
|
231
|
+
const command = {
|
|
232
|
+
id,
|
|
233
|
+
name: name ?? current.name,
|
|
234
|
+
description: description === undefined ? (current.description ?? null) : description,
|
|
235
|
+
isAsync: isAsync ?? current.isAsync === true,
|
|
236
|
+
parameters: parameters ?? (Array.isArray(current.parameters) ? current.parameters : []),
|
|
237
|
+
body: body ?? current.body
|
|
238
|
+
};
|
|
239
|
+
// Kurallar kaynağı ve kapsamı görsün (FN-001/003/004 kaynağa bağlı); PUT gövdesine girmez.
|
|
240
|
+
const definition = { ...command, source, applicationId: current.applicationId ?? null, scope: current.scope ?? null, pageId: current.pageId ?? null };
|
|
241
|
+
const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'function', definition, { operation: 'update' }, policyOverride));
|
|
242
|
+
const drift = unmappedMembers('UpdateGlobalFunctionCommand', command);
|
|
243
|
+
const payload = await platform.tenant('PUT', project, `${FUNCTIONS_PATH}/${id}`, { environment, body: command });
|
|
244
|
+
// Platform yanıtı `{ function, dependents, signatureChanged }` zarfıdır (Faz 4); eski şekil çıplak kayıttır.
|
|
245
|
+
const envelope = unwrapData(payload);
|
|
246
|
+
const saved = isRecord(envelope.function) ? envelope.function : envelope;
|
|
247
|
+
// Platform 2026-09-11'den beri adı çağıranlara yazar ve kaç kayıt yazdığını söyler; alan yoksa eski platformdur.
|
|
248
|
+
const renamedReferences = typeof envelope.renamedReferences === 'number' ? envelope.renamedReferences : undefined;
|
|
249
|
+
const renamed = name !== undefined && name !== current.name;
|
|
250
|
+
const signatureChange = describeSignatureChange(current, command);
|
|
251
|
+
let dependents;
|
|
252
|
+
if (isRecord(envelope.dependents)) {
|
|
253
|
+
dependents = summarizeDependents(envelope.dependents);
|
|
254
|
+
}
|
|
255
|
+
else {
|
|
256
|
+
try {
|
|
257
|
+
dependents = summarizeDependents(await platform.tenant('GET', project, `${FUNCTIONS_PATH}/${id}/dependencies`, { environment }));
|
|
258
|
+
}
|
|
259
|
+
catch {
|
|
260
|
+
dependents = undefined;
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
const signatureChanged = signatureChange.name !== null || signatureChange.parameters !== null;
|
|
264
|
+
// Ad çağıranlara yazıldıysa yalnız parametre değişimi çağıranı kırar; eski platformda ad da kırar.
|
|
265
|
+
const callersBreak = renamedReferences === undefined ? signatureChanged : signatureChange.parameters !== null;
|
|
266
|
+
const warningChange = renamedReferences === undefined ? signatureChange : { ...signatureChange, name: null };
|
|
267
|
+
const response = {
|
|
268
|
+
data: saved,
|
|
269
|
+
signature: functionSignature(saved.name ? saved : definition),
|
|
270
|
+
signatureChange,
|
|
271
|
+
dependents: dependents ?? { flows: [], workflows: [], total: 0 },
|
|
272
|
+
...(dependents === undefined ? { dependentsNote: 'Bağımlılık ucu okunamadı; çağıran akış olup olmadığı bilinmiyor.' } : {}),
|
|
273
|
+
...(renamedReferences !== undefined ? { renamedReferences } : {}),
|
|
274
|
+
...(dependents !== undefined && dependents.total > 0 && callersBreak
|
|
275
|
+
? { warnings: [signatureWarning(String(saved.name ?? command.name), source, warningChange, dependents)] }
|
|
276
|
+
: {}),
|
|
277
|
+
...(renamed
|
|
278
|
+
? {
|
|
279
|
+
renameNote: renamedReferences !== undefined
|
|
280
|
+
? source === 'workflow'
|
|
281
|
+
? `Ad '${String(current.name)}' → '${name}': platform ${renamedReferences} kayıttaki ${String(current.name)}(…) çağrısını yeni ada taşıdı (workflow ifadeleri, otomasyon koşulları, workflow fonksiyonu gövdeleri). Çağrı dışı bir kullanım ya da ham sorgu ifadesi kalsaydı platform 400 CantRenameUsedGlobalFunction ile reddederdi.`
|
|
282
|
+
: `Ad '${String(current.name)}' → '${name}': platform ${renamedReferences} kayıttaki ${FUNCTIONS_NAMESPACE}.${String(current.name)} başvurusunu yeni ada taşıdı (akış adımları, 'function' adımının functionName'i, code bağlamaları, diğer fonksiyon gövdeleri). Düz metin prop'lar değişmez.`
|
|
283
|
+
: source === 'workflow'
|
|
284
|
+
? `Ad '${String(current.name)}' → '${name}': platform kullanan workflow olmadığı için kabul etti; ifadelerde ${String(current.name)}(…) yazan başka bir yer varsa artık '${String(current.name)} is not defined' verir. Çağıranlar: dependency_graph(module:'global-function', id:'${id}').`
|
|
285
|
+
: `Ad '${String(current.name)}' → '${name}': ifadelerdeki ${FUNCTIONS_NAMESPACE}.${String(current.name)}(…) çağrıları artık 'is not a function' verir; 'function' adımları id ile çözülür ama functionName eski kaldı. Çağıranlar: dependency_graph(module:'global-function', id:'${id}').`
|
|
286
|
+
}
|
|
287
|
+
: {})
|
|
288
|
+
};
|
|
289
|
+
return attachFindings(withUnmappedMembers(withWriteDiff(response, writeDiff(command, saved)), drift), findings);
|
|
290
|
+
}));
|
|
291
|
+
server.registerTool('function_delete', {
|
|
292
|
+
title: 'Fonksiyonu sil',
|
|
293
|
+
description: "Fonksiyonu siler. Gateway önce bağımlılık grafiğini okur; kullanan flow/workflow varsa istek GÖNDERİLMEZ ve liste döner (409). source:'studio' → platform silmeyi engellemez (K12), bilinçli silme için acknowledgeDependents: true verin (çağıranlar 'Functions.<ad> is not a function' ile düşer). source:'workflow' → kullanan workflow varsa platform 400 ile REDDEDER (CantDeleteUsedGlobalFunction); acknowledgeDependents bunu geçemez, önce çağıran ifadeleri düzeltin. Bağımlılık ucu okunamazsa silme yine denenir ve yanıt bunu söyler.",
|
|
294
|
+
inputSchema: {
|
|
295
|
+
project: projectParam,
|
|
296
|
+
environment: environmentParam,
|
|
297
|
+
id: guid,
|
|
298
|
+
acknowledgeDependents: z.boolean().optional().describe("true: studio fonksiyonunu kullanan akış olsa da sil (çağıranlar kırılır). Varsayılan false — bağımlı varsa istek gönderilmez. workflow kaynağında platformun 400'ünü geçemez")
|
|
299
|
+
},
|
|
300
|
+
annotations: destructive
|
|
301
|
+
}, handle(config, async ({ project, environment, id, acknowledgeDependents }) => {
|
|
302
|
+
const current = unwrapData(await platform.tenant('GET', project, `${FUNCTIONS_PATH}/${id}`, { environment }));
|
|
303
|
+
if (!current.id)
|
|
304
|
+
throw new PlatformApiError(`'${id}' id'li fonksiyon bulunamadı; function_list ile doğrulayın.`, 404);
|
|
305
|
+
const source = sourceOf(current);
|
|
306
|
+
const prefix = callPrefix(source);
|
|
307
|
+
let dependents;
|
|
308
|
+
try {
|
|
309
|
+
dependents = summarizeDependents(await platform.tenant('GET', project, `${FUNCTIONS_PATH}/${id}/dependencies`, { environment }));
|
|
310
|
+
}
|
|
311
|
+
catch {
|
|
312
|
+
dependents = undefined;
|
|
313
|
+
}
|
|
314
|
+
if (dependents !== undefined && dependents.total > 0 && (acknowledgeDependents !== true || source === 'workflow')) {
|
|
315
|
+
throw new PlatformApiError(`Fonksiyonu ${dependents.total} kayıt kullanıyor (istek GÖNDERİLMEDİ) — flows: ${dependents.flows.join(', ') || '-'}; workflows: ${dependents.workflows.join(', ') || '-'}. `
|
|
316
|
+
+ (source === 'workflow'
|
|
317
|
+
? `source:'workflow' fonksiyonu kullanılırken silinemez: platform 400 CantDeleteUsedGlobalFunction döner, acknowledgeDependents bunu geçemez. Önce çağıran ifadelerdeki ${String(current.name)}(…) çağrılarını kaldırın.`
|
|
318
|
+
: `Silinirse bu akışlar '${prefix}${String(current.name)} is not a function' ile düşer. Önce çağıranları güncelleyin ya da bilinçli silme için acknowledgeDependents: true verin.`), 409);
|
|
319
|
+
}
|
|
320
|
+
await platform.tenant('DELETE', project, `${FUNCTIONS_PATH}/${id}`, { environment });
|
|
321
|
+
return {
|
|
322
|
+
deleted: id,
|
|
323
|
+
source,
|
|
324
|
+
dependents: dependents ?? { flows: [], workflows: [], total: 0 },
|
|
325
|
+
...(dependents === undefined ? { dependentsNote: 'Bağımlılık ucu okunamadı; çağıran akış olup olmadığı bilinmiyor.' } : {}),
|
|
326
|
+
...(dependents !== undefined && dependents.total > 0
|
|
327
|
+
? { note: `${dependents.total} çağıran kırıldı (acknowledgeDependents): ${[...dependents.flows, ...dependents.workflows].join(', ')} — ifadeleri düzeltin ya da fonksiyonu aynı adla yeniden tanımlayın.` }
|
|
328
|
+
: {})
|
|
329
|
+
};
|
|
330
|
+
}));
|
|
331
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
|
|
2
|
+
import { handle } from '../platform/responses.js';
|
|
3
|
+
import { z } from 'zod';
|
|
4
|
+
const identifier = /^[A-Za-z_$][A-Za-z0-9_$]*$/;
|
|
5
|
+
const nameParam = z.string().min(1).regex(identifier, 'Geçerli bir JavaScript tanımlayıcısı olmalı')
|
|
6
|
+
.describe("Motorun bağlayacağı ad; ifadelerde bu adla çağrılır (sum(1, 2)). Sistem fonksiyonlarının adları (Now, UtcNow, GenerateGuid, crypto…) rezervedir ve reddedilir");
|
|
7
|
+
const functionParam = z.string().min(1)
|
|
8
|
+
.describe("Klasik ya da arrow JavaScript FONKSİYON İFADESİ: '(left, right) => left + right' ya da 'function (a) { return a * 2 }'. Deyim değil ifade olmalı — 'return a + b' gibi bir gövde tek başına reddedilir. Platform kaydetmeden önce gerçek bir Jint motorunda çalıştırıp sonucun çağrılabilir olduğunu doğrular; SÖZDİZİMİ ve çağrılabilirlik denetlenir, gövdenin içindeki serbest değişkenler DENETLENMEZ");
|
|
9
|
+
const descriptionParam = z.string().min(1)
|
|
10
|
+
.describe('Fonksiyonun ne işe yaradığı; Studio kod editöründe autocomplete infobox başlığı olarak görünür. Zorunludur');
|
|
11
|
+
const schemaParam = z.array(z.object({
|
|
12
|
+
name: z.string().min(1).regex(identifier, 'Geçerli bir JavaScript tanımlayıcısı olmalı')
|
|
13
|
+
.describe('Parametre adı; fonksiyon içinde benzersiz olmalı'),
|
|
14
|
+
description: z.string().min(1).describe('Parametrenin ne olduğu; infobox\'ta parametre satırı olarak görünür. Zorunludur')
|
|
15
|
+
})).describe('Parametre şeması, SIRALI. Yalnız dokümantasyon ve autocomplete imzası için kullanılır — motor çağrıyı bu şemaya göre DOĞRULAMAZ, eksik argüman undefined gelir. Parametresiz fonksiyonda boş dizi verin');
|
|
16
|
+
export function registerGlobalFunctionTools(server, platform) {
|
|
17
|
+
const config = platform.config;
|
|
18
|
+
server.registerTool('global_function_list', {
|
|
19
|
+
title: 'Global fonksiyonları listele',
|
|
20
|
+
description: "Tenant kapsamındaki global JavaScript fonksiyonlarını listeler. Bunlar her workflow/automation/middleware koşumunda ve Studio preview'ında motora bağlanır; ifadelerde çıplak adla çağrılırlar.",
|
|
21
|
+
inputSchema: { project: projectParam, environment: environmentParam },
|
|
22
|
+
annotations: readOnly
|
|
23
|
+
}, handle(config, ({ project, environment }) => platform.tenant('GET', project, '/global-functions', { environment })));
|
|
24
|
+
server.registerTool('global_function_get', {
|
|
25
|
+
title: 'Global fonksiyonu getir',
|
|
26
|
+
description: 'Tek bir global fonksiyonun adını, gövdesini, açıklamasını ve parametre şemasını getirir.',
|
|
27
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
28
|
+
annotations: readOnly
|
|
29
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('GET', project, `/global-functions/${id}`, { environment })));
|
|
30
|
+
server.registerTool('global_function_create', {
|
|
31
|
+
title: 'Global fonksiyon tanımla',
|
|
32
|
+
description: "Yeni global fonksiyon kaydeder. Ad tenant içinde BENZERSİZDİR. Fonksiyon açıklaması ve HER parametre açıklaması zorunludur — Studio bunları autocomplete infobox'ında gösterir. Gövde bağlamdan bağımsız olmalı: içinde 'input' ya da bir adım adı gibi SERBEST DEĞİŞKEN kullanırsanız kayıt geçer ama fonksiyon o değişkenin tanımlı olmadığı her akışta 'X is not defined' ile patlar; dinamik veriyi parametre olarak geçirin.",
|
|
33
|
+
inputSchema: {
|
|
34
|
+
project: projectParam,
|
|
35
|
+
environment: environmentParam,
|
|
36
|
+
name: nameParam,
|
|
37
|
+
function: functionParam,
|
|
38
|
+
description: descriptionParam,
|
|
39
|
+
schema: schemaParam
|
|
40
|
+
},
|
|
41
|
+
annotations: writes
|
|
42
|
+
}, handle(config, ({ project, environment, name, function: body, description, schema }) => platform.tenant('POST', project, '/global-functions', { environment, body: { name, function: body, description, schema } })));
|
|
43
|
+
server.registerTool('global_function_update', {
|
|
44
|
+
title: 'Global fonksiyonu güncelle',
|
|
45
|
+
description: "Global fonksiyonun tamamını değiştirir (PUT — dört alanı da gönderin). ADI DEĞİŞTİRMEK, fonksiyonu çağıran bir workflow varsa 400 ile reddedilir (CantRenameUsedGlobalFunctionException); gövde/açıklama/şema değişikliği serbesttir. Adı gerçekten değiştirmeniz gerekiyorsa önce çağıran akışlardaki ifadeleri güncelleyin.",
|
|
46
|
+
inputSchema: {
|
|
47
|
+
project: projectParam,
|
|
48
|
+
environment: environmentParam,
|
|
49
|
+
id: guid,
|
|
50
|
+
name: nameParam,
|
|
51
|
+
function: functionParam,
|
|
52
|
+
description: descriptionParam,
|
|
53
|
+
schema: schemaParam
|
|
54
|
+
},
|
|
55
|
+
annotations: updates
|
|
56
|
+
}, handle(config, ({ project, environment, id, name, function: body, description, schema }) => platform.tenant('PUT', project, `/global-functions/${id}`, { environment, body: { name, function: body, description, schema } })));
|
|
57
|
+
server.registerTool('global_function_delete', {
|
|
58
|
+
title: 'Global fonksiyonu sil',
|
|
59
|
+
description: "Global fonksiyonu siler. Fonksiyonu çağıran bir workflow varsa 400 ile reddedilir (CantDeleteUsedGlobalFunctionException) ve mesaj çağıran akışların adlarını sayar. Eşleşme tam tanımlayıcı sınırıyla aranır: 'checksum' ya da 'mySum' içeren bir ifade 'sum' çağrısı sayılmaz.",
|
|
60
|
+
inputSchema: { project: projectParam, environment: environmentParam, id: guid },
|
|
61
|
+
annotations: destructive
|
|
62
|
+
}, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/global-functions/${id}`, { environment })));
|
|
63
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
export const importModes = ['create', 'append', 'upsert', 'update'];
|
|
3
|
+
export const importErrorPolicies = ['skip-rows', 'all-or-nothing'];
|
|
4
|
+
export const importEmptyCellPolicies = ['keep', 'clear'];
|
|
5
|
+
export const importUnmatchedPolicies = ['error', 'create', 'blank'];
|
|
6
|
+
export const importRowActions = ['skip', 'blank', 'apply-suggestion'];
|
|
7
|
+
const newTable = z.object({
|
|
8
|
+
name: z.string().min(1),
|
|
9
|
+
slug: z.string().min(1)
|
|
10
|
+
});
|
|
11
|
+
const relationChoice = z.object({
|
|
12
|
+
targetEntityId: z.string().uuid().optional().describe('Mevcut hedef tablo; yeni tablo kurulacaksa `newTable` verin'),
|
|
13
|
+
newTable: newTable.optional().describe('İlişki hedefi olarak kurulacak yeni tablo'),
|
|
14
|
+
matchField: z.string().min(1).optional().describe("Hedefte eşleşme alanı; display ya da benzersiz olmalı, değilse 422 (bu fazda değer yalnız id olarak kabul edilir)"),
|
|
15
|
+
unmatched: z.enum(importUnmatchedPolicies).default('error').describe("Eşleşmeyen değer: 'error' satırı hatalı sayar, 'create' hedefte yeni kayıt açar, 'blank' alanı boş bırakır"),
|
|
16
|
+
delimiter: z.string().min(1).optional().describe('Çoklu değer ayracı (Multi Relation)'),
|
|
17
|
+
parentColumnIndex: z.number().int().min(0).optional().describe('Hiyerarşide üst sütunun indeksi (İl → İlçe)'),
|
|
18
|
+
parentTable: newTable.optional(),
|
|
19
|
+
many: z.boolean().optional()
|
|
20
|
+
});
|
|
21
|
+
const fieldChoice = z.object({
|
|
22
|
+
slug: z.string().min(1),
|
|
23
|
+
name: z.string().min(1),
|
|
24
|
+
commonType: z.string().min(1).describe("Platform commonType: text | number | float | bool | date | time | datetime | guid"),
|
|
25
|
+
dbType: z.string().min(1).describe('PostgreSQL tipi: VARCHAR | TEXT | INT8 | FLOAT4 | BOOL | DATE | TIME | TIMESTAMP | UUID'),
|
|
26
|
+
nullable: z.boolean().default(true),
|
|
27
|
+
unique: z.boolean().default(false),
|
|
28
|
+
display: z.boolean().default(false)
|
|
29
|
+
});
|
|
30
|
+
const columnChoice = z.object({
|
|
31
|
+
index: z.number().int().min(0).describe('Dosyadaki 0 tabanlı sütun sırası (analiz yanıtındaki `index`)'),
|
|
32
|
+
include: z.boolean().default(true),
|
|
33
|
+
field: fieldChoice.optional().describe("Açılacak alan; mevcut tabloda zaten varsa `existingFieldSlug` verilir, yoksa `field` alanı tabloya ekler"),
|
|
34
|
+
existingFieldSlug: z.string().min(1).optional().describe('Mevcut tablodaki hedef alanın slug\'ı'),
|
|
35
|
+
relation: relationChoice.nullish().describe('İlişki kuralı; mevcut tabloda zaten ilişki olan bir alana yazıyorsanız boş bırakabilirsiniz, backend hedefi alanın kendisinden çözer'),
|
|
36
|
+
language: z.string().min(1).optional().describe("Çok dilli alanın dil kodu; başlık 'Ad (tr)' biçimindeyse analiz bunu önerir")
|
|
37
|
+
});
|
|
38
|
+
const tableChoice = z.object({
|
|
39
|
+
sheetKey: z.string().min(1).describe('Analiz yanıtındaki `sheets[].key`'),
|
|
40
|
+
mode: z.enum(importModes).describe("'create' yeni tablo kurar, 'append' ekler, 'upsert' anahtara göre ekle+güncelle, 'update' yalnız günceller"),
|
|
41
|
+
target: z.object({
|
|
42
|
+
entityId: z.string().uuid().optional().describe("create dışındaki modlarda zorunlu"),
|
|
43
|
+
name: z.string().min(1).optional().describe('create modunda zorunlu'),
|
|
44
|
+
slug: z.string().min(1).optional().describe('create modunda zorunlu')
|
|
45
|
+
}),
|
|
46
|
+
keyField: z.string().min(1).optional().describe("upsert/update'te zorunlu; benzersiz bir alan, 'code' ya da 'id'"),
|
|
47
|
+
emptyCellPolicy: z.enum(importEmptyCellPolicies).default('keep').describe("'keep' boş hücreyi yok sayar (mevcut değer silinmez), 'clear' alanı boşaltır"),
|
|
48
|
+
columns: z.array(columnChoice).min(1)
|
|
49
|
+
});
|
|
50
|
+
export const importChoicesShape = {
|
|
51
|
+
fileId: z.string().uuid().describe('import_analyze ile analiz edilmiş dosya'),
|
|
52
|
+
locale: z.enum(['tr-TR', 'en-US']).optional().describe('Analizde kullanılan profilin AYNISI verilmelidir; farklı profil farklı satır üretir'),
|
|
53
|
+
headerRow: z.number().int().min(0).optional().describe("Analizde verilen başlık satırının AYNISI (CSV/XLSX); JSON'da yok sayılır, başlık her zaman 1. satırdaki anahtarlardır"),
|
|
54
|
+
csv: z.object({ delimiter: z.string().min(1).optional(), encoding: z.string().min(1).optional() }).optional(),
|
|
55
|
+
errorPolicy: z.enum(importErrorPolicies).default('skip-rows').describe("'skip-rows' hatalı satırı atlar ve kalanını yazar, 'all-or-nothing' bir hata varsa hiçbir şey yazmaz"),
|
|
56
|
+
tables: z.array(tableChoice).min(1)
|
|
57
|
+
};
|
|
58
|
+
export const importChoicesSchema = z.object(importChoicesShape);
|
|
59
|
+
export function validateChoices(choices) {
|
|
60
|
+
const problems = [];
|
|
61
|
+
for (const table of choices.tables) {
|
|
62
|
+
if (table.mode === 'create' && (!table.target.name || !table.target.slug)) {
|
|
63
|
+
problems.push(`'${table.sheetKey}': mode 'create' için target.name ve target.slug zorunlu.`);
|
|
64
|
+
}
|
|
65
|
+
if (table.mode !== 'create' && !table.target.entityId) {
|
|
66
|
+
problems.push(`'${table.sheetKey}': mode '${table.mode}' için target.entityId zorunlu.`);
|
|
67
|
+
}
|
|
68
|
+
if ((table.mode === 'upsert' || table.mode === 'update') && !table.keyField) {
|
|
69
|
+
problems.push(`'${table.sheetKey}': mode '${table.mode}' için keyField zorunlu — benzersiz bir alan, 'code' ya da 'id'.`);
|
|
70
|
+
}
|
|
71
|
+
if (table.mode === 'create' && table.columns.some(column => column.include !== false && !column.field && !column.relation)) {
|
|
72
|
+
problems.push(`'${table.sheetKey}': mode 'create' içeren her sütun ya 'field' ya 'relation' taşımalı (dahil edilmeyen sütuna include: false verin).`);
|
|
73
|
+
}
|
|
74
|
+
if (table.mode !== 'create' && table.columns.some(column => column.include !== false && !column.existingFieldSlug && !column.relation && !column.field)) {
|
|
75
|
+
problems.push(`'${table.sheetKey}': mevcut tabloya yazarken her sütun 'existingFieldSlug', 'field' ya da 'relation' taşımalı — 'field' tabloda olmayan sütun için yeni alan açar.`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return problems;
|
|
79
|
+
}
|