@appaflytech/zeyrek-ai-mcp 0.0.1

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.
Files changed (88) hide show
  1. package/.env.example +46 -0
  2. package/README.md +260 -0
  3. package/dist/config.js +128 -0
  4. package/dist/index.js +88 -0
  5. package/dist/knowledge/componentCatalog.js +438 -0
  6. package/dist/knowledge/componentEvents.js +164 -0
  7. package/dist/knowledge/coverage.js +60 -0
  8. package/dist/knowledge/normalize.js +1309 -0
  9. package/dist/knowledge/outlines.js +167 -0
  10. package/dist/knowledge/policy.js +44 -0
  11. package/dist/knowledge/reservedTables.js +75 -0
  12. package/dist/knowledge/rules.js +1725 -0
  13. package/dist/observability/trace.js +337 -0
  14. package/dist/platform/errors.js +132 -0
  15. package/dist/platform/http.js +169 -0
  16. package/dist/platform/responses.js +94 -0
  17. package/dist/platform/token.js +79 -0
  18. package/dist/resources/examples/entity-pair-m2m.json +105 -0
  19. package/dist/resources/examples/flow-list-to-form.json +65 -0
  20. package/dist/resources/examples/flow-load-table.json +178 -0
  21. package/dist/resources/examples/microservice-table-list.json +49 -0
  22. package/dist/resources/examples/middleware-authenticate.json +194 -0
  23. package/dist/resources/examples/page-detail-profile.json +95 -0
  24. package/dist/resources/examples/page-dynamic-form.json +221 -0
  25. package/dist/resources/examples/page-form-upsert.json +279 -0
  26. package/dist/resources/examples/page-html-embed.json +106 -0
  27. package/dist/resources/examples/page-sign-in.json +194 -0
  28. package/dist/resources/examples/workflow-basic.json +94 -0
  29. package/dist/resources/guides/component.md +108 -0
  30. package/dist/resources/guides/entity.md +348 -0
  31. package/dist/resources/guides/expressions.md +139 -0
  32. package/dist/resources/guides/getting-started.md +109 -0
  33. package/dist/resources/guides/html.md +106 -0
  34. package/dist/resources/guides/microservice.md +313 -0
  35. package/dist/resources/guides/middleware.md +271 -0
  36. package/dist/resources/guides/modify-existing.md +35 -0
  37. package/dist/resources/guides/page.md +1574 -0
  38. package/dist/resources/guides/view.md +89 -0
  39. package/dist/resources/guides/workflow.md +359 -0
  40. package/dist/resources/knowledge.js +44 -0
  41. package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
  42. package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
  43. package/dist/resources/schemas/CreateEntityCommand.json +387 -0
  44. package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
  45. package/dist/resources/schemas/CreateQueryCommand.json +43 -0
  46. package/dist/resources/schemas/CreateViewCommand.json +158 -0
  47. package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
  48. package/dist/resources/schemas/Filter.json +124 -0
  49. package/dist/resources/schemas/GetRowsQuery.json +122 -0
  50. package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
  51. package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
  52. package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
  53. package/dist/resources/schemas/StepDto.json +900 -0
  54. package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
  55. package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
  56. package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
  57. package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
  58. package/dist/resources/schemas/UpdateViewCommand.json +153 -0
  59. package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
  60. package/dist/resources/schemas.js +40 -0
  61. package/dist/server.js +63 -0
  62. package/dist/tools/access.js +27 -0
  63. package/dist/tools/applications.js +59 -0
  64. package/dist/tools/automations.js +103 -0
  65. package/dist/tools/common.js +18 -0
  66. package/dist/tools/components.js +280 -0
  67. package/dist/tools/datasources.js +82 -0
  68. package/dist/tools/dependencies.js +17 -0
  69. package/dist/tools/deployments.js +17 -0
  70. package/dist/tools/dtos.js +54 -0
  71. package/dist/tools/entities.js +166 -0
  72. package/dist/tools/files.js +138 -0
  73. package/dist/tools/flows.js +226 -0
  74. package/dist/tools/folders.js +51 -0
  75. package/dist/tools/layouts.js +105 -0
  76. package/dist/tools/microservices.js +198 -0
  77. package/dist/tools/organizations.js +65 -0
  78. package/dist/tools/pages.js +597 -0
  79. package/dist/tools/platformInfo.js +44 -0
  80. package/dist/tools/projects.js +152 -0
  81. package/dist/tools/queries.js +48 -0
  82. package/dist/tools/rows.js +115 -0
  83. package/dist/tools/screens.js +97 -0
  84. package/dist/tools/templates.js +65 -0
  85. package/dist/tools/variables.js +94 -0
  86. package/dist/tools/views.js +58 -0
  87. package/dist/tools/workflows.js +196 -0
  88. package/package.json +49 -0
@@ -0,0 +1,226 @@
1
+ import { fetchHost, flowPath, hostParam, hostUpdateBody, unwrap } from './screens.js';
2
+ import { guid, projectParam, environmentParam, readOnly, writes, updates, destructive } from './common.js';
3
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
4
+ import { normalizeFlowSteps, withNormalization } from '../knowledge/normalize.js';
5
+ import { flowBodyWarnings } from '../knowledge/coverage.js';
6
+ import { PlatformApiError } from '../platform/errors.js';
7
+ import { handle } from '../platform/responses.js';
8
+ import { policyRules } from '../knowledge/rules.js';
9
+ import { randomUUID } from 'node:crypto';
10
+ import { z } from 'zod';
11
+ const stepsParam = z.array(z.record(z.unknown())).describe("Adım listesi (StepManagementSchema: { stepId, name, type, kind, args, branches }). DİKKAT: branches[].nextStepId bir GUID değil, hedef adımın stepId STRING'idir; dal ADI motorun anahtarıdır ve start/setter/code adımlarında 'always' olmak zorundadır. Tam şablon: ngapps://examples/flow-load-table (ya da flow_get çıktısı); adım tipleri ve setter/parameters şekli platform_guide('page') Kural 1c.");
12
+ const policyOverrideParam = z.boolean().optional();
13
+ const flowIdParam = guid.describe("Akış id'si; şemadaki events[].value bu id'dir");
14
+ function flowSummary(flow) {
15
+ return {
16
+ id: flow.id,
17
+ name: flow.name,
18
+ slug: flow.slug,
19
+ steps: Array.isArray(flow.steps) ? flow.steps.length : 0,
20
+ errorSteps: Array.isArray(flow.errorSteps) ? flow.errorSteps.length : 0
21
+ };
22
+ }
23
+ async function hostEnvelope(platform, host, project, environment, applicationId, hostId) {
24
+ const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
25
+ return { [host]: hostUpdateBody(host, { ...record, applicationId }) };
26
+ }
27
+ async function requiredParameterWarnings(platform, project, environment, applicationId, steps) {
28
+ const warnings = [];
29
+ const microserviceSteps = steps
30
+ .map(step => ({ step, args: (step.args ?? {}) }))
31
+ .filter(entry => entry.step.type === 'microservice' && typeof entry.args.microserviceId === 'string');
32
+ const requiredFiltersByMicroservice = new Map();
33
+ const paginationByMicroservice = new Map();
34
+ for (const microserviceId of new Set(microserviceSteps.map(entry => entry.args.microserviceId))) {
35
+ try {
36
+ const definition = unwrap(await platform.tenant('GET', project, `/microservices/${applicationId}/${microserviceId}`, { environment }));
37
+ const parameters = (definition.sourceParameters ?? {});
38
+ const flattenFilters = (filters) => filters
39
+ .filter((filter) => Boolean(filter) && typeof filter === 'object')
40
+ .flatMap(filter => [filter, ...flattenFilters(Array.isArray(filter.children) ? filter.children : [])]);
41
+ // Uç okunduğunda filtrede fieldSlug YOKTUR (TableFilter yalnız fieldId taşır) — parametre
42
+ // ADI, platformun ürettiği parameterScheme'de durur: HandleFilters her dış kaynaklı filtre
43
+ // için { id: <alan id>, name: <alan slug'ı ya da 'alias.slug'>, source } satırı yazar
44
+ // (Microservice.SourceEntityParameterScheme:1031). Eskiden fieldId GUID'i parametre adıyla
45
+ // karşılaştırılıyordu ve zorunlu filtreli her uçta uyarı HER ZAMAN çıkıyordu (crm koşumu B-13).
46
+ const parameterNameByFieldId = new Map((Array.isArray(definition.parameterScheme) ? definition.parameterScheme : [])
47
+ .filter((item) => Boolean(item) && typeof item === 'object')
48
+ .map(item => [String(item.id ?? ''), String(item.name ?? '')])
49
+ .filter(([id, name]) => id !== '' && name !== ''));
50
+ requiredFiltersByMicroservice.set(microserviceId, flattenFilters(Array.isArray(parameters.filters) ? parameters.filters : [])
51
+ .filter(filter => filter.isRequired === true && filter.source !== 'expression')
52
+ .map(filter => String(filter.fieldSlug ?? parameterNameByFieldId.get(String(filter.fieldId ?? '')) ?? '')));
53
+ const limit = (parameters.limit ?? null);
54
+ if (limit && typeof limit.name === 'string' && limit.name.length > 0) {
55
+ paginationByMicroservice.set(microserviceId, { limitName: limit.name, offsetName: String(limit.offset ?? '') });
56
+ }
57
+ }
58
+ catch {
59
+ requiredFiltersByMicroservice.set(microserviceId, []);
60
+ }
61
+ }
62
+ const rowsConsumers = rowsTargetsByStepName(steps);
63
+ for (const entry of microserviceSteps) {
64
+ const providedParameterNames = (Array.isArray(entry.args.parameters) ? entry.args.parameters : [])
65
+ .filter((parameter) => Boolean(parameter) && typeof parameter === 'object')
66
+ .map(parameter => String(parameter.name ?? ''));
67
+ const missing = (requiredFiltersByMicroservice.get(entry.args.microserviceId) ?? [])
68
+ .filter(slug => slug && !providedParameterNames.includes(slug));
69
+ if (missing.length > 0) {
70
+ warnings.push(`FLOW-003: '${entry.step.stepId ?? entry.step.name}' adımının çağırdığı microservice'in zorunlu (isRequired) filtre parametreleri eksik: ${missing.join(', ')} — runtime'da MissingParameterException verir. args.parameters'a ekleyin; kimlik parametresiyse kalıcı çözüm middleware + user.id filtresidir (platform_guide('middleware')).`);
71
+ }
72
+ const pagination = paginationByMicroservice.get(entry.args.microserviceId);
73
+ const rowsTarget = rowsConsumers.get(String(entry.step.name ?? ''));
74
+ if (!pagination || !rowsTarget)
75
+ continue;
76
+ const missingPagination = [pagination.limitName, pagination.offsetName]
77
+ .filter(name => name.length > 0 && !providedParameterNames.includes(name));
78
+ if (missingPagination.length > 0) {
79
+ warnings.push(`FLOW-013: '${entry.step.stepId ?? entry.step.name}' adımının sonucu '${rowsTarget}' komponentinin rows prop'una yazılıyor ama uç query-string'ten ${missingPagination.join('/')} alırken adım bunları GEÇMİYOR — TableMicroserviceExecutor.cs:178 sayfalamayı yalnız limit doluysa uygular, yani sunucu her çağrıda TÜM kayıtları döndürür ve tablonun pagination olayı yeni sayfa çekemez. Her biri için args.parameters'a { name: '<ad>', type: 'text', source: 'query-string', value: { type: 'property', ref: { source: 'component', element: '${rowsTarget}', property: '<ad>' } } } ekleyin; tabloda ayrıca isPaginated: true ve pagination olayı bu akışa bağlı olmalı.`);
80
+ }
81
+ }
82
+ return warnings;
83
+ }
84
+ /**
85
+ * Hangi microservice adımının sonucu bir komponentin `rows` prop'una yazılıyor: setter iteratee'sinin
86
+ * storage anahtarı (mapping.source doluysa o, değilse value.ref — setter.ts:161) adımın ADIdır.
87
+ * Liste akışını böyle tanıyoruz; dönen sözlük adım adı → hedef elementId.
88
+ */
89
+ function rowsTargetsByStepName(steps) {
90
+ const targets = new Map();
91
+ for (const step of steps.filter(step => step.type === 'setter')) {
92
+ const iteratees = Array.isArray((step.args ?? {}).iteratees)
93
+ ? (step.args ?? {}).iteratees
94
+ : [];
95
+ for (const iteratee of iteratees.filter((iteratee) => Boolean(iteratee) && typeof iteratee === 'object')) {
96
+ if (iteratee.property !== 'rows')
97
+ continue;
98
+ const mapping = (iteratee.mapping ?? {});
99
+ const value = (iteratee.value ?? {});
100
+ const reference = typeof mapping.source === 'string' && mapping.source.length > 0
101
+ ? mapping.source
102
+ : typeof value.ref === 'string' ? value.ref : '';
103
+ const stepName = reference.split('.')[0] ?? '';
104
+ if (stepName.length > 0 && typeof iteratee.element === 'string')
105
+ targets.set(stepName, iteratee.element);
106
+ }
107
+ }
108
+ return targets;
109
+ }
110
+ export function registerFlowTools(server, platform) {
111
+ const config = platform.config;
112
+ server.registerTool('flow_list', {
113
+ title: 'Akışları listele',
114
+ description: "Barındırıcının olay akışlarını listeler. Sayfada ayrı bir liste ucu vardır; layout ve komponentte akışlar kaydın kendi gövdesinden okunur.",
115
+ inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid },
116
+ annotations: readOnly
117
+ }, handle(config, async ({ project, environment, host, applicationId, hostId }) => {
118
+ if (host === 'page') {
119
+ const payload = await platform.tenant('GET', project, flowPath('page', applicationId, hostId), { environment });
120
+ return { flows: (payload?.data ?? []).map(flowSummary) };
121
+ }
122
+ const record = await fetchHost(platform, host, project, environment, applicationId, hostId);
123
+ return { flows: (Array.isArray(record.flows) ? record.flows : []).map(flowSummary) };
124
+ }));
125
+ server.registerTool('flow_get', {
126
+ title: 'Akış getir',
127
+ description: 'Tek akışı adımlarıyla getirir; yeni akış yazarken en iyi şablon budur.',
128
+ inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
129
+ annotations: readOnly
130
+ }, handle(config, ({ project, environment, host, applicationId, hostId, flowId }) => platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment })));
131
+ server.registerTool('flow_create', {
132
+ title: 'Akış oluştur',
133
+ description: "Sayfa/layout/komponent için olay akışı oluşturur. Platform bu uçta barındırıcının TAM gövdesini de ister (akış ile şemadaki olay bağı tek işlemde tutarlı kalsın diye); gateway barındırıcıyı okuyup gövdeye kendisi koyar. Akışı bir komponent olayına bağlamak ayrı adımdır: page_schema_patch ile set_event {target, event, eventValue: <flow id>}.",
134
+ inputSchema: {
135
+ project: projectParam,
136
+ environment: environmentParam,
137
+ host: hostParam,
138
+ applicationId: guid,
139
+ hostId: guid,
140
+ name: z.string().min(1),
141
+ slug: z.string().min(1),
142
+ steps: stepsParam,
143
+ errorSteps: stepsParam.optional(),
144
+ flowId: guid.optional().describe('Verilmezse platform üretir; şemaya önceden bağlamak için kendiniz üretebilirsiniz'),
145
+ policyOverride: policyOverrideParam
146
+ },
147
+ annotations: writes
148
+ }, handle(config, async ({ project, environment, host, applicationId, hostId, name, slug, steps, errorSteps, flowId, policyOverride }) => {
149
+ const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
150
+ const resolvedFlowId = flowId ?? randomUUID();
151
+ const flow = {
152
+ id: resolvedFlowId,
153
+ name,
154
+ slug,
155
+ steps,
156
+ errorSteps: errorSteps ?? [],
157
+ ...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
158
+ };
159
+ const normalized = [...normalizeFlowSteps(flow.steps), ...normalizeFlowSteps(flow.errorSteps)];
160
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'flow', flow, { operation: 'create' }, policyOverride));
161
+ const parameterWarnings = [
162
+ ...await requiredParameterWarnings(platform, project, environment, applicationId, steps),
163
+ ...flowBodyWarnings(steps)
164
+ ];
165
+ await platform.tenant('POST', project, flowPath(host, applicationId, hostId), { environment, body: { flow, ...envelope } });
166
+ return attachFindings(withNormalization({
167
+ host,
168
+ hostId,
169
+ flow: { id: resolvedFlowId, name, slug, steps: steps.length },
170
+ ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}),
171
+ hint: "Akışı bir olaya bağlamak için: page_schema_patch ops:[{op:'set_event', target:<elementId>, event:'<component_get events listesindeki ad>', eventValue:<flow id>}]. Olay adları komponente özeldir ve 'on' ÖNEKİ ALMAZ (button → 'click', form → 'submit', table → 'mount'/'select'/'sort'…); uydurma ad 200 döner ama bağ sessizce ölü kalır — gateway PAGE-008 ile yakalar."
172
+ }, normalized), findings);
173
+ }));
174
+ server.registerTool('flow_update', {
175
+ title: 'Akış güncelle',
176
+ description: 'Akışın adını/adımlarını değiştirir. Gövde replace\'tir: verilen steps listesi akışın yeni tam hâlidir. Barındırıcı gövdesini gateway ekler.',
177
+ inputSchema: {
178
+ project: projectParam,
179
+ environment: environmentParam,
180
+ host: hostParam,
181
+ applicationId: guid,
182
+ hostId: guid,
183
+ flowId: flowIdParam,
184
+ name: z.string().min(1).optional(),
185
+ slug: z.string().min(1).optional(),
186
+ steps: stepsParam.optional(),
187
+ errorSteps: stepsParam.optional(),
188
+ policyOverride: policyOverrideParam
189
+ },
190
+ annotations: updates
191
+ }, handle(config, async ({ project, environment, host, applicationId, hostId, flowId, name, slug, steps, errorSteps, policyOverride }) => {
192
+ const current = unwrap(await platform.tenant('GET', project, flowPath(host, applicationId, hostId, flowId), { environment }));
193
+ if (!current.id)
194
+ throw new PlatformApiError(`'${flowId}' id'li akış bulunamadı; flow_list ile doğrulayın.`);
195
+ const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
196
+ const flow = {
197
+ id: flowId,
198
+ name: name ?? current.name,
199
+ slug: slug ?? current.slug,
200
+ steps: steps ?? current.steps ?? [],
201
+ errorSteps: errorSteps ?? current.errorSteps ?? [],
202
+ ...(host === 'page' ? { pageId: hostId } : host === 'layout' ? { layoutId: hostId } : { componentId: hostId })
203
+ };
204
+ const authored = { steps: steps ?? [], errorSteps: errorSteps ?? [] };
205
+ const normalized = [...normalizeFlowSteps(authored.steps), ...normalizeFlowSteps(authored.errorSteps)];
206
+ const findings = steps || errorSteps
207
+ ? enforcePolicy(evaluatePolicy(config, policyRules, 'flow', authored, { operation: 'update' }, policyOverride))
208
+ : [];
209
+ const parameterWarnings = [
210
+ ...await requiredParameterWarnings(platform, project, environment, applicationId, flow.steps),
211
+ ...flowBodyWarnings(flow.steps)
212
+ ];
213
+ await platform.tenant('PUT', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { flow, ...envelope } });
214
+ return attachFindings(withNormalization({ host, hostId, flow: flowSummary(flow), ...(parameterWarnings.length > 0 ? { parameterWarnings } : {}) }, normalized), findings);
215
+ }));
216
+ server.registerTool('flow_delete', {
217
+ title: 'Akış sil',
218
+ description: "Akışı siler. Bu uç GÖVDE ister (barındırıcı komutu) — gövdesiz silme şemadaki olay bağını sarkıtır; gateway gövdeyi kendisi kurar. Olay bağını da temizlemek için ayrıca page_schema_patch set_event ile eventValue'yu boşaltın.",
219
+ inputSchema: { project: projectParam, environment: environmentParam, host: hostParam, applicationId: guid, hostId: guid, flowId: flowIdParam },
220
+ annotations: destructive
221
+ }, handle(config, async ({ project, environment, host, applicationId, hostId, flowId }) => {
222
+ const envelope = await hostEnvelope(platform, host, project, environment, applicationId, hostId);
223
+ await platform.tenant('DELETE', project, flowPath(host, applicationId, hostId, flowId), { environment, body: { id: flowId, ...envelope } });
224
+ return { deleted: flowId, host, hostId };
225
+ }));
226
+ }
@@ -0,0 +1,51 @@
1
+ import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { handle } from '../platform/responses.js';
3
+ import { z } from 'zod';
4
+ export function registerFolderTools(server, platform) {
5
+ const config = platform.config;
6
+ server.registerTool('folder_list', {
7
+ title: 'Klasörleri listele',
8
+ description: 'Proje klasör ağacını listeler (drive/dosya organizasyonu).',
9
+ inputSchema: { project: projectParam, environment: environmentParam, searchTerm: z.string().optional(), parentId: guid.optional(), ...pagingParams },
10
+ annotations: readOnly
11
+ }, handle(config, ({ project, environment, ...query }) => platform.tenant('GET', project, '/folders', { environment, query })));
12
+ server.registerTool('folder_get', {
13
+ title: 'Klasör getir',
14
+ description: 'Tek klasörü getirir; breadcrumb: true ile üst yol zinciri döner.',
15
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, breadcrumb: z.boolean().optional() },
16
+ annotations: readOnly
17
+ }, handle(config, ({ project, environment, id, breadcrumb }) => platform.tenant('GET', project, breadcrumb ? `/folders/${id}/breadcrumb` : `/folders/${id}`, { environment })));
18
+ server.registerTool('folder_create', {
19
+ title: 'Klasör oluştur',
20
+ description: "Yeni klasör oluşturur (definition = CreateFolderCommand: { name, parentId? }). DİKKAT: fiziksel klasör adı ve depolama yolu ASCII'ye katlanır ('Donanım' → 'Donanim') ve aynı üst klasörde çakışırsa sonuna ' (1)' eklenir — kullanıcıya gösterilen displayName ham adı korur. Yanıttaki name/path katlanmış hâldir, dosya yollarını oradan okuyun.",
21
+ inputSchema: { project: projectParam, environment: environmentParam, definition: definitionParam },
22
+ annotations: writes
23
+ }, handle(config, ({ project, environment, definition }) => platform.tenant('POST', project, '/folders', { environment, body: definition })));
24
+ server.registerTool('folder_update', {
25
+ title: 'Klasör yetkilerini güncelle',
26
+ description: "Klasörün YETKİLENDİRMESİNİ günceller — `UpdateFolderCommand`'ın alanları yalnız `middlewareIds` ve `roleIds`'tir. Ad değiştirmek için folder_rename kullanın; **klasör TAŞIMA (parentId değiştirme) platformda desteklenmiyor** — gövdeye parentId/name koyarsanız sessizce yok sayılır ve istek yine 200 döner (etkinlik turu Ç3).",
27
+ inputSchema: {
28
+ project: projectParam,
29
+ environment: environmentParam,
30
+ id: guid,
31
+ middlewareIds: z.array(guid).optional().describe('Klasöre erişimden önce koşacak middleware workflow id\'leri'),
32
+ roleIds: z.array(guid).optional().describe('Klasörü görebilecek roller; boş = kısıt yok')
33
+ },
34
+ annotations: updates
35
+ }, handle(config, ({ project, environment, id, middlewareIds, roleIds }) => platform.tenant('PUT', project, `/folders/${id}`, {
36
+ environment,
37
+ body: { id, middlewareIds: middlewareIds ?? [], roleIds: roleIds ?? [] }
38
+ })));
39
+ server.registerTool('folder_rename', {
40
+ title: 'Klasörü yeniden adlandır',
41
+ description: "Klasörün görünen adını (displayName) değiştirir; fiziksel depolama yolu (`name`/`path`) sabit kalır — bu kasıtlıdır, yüklenmiş dosyaların yolu kırılmasın diye. Üst klasörü değiştirmez (taşıma desteklenmiyor).",
42
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, name: z.string().min(1) },
43
+ annotations: updates
44
+ }, handle(config, ({ project, environment, id, name }) => platform.tenant('PATCH', project, `/folders/${id}/rename`, { environment, body: { name } })));
45
+ server.registerTool('folder_delete', {
46
+ title: 'Klasör sil',
47
+ description: 'Klasörü siler.',
48
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
49
+ annotations: destructive
50
+ }, handle(config, ({ project, environment, id }) => platform.tenant('DELETE', project, `/folders/${id}`, { environment })));
51
+ }
@@ -0,0 +1,105 @@
1
+ import { guid, projectParam, environmentParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { normalizeComponentDefaults, componentDefaultsNote, blockSizeNote, heightTypeNote, invalidScreenEventNames, invalidWidgetTypeNodes, verticalGapWarnings, withNormalization } from '../knowledge/normalize.js';
3
+ import { fetchHost, layoutUpdateBody, schemaParam, eventsParam } from './screens.js';
4
+ import { applyCatalogLayoutRules } from './components.js';
5
+ import { PlatformApiError } from '../platform/errors.js';
6
+ import { handle } from '../platform/responses.js';
7
+ import { z } from 'zod';
8
+ const viewsParam = schemaParam.describe("Layout'un komponent ağacı. DİKKAT: layout'ta şema alanının adı 'schema' değil 'views'tir ve sayfa micro-op hattı (page_schema_patch) layout'a uygulanmaz. İçinde MUTLAKA bir { type: 'slot' } komponenti olmalı — sayfa içeriği YALNIZ slot'un içine render edilir, slot'suz layout'a bağlanan sayfalar boş görünür.");
9
+ function slotWarning(views) {
10
+ const hasSlot = (nodes) => Array.isArray(nodes) && nodes.some(node => Boolean(node) && typeof node === 'object' &&
11
+ (node.type === 'slot' || hasSlot(node.children)));
12
+ return hasSlot(views)
13
+ ? []
14
+ : ["LAY-002: layout'ta 'slot' tipli komponent YOK — Studio ve runtime, sayfa şemasını yalnız layout'taki slot'un içine yerleştirir (page.mappers: layout varsa render şeması layout views'üdür); bu layout'a bağlanan her sayfanın içeriği görünmez olur. views'e bir { type: 'slot' } komponenti ekleyin ve page_state.layoutSlots ile doğrulayın."];
15
+ }
16
+ export function registerLayoutTools(server, platform) {
17
+ const config = platform.config;
18
+ server.registerTool('layout_list', {
19
+ title: 'Layout listele',
20
+ description: "Uygulamanın layout'larını listeler; sayfalar layoutId ile bunlara bağlanır.",
21
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, searchTerm: z.string().optional(), ...pagingParams },
22
+ annotations: readOnly
23
+ }, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/layouts/${applicationId}`, { environment, query })));
24
+ server.registerTool('layout_get', {
25
+ title: 'Layout getir',
26
+ description: "Layout'u komponent ağacı (views), widget'ları, akışları ve olaylarıyla getirir.",
27
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
28
+ annotations: readOnly
29
+ }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('GET', project, `/layouts/${applicationId}/${id}`, { environment })));
30
+ server.registerTool('layout_create', {
31
+ title: 'Layout oluştur',
32
+ description: "Yeni layout oluşturur. Şablon için mevcut bir layout'u layout_get ile alın; slot kurgusunu page_state çıktısındaki layoutSlots ile doğrulayın.",
33
+ inputSchema: {
34
+ project: projectParam,
35
+ environment: environmentParam,
36
+ applicationId: guid,
37
+ name: z.string().min(1),
38
+ views: viewsParam.optional(),
39
+ events: eventsParam.optional(),
40
+ parameters: z.record(z.unknown()).optional()
41
+ },
42
+ annotations: writes
43
+ }, handle(config, async ({ project, environment, applicationId, name, views, events, parameters }) => {
44
+ const badEvents = invalidScreenEventNames(events, 'LAY-004');
45
+ if (badEvents.length > 0)
46
+ throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
47
+ const badWidgets = invalidWidgetTypeNodes(views, 'LAY-005');
48
+ if (badWidgets.length > 0)
49
+ throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
50
+ const fixed = normalizeComponentDefaults(views);
51
+ const catalog = (views ?? []).length > 0
52
+ ? await applyCatalogLayoutRules(platform, project, environment, applicationId, views)
53
+ : { clamped: [], heights: [] };
54
+ const gaps = verticalGapWarnings(views ?? [], 'LAY-003 (uyarı)');
55
+ const response = await platform.tenant('POST', project, `/layouts/${applicationId}`, {
56
+ environment,
57
+ body: { name, views: views ?? [], events: events ?? [], parameters: parameters ?? null }
58
+ });
59
+ const normalized = withNormalization(response, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(views)]);
60
+ if (gaps.length === 0 || !normalized || typeof normalized !== 'object')
61
+ return normalized;
62
+ return { ...normalized, layoutWarnings: gaps };
63
+ }));
64
+ server.registerTool('layout_update', {
65
+ title: 'Layout güncelle',
66
+ description: "Layout'u günceller. PUT replace olduğu için gateway kaydı önce okur, yalnız verdiğiniz alanları değiştirip tam gövdeyi geri yazar — views göndermezseniz mevcut ağaç korunur.",
67
+ inputSchema: {
68
+ project: projectParam,
69
+ environment: environmentParam,
70
+ applicationId: guid,
71
+ id: guid,
72
+ name: z.string().min(1).optional(),
73
+ views: viewsParam.optional(),
74
+ events: eventsParam.optional(),
75
+ parameters: z.record(z.unknown()).optional()
76
+ },
77
+ annotations: updates
78
+ }, handle(config, async ({ project, environment, applicationId, id, ...changes }) => {
79
+ const overrides = Object.fromEntries(Object.entries(changes).filter(([, value]) => value !== undefined));
80
+ const badEvents = invalidScreenEventNames(overrides.events, 'LAY-004');
81
+ if (badEvents.length > 0)
82
+ throw new PlatformApiError(`Geçersiz layout olayı (istek gönderilmedi): ${badEvents.join(' · ')}`, 400);
83
+ const badWidgets = invalidWidgetTypeNodes(overrides.views, 'LAY-005');
84
+ if (badWidgets.length > 0)
85
+ throw new PlatformApiError(`Geçersiz widget düğümü (istek gönderilmedi): ${badWidgets.join(' · ')}`, 400);
86
+ const current = await fetchHost(platform, 'layout', project, environment, applicationId, id);
87
+ const fixed = normalizeComponentDefaults(overrides.views);
88
+ const catalog = overrides.views === undefined
89
+ ? { clamped: [], heights: [] }
90
+ : await applyCatalogLayoutRules(platform, project, environment, applicationId, overrides.views);
91
+ const body = layoutUpdateBody({ ...current, applicationId }, overrides);
92
+ await platform.tenant('PUT', project, `/layouts/${applicationId}/${id}`, { environment, body });
93
+ const gaps = overrides.views === undefined ? [] : verticalGapWarnings(body.views, 'LAY-003 (uyarı)');
94
+ const summary = { id, changed: Object.keys(overrides), preserved: { viewNodes: body.views.length } };
95
+ if (gaps.length > 0)
96
+ summary.layoutWarnings = gaps;
97
+ return withNormalization(summary, [...componentDefaultsNote(fixed), ...blockSizeNote(catalog.clamped), ...heightTypeNote(catalog.heights), ...slotWarning(body.views)]);
98
+ }));
99
+ server.registerTool('layout_delete', {
100
+ title: 'Layout sil',
101
+ description: "Layout'u siler; bu layout'a bağlı sayfalar layoutsuz kalır. Önce dependency_graph module:'layout' ile kontrol edin.",
102
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
103
+ annotations: destructive
104
+ }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/layouts/${applicationId}/${id}`, { environment })));
105
+ }
@@ -0,0 +1,198 @@
1
+ import { guid, projectParam, environmentParam, definitionParam, pagingParams, readOnly, writes, updates, destructive } from './common.js';
2
+ import { normalizeMicroserviceDefinition, withNormalization } from '../knowledge/normalize.js';
3
+ import { evaluatePolicy, enforcePolicy, attachFindings } from '../knowledge/policy.js';
4
+ import { microserviceOutline, resolvePart, resolvePatchOperations } from '../knowledge/outlines.js';
5
+ import { policyRules } from '../knowledge/rules.js';
6
+ import { handle } from '../platform/responses.js';
7
+ import fastJsonPatch from 'fast-json-patch';
8
+ import { z } from 'zod';
9
+ const policyOverrideParam = z.boolean().optional()
10
+ .describe('true: politika block bulgularını warn\'a indirir (bilinçli istisna; yanıtta işaretlenir)');
11
+ const patchOperationsParam = z.array(z.record(z.unknown())).min(1)
12
+ .describe("RFC 6902 JSON Patch işlemleri; path'leri view:'outline'/'part' ile keşfedin. Dizi öğelerinde indeks yerine seçici kullanılabilir: '/sourceParameters/joins[alias=customers]/path' — seçici gövde platforma gitmeden indekse çevrilir, çözülemezse istek hiç gönderilmez.");
13
+ const executeInput = {
14
+ project: projectParam,
15
+ environment: environmentParam,
16
+ application: z.string().min(1).describe('Application slug\'ı'),
17
+ microservice: z.string().min(1).describe('Microservice slug\'ı'),
18
+ method: z.enum(['GET', 'POST', 'PUT', 'DELETE']).default('GET'),
19
+ routeParams: z.array(z.string()).optional(),
20
+ query: z.record(z.unknown()).optional(),
21
+ body: z.union([z.record(z.unknown()), z.array(z.record(z.unknown()))]).optional()
22
+ .describe('İstek gövdesi: tek nesne ya da nesne dizisi (bulk uçlar için)'),
23
+ headers: z.record(z.string()).optional().describe("İsteğe eklenecek header'lar. 'Authorization' verirseniz gateway KENDİ token'ını KOYMAZ — istek tamamen sizin kimliğinizle gider (middleware'li uçları üye token'ıyla test etmenin yolu budur)"),
24
+ anonymous: z.boolean().optional().describe("true: istek HİÇBİR Authorization header'ı olmadan gider — 'korumalı uç tokensız ne döner?' sorusunun cevabı budur (korumalı uçtan 401, anonim uçtan 200 beklenir). Gateway normalde her çağrıya kendi token'ını koyduğu için bu bayrak olmadan anonim erişim testi yapılamaz.")
25
+ };
26
+ function executePath(prefix, application, microservice, routeParams) {
27
+ const routeSuffix = routeParams && routeParams.length > 0
28
+ ? `/${routeParams.map(encodeURIComponent).join('/')}`
29
+ : '';
30
+ return `/${prefix}/${application}/${microservice}${routeSuffix}`;
31
+ }
32
+ /** Slug application_update ile değişebildiği için önbellek kısa ömürlüdür (yalnız aynı turdaki tekrar okumaları ucuzlatır). */
33
+ const APPLICATION_SLUG_TTL_MS = 60_000;
34
+ const applicationSlugCache = new Map();
35
+ async function applicationSlug(platform, applicationId) {
36
+ const cached = applicationSlugCache.get(applicationId);
37
+ if (cached && cached.expiresAt > Date.now())
38
+ return cached.slug;
39
+ try {
40
+ const payload = await platform.org('GET', `/applications/${applicationId}`);
41
+ const application = (payload?.data ?? payload);
42
+ const slug = typeof application.slug === 'string' && application.slug !== '' ? application.slug : undefined;
43
+ if (slug)
44
+ applicationSlugCache.set(applicationId, { slug, expiresAt: Date.now() + APPLICATION_SLUG_TTL_MS });
45
+ return slug;
46
+ }
47
+ catch {
48
+ return undefined;
49
+ }
50
+ }
51
+ /**
52
+ * Çalıştırma rotası `[Route("{project}")] + m/{application}/{microservice}` ve arama
53
+ * `m.Slug == microserviceSlug` ile yapılır (MicroservicesController:91,
54
+ * MicroserviceRepository.FindBySlugAsync) — yani yolu kuran alan `url` DEĞİL **slug**'dır ve
55
+ * proje öneki zorunludur. Ham curl ile anonim test yapmak isteyen bu yolu tahmin etmek zorunda
56
+ * kalıyordu (crm koşumu B-8): artık kayıt okunduğunda hazır geliyor.
57
+ */
58
+ async function resolveRuntimeUrl(platform, project, environment, applicationId, definition) {
59
+ const slug = String(definition.slug ?? '');
60
+ const method = String(definition.httpMethod ?? 'get').toUpperCase();
61
+ const resolvedProject = platform.resolveProject(project);
62
+ const resolvedEnvironment = platform.resolveEnvironment(environment);
63
+ const application = await applicationSlug(platform, applicationId);
64
+ const path = `/${resolvedProject}/m/${application ?? '<application slug>'}/${slug}`;
65
+ return {
66
+ method,
67
+ path,
68
+ absolute: `${platform.config.tenantApiBaseUrl}${path}`,
69
+ headers: {
70
+ Host: `${platform.config.organization}.${platform.config.hostSuffix}`,
71
+ 'X-Environment': resolvedEnvironment
72
+ },
73
+ note: `Yolu SLUG kurar, 'url' alanı ('${String(definition.url ?? '')}') rotaya girmez. Route parametreleri slug'ın ardına eklenir. Anonim erişim testi için microservice_execute'u anonymous: true ile çağırın; korumalı uçlarda 401, anonim uçlarda 200 beklenir.`
74
+ };
75
+ }
76
+ export function registerMicroserviceTools(server, platform) {
77
+ const config = platform.config;
78
+ server.registerTool('microservice_list', {
79
+ title: 'Microservice listele',
80
+ description: 'Application altındaki microservice\'leri listeler.',
81
+ inputSchema: {
82
+ project: projectParam,
83
+ environment: environmentParam,
84
+ applicationId: guid,
85
+ searchTerm: z.string().optional(),
86
+ httpMethod: z.string().optional(),
87
+ entityId: guid.optional(),
88
+ queryId: guid.optional(),
89
+ type: z.string().optional(),
90
+ isDefault: z.boolean().optional(),
91
+ ...pagingParams
92
+ },
93
+ annotations: readOnly
94
+ }, handle(config, ({ project, environment, applicationId, ...query }) => platform.tenant('GET', project, `/microservices/${applicationId}`, { environment, query })));
95
+ server.registerTool('microservice_get', {
96
+ title: 'Microservice getir',
97
+ description: "Microservice tanımını getirir. Büyük tanımda önce view:'outline' (kaynak/alan özeti), sonra view:'part' ile tek parça çekin.",
98
+ inputSchema: {
99
+ project: projectParam,
100
+ environment: environmentParam,
101
+ applicationId: guid,
102
+ id: guid,
103
+ view: z.enum(['full', 'outline', 'part']).optional().describe('varsayılan full'),
104
+ part: z.string().optional().describe("view:'part' için yol, ör. sourceParameters.joins veya validations[0]")
105
+ },
106
+ annotations: readOnly
107
+ }, handle(config, async ({ project, environment, applicationId, id, view, part }) => {
108
+ const payload = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
109
+ const definition = (payload?.data ?? payload);
110
+ if (view === 'part') {
111
+ if (!part)
112
+ throw new Error("view:'part' için 'part' parametresi zorunlu.");
113
+ return { part, value: resolvePart(definition, part) };
114
+ }
115
+ const resolvedUrl = await resolveRuntimeUrl(platform, project, environment, applicationId, definition);
116
+ if (view === 'outline')
117
+ return { ...microserviceOutline(definition), resolvedUrl };
118
+ return { ...payload, resolvedUrl };
119
+ }));
120
+ server.registerTool('microservice_create', {
121
+ title: 'Microservice oluştur',
122
+ description: "Yeni microservice tanımlar (definition = CreateMicroserviceCommand). İlk kullanımdan önce platform_guide('microservice') okuyun: kaynak seçimi (table > query) ve selectFields disiplini. Auth/oturum gerektiren uçlarda definition.middlewareIds ile middleware bağlayın; kimlik kapsaması filtrede source:'expression' + value:'user.id' ile yapılır (platform_guide('middleware')). Gövde politika kontrolünden geçer.",
123
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
124
+ annotations: writes
125
+ }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
126
+ const normalized = normalizeMicroserviceDefinition(definition);
127
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'create' }, policyOverride));
128
+ const payload = await platform.tenant('POST', project, `/microservices/${applicationId}`, { environment, body: definition });
129
+ return attachFindings(withNormalization(payload, normalized), findings);
130
+ }));
131
+ server.registerTool('microservice_update', {
132
+ title: 'Microservice güncelle',
133
+ description: 'Microservice tanımını günceller (tam gövde). Hedefli değişiklik için microservice_patch tercih edin. Gövde politika kontrolünden geçer.',
134
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, definition: definitionParam, policyOverride: policyOverrideParam },
135
+ annotations: updates
136
+ }, handle(config, async ({ project, environment, applicationId, id, definition, policyOverride }) => {
137
+ const normalized = normalizeMicroserviceDefinition(definition);
138
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'update' }, policyOverride));
139
+ const payload = await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: definition });
140
+ return attachFindings(withNormalization(payload, normalized), findings);
141
+ }));
142
+ server.registerTool('microservice_patch', {
143
+ title: 'Microservice\'e patch uygula',
144
+ description: 'Tam gövdeyi bağlama almadan hedefli değişiklik: gateway mevcut tanımı çeker, JSON Patch uygular, politika kontrolünden geçirir, geri yazar. Yanıt outline özetidir.',
145
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid, operations: patchOperationsParam, policyOverride: policyOverrideParam },
146
+ annotations: updates
147
+ }, handle(config, async ({ project, environment, applicationId, id, operations, policyOverride }) => {
148
+ const current = await platform.tenant('GET', project, `/microservices/${applicationId}/${id}`, { environment });
149
+ const definition = structuredClone((current?.data ?? current));
150
+ const resolved = resolvePatchOperations(definition, operations);
151
+ const patched = fastJsonPatch.applyPatch(definition, resolved.operations, true, false).newDocument;
152
+ const normalized = normalizeMicroserviceDefinition(patched);
153
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', patched, { operation: 'update' }, policyOverride));
154
+ await platform.tenant('PUT', project, `/microservices/${applicationId}/${id}`, { environment, body: patched });
155
+ return attachFindings(withNormalization({ applied: operations.length, outline: microserviceOutline(patched) }, [...normalized, ...resolved.notes.map(note => `patch yolu seçiciden indekse çözüldü: ${note}`)]), findings);
156
+ }));
157
+ server.registerTool('microservice_delete', {
158
+ title: 'Microservice sil',
159
+ description: 'Microservice tanımını siler.',
160
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, id: guid },
161
+ annotations: destructive
162
+ }, handle(config, ({ project, environment, applicationId, id }) => platform.tenant('DELETE', project, `/microservices/${applicationId}/${id}`, { environment })));
163
+ server.registerTool('microservice_clone', {
164
+ title: 'Microservice klonla',
165
+ description: 'Microservice\'in kopyasını oluşturur.',
166
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid },
167
+ annotations: writes
168
+ }, handle(config, ({ project, environment, id }) => platform.tenant('POST', project, `/microservices/${id}/clone`, { environment })));
169
+ server.registerTool('microservice_execute', {
170
+ title: 'Microservice çalıştır',
171
+ description: 'Kayıtlı microservice\'i gerçek runtime yolundan çalıştırır (POST /{project}/m/{application}/{microservice SLUG\'ı}). Dönen httpStatus microservice\'in kendi sonucudur (hata da geçerli çıktıdır); beklenmedik sonuçta microservice_debug ile adım adım inceleyin. Anonim erişim testi için anonymous: true.',
172
+ inputSchema: executeInput,
173
+ annotations: writes
174
+ }, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('m', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
175
+ server.registerTool('microservice_debug', {
176
+ title: 'Microservice debug',
177
+ description: 'Microservice\'i debug modunda çalıştırır; yanıtta adım adım executionItems ağacı döner (her adımın girdi/çıktısı).',
178
+ inputSchema: executeInput,
179
+ annotations: writes
180
+ }, handle(config, ({ project, environment, application, microservice, method, routeParams, query, body, headers, anonymous }) => platform.tenantRaw(method, project, executePath('debug', application, microservice, routeParams), { environment, query, body, headers, anonymous })));
181
+ server.registerTool('microservice_preview', {
182
+ title: 'Microservice önizle',
183
+ description: 'Kaydedilmemiş microservice tanımını çalıştırır; kaydetmeden önce preview ile doğrulayın. definition = PreviewMicroserviceCommand gövdesi (previewParameters dahil).',
184
+ inputSchema: { project: projectParam, environment: environmentParam, applicationId: guid, definition: definitionParam, policyOverride: policyOverrideParam },
185
+ annotations: writes
186
+ }, handle(config, async ({ project, environment, applicationId, definition, policyOverride }) => {
187
+ const normalized = normalizeMicroserviceDefinition(definition);
188
+ const findings = enforcePolicy(evaluatePolicy(config, policyRules, 'microservice', definition, { operation: 'preview' }, policyOverride));
189
+ const payload = await platform.tenantRaw('POST', project, `/microservices/${applicationId}/preview`, { environment, body: definition });
190
+ return attachFindings(withNormalization(payload, normalized), findings);
191
+ }));
192
+ server.registerTool('microservice_job_history', {
193
+ title: 'Job geçmişi',
194
+ description: 'Zamanlanmış (job tipli) microservice\'in çalışma geçmişini listeler.',
195
+ inputSchema: { project: projectParam, environment: environmentParam, id: guid, ...pagingParams },
196
+ annotations: readOnly
197
+ }, handle(config, ({ project, environment, id, ...query }) => platform.tenant('GET', project, `/microservices/${id}/job-history`, { environment, query })));
198
+ }