@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.
- package/.env.example +46 -0
- package/README.md +260 -0
- package/dist/config.js +128 -0
- package/dist/index.js +88 -0
- package/dist/knowledge/componentCatalog.js +438 -0
- package/dist/knowledge/componentEvents.js +164 -0
- package/dist/knowledge/coverage.js +60 -0
- package/dist/knowledge/normalize.js +1309 -0
- package/dist/knowledge/outlines.js +167 -0
- package/dist/knowledge/policy.js +44 -0
- package/dist/knowledge/reservedTables.js +75 -0
- package/dist/knowledge/rules.js +1725 -0
- package/dist/observability/trace.js +337 -0
- package/dist/platform/errors.js +132 -0
- package/dist/platform/http.js +169 -0
- package/dist/platform/responses.js +94 -0
- package/dist/platform/token.js +79 -0
- package/dist/resources/examples/entity-pair-m2m.json +105 -0
- package/dist/resources/examples/flow-list-to-form.json +65 -0
- package/dist/resources/examples/flow-load-table.json +178 -0
- package/dist/resources/examples/microservice-table-list.json +49 -0
- package/dist/resources/examples/middleware-authenticate.json +194 -0
- package/dist/resources/examples/page-detail-profile.json +95 -0
- package/dist/resources/examples/page-dynamic-form.json +221 -0
- package/dist/resources/examples/page-form-upsert.json +279 -0
- package/dist/resources/examples/page-html-embed.json +106 -0
- package/dist/resources/examples/page-sign-in.json +194 -0
- package/dist/resources/examples/workflow-basic.json +94 -0
- package/dist/resources/guides/component.md +108 -0
- package/dist/resources/guides/entity.md +348 -0
- package/dist/resources/guides/expressions.md +139 -0
- package/dist/resources/guides/getting-started.md +109 -0
- package/dist/resources/guides/html.md +106 -0
- package/dist/resources/guides/microservice.md +313 -0
- package/dist/resources/guides/middleware.md +271 -0
- package/dist/resources/guides/modify-existing.md +35 -0
- package/dist/resources/guides/page.md +1574 -0
- package/dist/resources/guides/view.md +89 -0
- package/dist/resources/guides/workflow.md +359 -0
- package/dist/resources/knowledge.js +44 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +54 -0
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +133 -0
- package/dist/resources/schemas/CreateEntityCommand.json +387 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +528 -0
- package/dist/resources/schemas/CreateQueryCommand.json +43 -0
- package/dist/resources/schemas/CreateViewCommand.json +158 -0
- package/dist/resources/schemas/CreateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas/Filter.json +124 -0
- package/dist/resources/schemas/GetRowsQuery.json +122 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +565 -0
- package/dist/resources/schemas/PreviewQueryCommand.json +74 -0
- package/dist/resources/schemas/PreviewWorkflowCommand.json +981 -0
- package/dist/resources/schemas/StepDto.json +900 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +58 -0
- package/dist/resources/schemas/UpdateEntityCommand.json +679 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +512 -0
- package/dist/resources/schemas/UpdateQueryCommand.json +86 -0
- package/dist/resources/schemas/UpdateViewCommand.json +153 -0
- package/dist/resources/schemas/UpdateWorkflowCommand.json +1719 -0
- package/dist/resources/schemas.js +40 -0
- package/dist/server.js +63 -0
- package/dist/tools/access.js +27 -0
- package/dist/tools/applications.js +59 -0
- package/dist/tools/automations.js +103 -0
- package/dist/tools/common.js +18 -0
- package/dist/tools/components.js +280 -0
- package/dist/tools/datasources.js +82 -0
- package/dist/tools/dependencies.js +17 -0
- package/dist/tools/deployments.js +17 -0
- package/dist/tools/dtos.js +54 -0
- package/dist/tools/entities.js +166 -0
- package/dist/tools/files.js +138 -0
- package/dist/tools/flows.js +226 -0
- package/dist/tools/folders.js +51 -0
- package/dist/tools/layouts.js +105 -0
- package/dist/tools/microservices.js +198 -0
- package/dist/tools/organizations.js +65 -0
- package/dist/tools/pages.js +597 -0
- package/dist/tools/platformInfo.js +44 -0
- package/dist/tools/projects.js +152 -0
- package/dist/tools/queries.js +48 -0
- package/dist/tools/rows.js +115 -0
- package/dist/tools/screens.js +97 -0
- package/dist/tools/templates.js +65 -0
- package/dist/tools/variables.js +94 -0
- package/dist/tools/views.js +58 -0
- package/dist/tools/workflows.js +196 -0
- package/package.json +49 -0
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { logError, logStep } from '../observability/trace.js';
|
|
2
|
+
import { PlatformApiError } from './errors.js';
|
|
3
|
+
export function prune(value) {
|
|
4
|
+
if (Array.isArray(value))
|
|
5
|
+
return value.map(prune);
|
|
6
|
+
if (value && typeof value === 'object') {
|
|
7
|
+
const result = {};
|
|
8
|
+
for (const [key, entry] of Object.entries(value)) {
|
|
9
|
+
if (entry === null || entry === undefined)
|
|
10
|
+
continue;
|
|
11
|
+
result[key] = prune(entry);
|
|
12
|
+
}
|
|
13
|
+
return result;
|
|
14
|
+
}
|
|
15
|
+
return value;
|
|
16
|
+
}
|
|
17
|
+
function findLongestArray(value, best) {
|
|
18
|
+
if (Array.isArray(value)) {
|
|
19
|
+
if (!best.array || value.length > best.array.length)
|
|
20
|
+
best.array = value;
|
|
21
|
+
for (const entry of value)
|
|
22
|
+
findLongestArray(entry, best);
|
|
23
|
+
return;
|
|
24
|
+
}
|
|
25
|
+
if (value && typeof value === 'object') {
|
|
26
|
+
for (const entry of Object.values(value))
|
|
27
|
+
findLongestArray(entry, best);
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
export function shrink(payload, maxBytes) {
|
|
31
|
+
let current = payload;
|
|
32
|
+
let truncated = false;
|
|
33
|
+
for (let attempt = 0; attempt < 32; attempt += 1) {
|
|
34
|
+
const serialized = JSON.stringify(current) ?? '';
|
|
35
|
+
if (serialized.length <= maxBytes)
|
|
36
|
+
return { payload: current, truncated };
|
|
37
|
+
const best = { array: null };
|
|
38
|
+
findLongestArray(current, best);
|
|
39
|
+
if (!best.array || best.array.length <= 1) {
|
|
40
|
+
return {
|
|
41
|
+
payload: {
|
|
42
|
+
truncated: true,
|
|
43
|
+
note: `Yanıt ${maxBytes} bayt sınırını aşıyor ve liste kırpmasıyla küçültülemedi; daha dar bir sorgu (fields/sayfalama) kullanın.`,
|
|
44
|
+
preview: serialized.slice(0, maxBytes)
|
|
45
|
+
},
|
|
46
|
+
truncated: true
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
best.array.length = Math.max(1, Math.floor(best.array.length / 2));
|
|
50
|
+
truncated = true;
|
|
51
|
+
}
|
|
52
|
+
return { payload: current, truncated };
|
|
53
|
+
}
|
|
54
|
+
function asStructured(payload) {
|
|
55
|
+
if (payload && typeof payload === 'object' && !Array.isArray(payload))
|
|
56
|
+
return payload;
|
|
57
|
+
return { data: payload };
|
|
58
|
+
}
|
|
59
|
+
export function toolResult(payload, config) {
|
|
60
|
+
const { payload: bounded, truncated } = shrink(prune(payload), config.maxResponseBytes);
|
|
61
|
+
const structured = asStructured(bounded);
|
|
62
|
+
if (truncated) {
|
|
63
|
+
logStep('kırpma ✂', `yanıt ${config.maxResponseBytes} bayt sınırını aştı — en uzun liste yarıya indirilerek küçültüldü (MAX_RESPONSE_BYTES)`);
|
|
64
|
+
}
|
|
65
|
+
const enriched = truncated
|
|
66
|
+
? { ...structured, truncated: true, truncationNote: 'Liste, yanıt boyutu sınırı nedeniyle kırpıldı; sayfalama ya da daha dar filtre kullanın.' }
|
|
67
|
+
: structured;
|
|
68
|
+
return {
|
|
69
|
+
content: [{ type: 'text', text: JSON.stringify(enriched) }],
|
|
70
|
+
structuredContent: enriched
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
export function toolError(error) {
|
|
74
|
+
logError(error);
|
|
75
|
+
const message = error instanceof PlatformApiError
|
|
76
|
+
? error.message
|
|
77
|
+
: error instanceof Error
|
|
78
|
+
? `Beklenmeyen gateway hatası: ${error.message}`
|
|
79
|
+
: `Beklenmeyen gateway hatası: ${String(error)}`;
|
|
80
|
+
return {
|
|
81
|
+
content: [{ type: 'text', text: message }],
|
|
82
|
+
isError: true
|
|
83
|
+
};
|
|
84
|
+
}
|
|
85
|
+
export function handle(config, run) {
|
|
86
|
+
return async (args) => {
|
|
87
|
+
try {
|
|
88
|
+
return toolResult(await run(args), config);
|
|
89
|
+
}
|
|
90
|
+
catch (error) {
|
|
91
|
+
return toolError(error);
|
|
92
|
+
}
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { PlatformApiError, mapNetworkError, mapPlatformError } from './errors.js';
|
|
2
|
+
import { logStep } from '../observability/trace.js';
|
|
3
|
+
import { request } from 'undici';
|
|
4
|
+
function readExpiry(token) {
|
|
5
|
+
const payloadSegment = token.split('.')[1];
|
|
6
|
+
if (!payloadSegment)
|
|
7
|
+
return Date.now() + 5 * 60 * 1000;
|
|
8
|
+
try {
|
|
9
|
+
const payload = JSON.parse(Buffer.from(payloadSegment, 'base64url').toString('utf8'));
|
|
10
|
+
if (typeof payload.exp === 'number')
|
|
11
|
+
return payload.exp * 1000 - 60 * 1000;
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return Date.now() + 5 * 60 * 1000;
|
|
15
|
+
}
|
|
16
|
+
return Date.now() + 5 * 60 * 1000;
|
|
17
|
+
}
|
|
18
|
+
export class TokenProvider {
|
|
19
|
+
config;
|
|
20
|
+
cached;
|
|
21
|
+
constructor(config) {
|
|
22
|
+
this.config = config;
|
|
23
|
+
}
|
|
24
|
+
async getToken() {
|
|
25
|
+
if (this.config.token)
|
|
26
|
+
return this.config.token;
|
|
27
|
+
if (this.cached && this.cached.expiresAt > Date.now())
|
|
28
|
+
return this.cached.value;
|
|
29
|
+
const context = {
|
|
30
|
+
api: 'org',
|
|
31
|
+
method: 'POST',
|
|
32
|
+
path: '/connect/token',
|
|
33
|
+
baseUrl: this.config.orgApiBaseUrl,
|
|
34
|
+
authMode: this.config.authMode
|
|
35
|
+
};
|
|
36
|
+
const form = new URLSearchParams({
|
|
37
|
+
Username: this.config.username ?? '',
|
|
38
|
+
Password: this.config.password ?? ''
|
|
39
|
+
});
|
|
40
|
+
let statusCode;
|
|
41
|
+
let payload;
|
|
42
|
+
logStep('kimlik →', `önbellekte geçerli token yok — POST ${this.config.orgApiBaseUrl}/connect/token · kullanıcı ${this.config.username ?? '(yok)'}`);
|
|
43
|
+
try {
|
|
44
|
+
const response = await request(`${this.config.orgApiBaseUrl}/connect/token`, {
|
|
45
|
+
method: 'POST',
|
|
46
|
+
headers: {
|
|
47
|
+
host: `${this.config.organization}.${this.config.hostSuffix}`,
|
|
48
|
+
'content-type': 'application/x-www-form-urlencoded',
|
|
49
|
+
accept: 'application/json'
|
|
50
|
+
},
|
|
51
|
+
body: form.toString(),
|
|
52
|
+
signal: AbortSignal.timeout(this.config.requestTimeoutMs)
|
|
53
|
+
});
|
|
54
|
+
statusCode = response.statusCode;
|
|
55
|
+
const text = await response.body.text();
|
|
56
|
+
try {
|
|
57
|
+
payload = text ? JSON.parse(text) : undefined;
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
payload = text;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
catch (error) {
|
|
64
|
+
throw mapNetworkError(error, context);
|
|
65
|
+
}
|
|
66
|
+
if (statusCode >= 400)
|
|
67
|
+
throw mapPlatformError(statusCode, payload, context);
|
|
68
|
+
const accessToken = payload && typeof payload === 'object' ? payload.access_token : undefined;
|
|
69
|
+
if (typeof accessToken !== 'string' || accessToken.length === 0) {
|
|
70
|
+
throw new PlatformApiError('Token yanıtı beklenen biçimde değil: access_token alanı yok.');
|
|
71
|
+
}
|
|
72
|
+
this.cached = { value: accessToken, expiresAt: readExpiry(accessToken) };
|
|
73
|
+
logStep('kimlik ←', `HTTP ${statusCode} · token alındı, önbellekte ${Math.max(0, Math.round((this.cached.expiresAt - Date.now()) / 1000))} sn geçerli`);
|
|
74
|
+
return accessToken;
|
|
75
|
+
}
|
|
76
|
+
invalidate() {
|
|
77
|
+
this.cached = undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "hr projesinde doğrulanmış şekillerle sentetik örnek. Kurallar: dbType BÜYÜK HARF + commonType eşlemesi zorunlu; ondalık tip yok (para INT8 kuruş); ilişki alanı dbType RELATION + commonType relation, relation alt nesnesini platform doldurur; applicationIds create'te platformca bağlanmaz — gateway otomatik patch'ler.",
|
|
5
|
+
"usage": "İki entity'yi önce ilişkisiz oluşturun; id'ler belli olunca product'a m2m alanını entity_patch ile ekleyin."
|
|
6
|
+
},
|
|
7
|
+
"step1_tagEntity": {
|
|
8
|
+
"name": "Tag",
|
|
9
|
+
"slug": "tags",
|
|
10
|
+
"description": "Etiketler",
|
|
11
|
+
"isAuditTrailActive": false,
|
|
12
|
+
"applicationIds": [
|
|
13
|
+
"<application id — PLT-004: gateway create sonrası bağlamayı otomatik patch'ler>"
|
|
14
|
+
],
|
|
15
|
+
"fields": [
|
|
16
|
+
{
|
|
17
|
+
"name": "Id",
|
|
18
|
+
"slug": "id",
|
|
19
|
+
"dbType": "UUID",
|
|
20
|
+
"commonType": "guid",
|
|
21
|
+
"relationType": "none",
|
|
22
|
+
"nullable": false,
|
|
23
|
+
"primaryKey": true,
|
|
24
|
+
"unique": true,
|
|
25
|
+
"identity": true,
|
|
26
|
+
"defaultValueType": "none",
|
|
27
|
+
"properties": {
|
|
28
|
+
"generationType": 0
|
|
29
|
+
}
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
"name": "Name",
|
|
33
|
+
"slug": "name",
|
|
34
|
+
"dbType": "VARCHAR",
|
|
35
|
+
"commonType": "text",
|
|
36
|
+
"relationType": "none",
|
|
37
|
+
"nullable": false,
|
|
38
|
+
"unique": true,
|
|
39
|
+
"display": true
|
|
40
|
+
}
|
|
41
|
+
]
|
|
42
|
+
},
|
|
43
|
+
"step2_productEntity": {
|
|
44
|
+
"name": "Product",
|
|
45
|
+
"slug": "products",
|
|
46
|
+
"description": "Ürünler",
|
|
47
|
+
"isAuditTrailActive": false,
|
|
48
|
+
"applicationIds": [
|
|
49
|
+
"<application id>"
|
|
50
|
+
],
|
|
51
|
+
"fields": [
|
|
52
|
+
{
|
|
53
|
+
"name": "Id",
|
|
54
|
+
"slug": "id",
|
|
55
|
+
"dbType": "UUID",
|
|
56
|
+
"commonType": "guid",
|
|
57
|
+
"relationType": "none",
|
|
58
|
+
"nullable": false,
|
|
59
|
+
"primaryKey": true,
|
|
60
|
+
"unique": true,
|
|
61
|
+
"identity": true,
|
|
62
|
+
"defaultValueType": "none",
|
|
63
|
+
"properties": {
|
|
64
|
+
"generationType": 0
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{
|
|
68
|
+
"name": "Name",
|
|
69
|
+
"slug": "name",
|
|
70
|
+
"dbType": "VARCHAR",
|
|
71
|
+
"commonType": "text",
|
|
72
|
+
"relationType": "none",
|
|
73
|
+
"nullable": false,
|
|
74
|
+
"display": true
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
"name": "Price Minor",
|
|
78
|
+
"slug": "price_minor",
|
|
79
|
+
"dbType": "INT8",
|
|
80
|
+
"commonType": "number",
|
|
81
|
+
"relationType": "none",
|
|
82
|
+
"nullable": false,
|
|
83
|
+
"description": "Fiyat, kuruş cinsinden (ondalık tip desteklenmez)"
|
|
84
|
+
}
|
|
85
|
+
]
|
|
86
|
+
},
|
|
87
|
+
"step3_addManyToManyField_via_entity_patch": {
|
|
88
|
+
"operations": [
|
|
89
|
+
{
|
|
90
|
+
"op": "add",
|
|
91
|
+
"path": "/fields/-",
|
|
92
|
+
"value": {
|
|
93
|
+
"name": "Tags",
|
|
94
|
+
"slug": "tags",
|
|
95
|
+
"dbType": "RELATION",
|
|
96
|
+
"commonType": "relation",
|
|
97
|
+
"relationType": "many-to-many",
|
|
98
|
+
"relationId": "<tag entity id>",
|
|
99
|
+
"nullable": true
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
],
|
|
103
|
+
"note": "relation alt nesnesini (primaryKeyId, displayFieldId...) platform kendisi doldurur — elle verme."
|
|
104
|
+
}
|
|
105
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": false,
|
|
4
|
+
"note": "Liste sayfasından kayıt sayfasına geçiş: satıra çift tıkla → edit (?id=), 'Yeni' butonu → create (parametresiz). Tek yeni kavram redirect adımıdır; query alanı SABİT DEĞİL İFADEDİR.",
|
|
5
|
+
"harvestedFrom": "qm projesi '[SY] Departmanlar' sayfası (table-doubleClick + button-click akışları), 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §2",
|
|
6
|
+
"pairsWith": "page-form-upsert (geçilen sayfa), flow-load-table (listenin kendisini dolduran akış)"
|
|
7
|
+
},
|
|
8
|
+
"_rules": {
|
|
9
|
+
"queryIsExpression": "redirect.args.query bir İFADEDİR, sabit string değil: template literal (`id=${…}`) ve koşullu ifade yazılabilir. Sayfalar arası parametre geçmenin yolu budur; hedef sayfa Page.queryParams.<ad> ile okur. variable_create gerekmez.",
|
|
10
|
+
"selfAndNames": "İfade içinde `self` tetikleyen komponenttir (self.selectedRow.id). Komponente adıyla da erişilir (table?.selectedRow?.id) — addaki '-' silinir. Sözlük: platform_guide('expressions') §2.",
|
|
11
|
+
"pageIdAndPath": "redirect args'ında pageId (bağlayıcı referans) ve path (slug) birlikte verilir; sabit URL yazma — slug değişince pageId referansı ayakta kalır.",
|
|
12
|
+
"eventsAreSeparate": "Bu örnek `table` komponentini varsayar: olaylar AYRI akışlara bağlanır (mount = yükle · pagination · filter · sort · doubleClick = kaydı aç). `datatable` seçersen tek `mount` akışı yeterlidir — o komponent süzme/sıralama/sayfalamayı kendi içinde yapıp prop'ları yazdıktan sonra mount'u yeniden tetikler (Kural 1e). Hangi olayların var olduğunu component_get ile doğrulayın; olay adları `on` öneki ALMAZ (button → click).",
|
|
13
|
+
"newButton": "'Yeni' butonu ayrı bir komponenttir ve akışı parametresiz redirect'tir; hedef sayfa parametre görmediği için create kipine düşer."
|
|
14
|
+
},
|
|
15
|
+
"openRecordFlowCreateArgs": {
|
|
16
|
+
"host": "page",
|
|
17
|
+
"hostId": "<liste sayfası id>",
|
|
18
|
+
"name": "records_table - doubleClick",
|
|
19
|
+
"slug": "records-table-doubleclick",
|
|
20
|
+
"steps": [
|
|
21
|
+
{
|
|
22
|
+
"stepId": "start", "name": "start", "type": "start", "kind": "normal", "args": {},
|
|
23
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": "go_record" }]
|
|
24
|
+
},
|
|
25
|
+
{
|
|
26
|
+
"stepId": "go_record", "name": "go_record", "type": "redirect", "kind": "normal",
|
|
27
|
+
"args": {
|
|
28
|
+
"pageId": "<form sayfası id>",
|
|
29
|
+
"path": "departman",
|
|
30
|
+
"query": "`id=${self.selectedRow.id}`"
|
|
31
|
+
},
|
|
32
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
|
|
33
|
+
}
|
|
34
|
+
]
|
|
35
|
+
},
|
|
36
|
+
"newRecordFlowCreateArgs": {
|
|
37
|
+
"host": "page",
|
|
38
|
+
"hostId": "<liste sayfası id>",
|
|
39
|
+
"name": "btn_new - click",
|
|
40
|
+
"slug": "btn-new-click",
|
|
41
|
+
"steps": [
|
|
42
|
+
{
|
|
43
|
+
"stepId": "start", "name": "start", "type": "start", "kind": "normal", "args": {},
|
|
44
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": "go_new" }]
|
|
45
|
+
},
|
|
46
|
+
{
|
|
47
|
+
"stepId": "go_new", "name": "go_new", "type": "redirect", "kind": "normal",
|
|
48
|
+
"args": {
|
|
49
|
+
"pageId": "<form sayfası id>",
|
|
50
|
+
"path": "departman",
|
|
51
|
+
"query": "records_table?.selectedRow?.id ? `parent=${records_table.selectedRow.id}` : ''"
|
|
52
|
+
},
|
|
53
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
|
|
54
|
+
}
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
"schemaPatchOps": [
|
|
58
|
+
{ "op": "set_event", "target": "records_table", "event": "mount", "eventValue": "<liste yükleme flow id>" },
|
|
59
|
+
{ "op": "set_event", "target": "records_table", "event": "doubleClick", "eventValue": "<openRecord flow id>" },
|
|
60
|
+
{ "op": "set_event", "target": "records_table", "event": "pagination", "eventValue": "<liste yükleme flow id>" },
|
|
61
|
+
{ "op": "set_event", "target": "records_table", "event": "filter", "eventValue": "<liste yükleme flow id>" },
|
|
62
|
+
{ "op": "set_event", "target": "records_table", "event": "sort", "eventValue": "<liste yükleme flow id>" },
|
|
63
|
+
{ "op": "set_event", "target": "btn_new", "event": "click", "eventValue": "<newRecord flow id>" }
|
|
64
|
+
]
|
|
65
|
+
}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "Komponentin mount olayına bağlanan flow tabloya veri yükler: yükleniyor bayrağı → microservice → satır eşleme + toplam kayıt. Bu dosyanın 2026-08-16 öncesi sürümü ÜÇ HATA taşıyordu ve ürettiği akışlar ne Studio'da çiziliyor ne runtime'da çalışıyordu; aşağıdaki şekil UI kodu okunarak düzeltildi (bkz. _rules).",
|
|
5
|
+
"harvestedFrom": "kod doğrulaması: NgApps.UI packages/ng-apps-flow + apps/studio/src/features/event-actions (2026-08-16)"
|
|
6
|
+
},
|
|
7
|
+
"_rules": {
|
|
8
|
+
"branchName": "Dal ADI motorun anahtarıdır, etiket değil. Runtime.svelte:329 branch.id'yi branch.name'den üretir; start.ts/setter.ts/code.ts `engine.next('always')` çağırır. Ad 'always' değilse flow-engine.ts:79 akışı SESSİZCE durdurur ve Studio paneli (EventActionsStart.svelte:47 `sourceBranch === 'always'`) Start kartından sonrasını hiç çizmez. İstisnalar: condition = rule id'leri, confirm = yes|no|always, microservice = always (eski şemada success de kabul edilir).",
|
|
9
|
+
"nextStepId": "branches[].nextStepId bir GUID DEĞİL, hedef adımın stepId STRING'idir (StepManagementSchema.ToDomain -> allSteps.TryGetValue). Eşleşmeyeni backend sessizce null'a çevirir. Son adımın dalı nextStepId: null ile biter; ayrı bir 'end' adımı şart değildir (flow-engine.ts:79-92 hedef bulamayınca akışı completed() ile kapatır, 'end' tipinin kayıtlı bir handler'ı yoktur).",
|
|
10
|
+
"iterateeType": "setter iteratees[].type yalnız 'change' (tek prop'a değer yaz) veya 'fill' (diziyi mapping ile eşle) olabilir (setter.ts:70). Backend bu alanı serbest string sakladığı için 'set' gibi bir değer 200 döner ama adım hiçbir şey yapmaz.",
|
|
11
|
+
"storageRef": "Microservice sonucu `machine.setStorage(task.name, …)` ile yazılır — anahtar adımın ADIdır, stepId DEĞİL. isRaw=false iken storage { data, headers, error } sarmalıdır, bu yüzden ref '<adım adı>.data' olur. Adım adında nokta kullanmayın (getStorage dot-prop ile okur). Kod ifadesinden okuma biçimi: Steps[\"<adım adı>\"].data",
|
|
12
|
+
"storageSource": "change + storage dalında anahtar ÖNCE mapping.source'tan okunur, doluysa value.ref'e hiç bakılmaz (setter.ts:161). Studio'nun ürettiği şekil budur: mapping.source '<adım adı>.data', value.ref boş. Eşlemesiz tek değer yazacaksanız mapping.source'u boş bırakıp value.ref'i doldurun — ikisini birden yazmayın.",
|
|
13
|
+
"headersGuard": "Hata yolunda storage { error, data } olarak yazılır ve 'headers' alanı YOKTUR (microservice.ts:132). Guard'sız `Steps.load_titles.headers[\"total-count\"]` istek patladığında TypeError verir: prop'a NaN yazılır, ifade hatası toast'ı düşer ve flow-engine.ts:53 akışı onError'a çevirebilir. Daima `Steps.<ad>?.headers?.[\"total-count\"] ?? 0` yazın. Header adı backend'de 'Total-Count'tur, axios anahtarları küçük harfe indirir → 'total-count'.",
|
|
14
|
+
"mappingKeys": "mapping.mappings anahtarı satır nesnesine DÜZ yazılır (setter.ts:83-104 `object[key] = …`), iç içe alan üretmez: 'parent.name' anahtarı row['parent.name'] olur, row.parent.name olmaz. Kolonun key'i neyse onu düz anahtar verin, kaynaktaki iç içe yolu ref'e yazın ({ type: 'item', ref: 'parent_title.name' }). Anahtar kümesini tablonun gerçek kolonlarıyla sınırlayın; her fazladan anahtar her satırda taşınır.",
|
|
15
|
+
"errorSteps": "errorSteps doluysa (start dışında en az bir adım) engine.onError true döner ve ANA akışın kalan adımları ÇALIŞMAZ (flow-engine.ts:155-171): yükleniyor bayrağını error flow'un kendisi kapatmalıdır. errorSteps varken ifade hatası toast'ları da bastırılır (setSuppressErrorToast).",
|
|
16
|
+
"setterTarget": "setter hedefi kayıt anında DesignComponent id'sine çözülür — komponenti silip yeniden eklersen flow'u yeniden kaydet.",
|
|
17
|
+
"binding": "Akışı bağlamak ayrı adımdır: page_schema_patch set_event { target: '<elementId>', event: 'mount', eventValue: '<flow id>' }"
|
|
18
|
+
},
|
|
19
|
+
"flowCreateArgs": {
|
|
20
|
+
"host": "page",
|
|
21
|
+
"hostId": "<page id>",
|
|
22
|
+
"name": "Load titles",
|
|
23
|
+
"slug": "load-titles",
|
|
24
|
+
"steps": [
|
|
25
|
+
{
|
|
26
|
+
"stepId": "start",
|
|
27
|
+
"name": "Start",
|
|
28
|
+
"type": "start",
|
|
29
|
+
"kind": "normal",
|
|
30
|
+
"args": {},
|
|
31
|
+
"branches": [
|
|
32
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": "begin_loading" }
|
|
33
|
+
]
|
|
34
|
+
},
|
|
35
|
+
{
|
|
36
|
+
"stepId": "begin_loading",
|
|
37
|
+
"name": "Yükleniyor",
|
|
38
|
+
"type": "setter",
|
|
39
|
+
"kind": "normal",
|
|
40
|
+
"args": {
|
|
41
|
+
"iteratees": [
|
|
42
|
+
{
|
|
43
|
+
"id": "<GUID>",
|
|
44
|
+
"type": "change",
|
|
45
|
+
"element": "titles_table",
|
|
46
|
+
"property": "isLoading",
|
|
47
|
+
"source": "component",
|
|
48
|
+
"mapping": { "source": "", "mappings": {} },
|
|
49
|
+
"value": { "type": "static", "ref": true }
|
|
50
|
+
}
|
|
51
|
+
]
|
|
52
|
+
},
|
|
53
|
+
"branches": [
|
|
54
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": "load_titles" }
|
|
55
|
+
]
|
|
56
|
+
},
|
|
57
|
+
{
|
|
58
|
+
"stepId": "load_titles",
|
|
59
|
+
"name": "load_titles",
|
|
60
|
+
"type": "microservice",
|
|
61
|
+
"kind": "normal",
|
|
62
|
+
"args": {
|
|
63
|
+
"microserviceId": "<microservice id>",
|
|
64
|
+
"isRaw": false,
|
|
65
|
+
"parameters": [
|
|
66
|
+
{ "name": "limit", "type": "text", "source": "query-string", "value": { "type": "property", "ref": { "element": "titles_table", "property": "limit" } } },
|
|
67
|
+
{ "name": "offset", "type": "text", "source": "query-string", "value": { "type": "property", "ref": { "element": "titles_table", "property": "offset" } } },
|
|
68
|
+
{ "name": "sortField", "type": "text", "source": "query-string", "value": { "type": "property", "ref": { "element": "titles_table", "property": "sortField" } } },
|
|
69
|
+
{ "name": "sortType", "type": "text", "source": "query-string", "value": { "type": "property", "ref": { "element": "titles_table", "property": "sortType" } } },
|
|
70
|
+
{ "name": "filters", "type": "array", "source": "query-string", "value": { "type": "property", "ref": { "element": "titles_table", "property": "filters" } } }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
"branches": [
|
|
74
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": "apply_rows" }
|
|
75
|
+
]
|
|
76
|
+
},
|
|
77
|
+
{
|
|
78
|
+
"stepId": "apply_rows",
|
|
79
|
+
"name": "Tabloya yaz",
|
|
80
|
+
"type": "setter",
|
|
81
|
+
"kind": "normal",
|
|
82
|
+
"args": {
|
|
83
|
+
"iteratees": [
|
|
84
|
+
{
|
|
85
|
+
"id": "<GUID>",
|
|
86
|
+
"type": "change",
|
|
87
|
+
"element": "titles_table",
|
|
88
|
+
"property": "isLoading",
|
|
89
|
+
"source": "component",
|
|
90
|
+
"mapping": { "source": "", "mappings": {} },
|
|
91
|
+
"value": { "type": "static", "ref": false }
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
"id": "<GUID>",
|
|
95
|
+
"type": "change",
|
|
96
|
+
"element": "titles_table",
|
|
97
|
+
"property": "totalRows",
|
|
98
|
+
"source": "component",
|
|
99
|
+
"mapping": { "source": "", "mappings": {} },
|
|
100
|
+
"value": { "type": "code", "ref": "Number(Steps.load_titles?.headers?.[\"total-count\"] ?? 0)" }
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"id": "<GUID>",
|
|
104
|
+
"type": "change",
|
|
105
|
+
"element": "titles_table",
|
|
106
|
+
"property": "rows",
|
|
107
|
+
"source": "component",
|
|
108
|
+
"mapping": {
|
|
109
|
+
"source": "load_titles.data",
|
|
110
|
+
"mappings": {
|
|
111
|
+
"id": { "type": "item", "ref": "id", "propertyType": "text" },
|
|
112
|
+
"code": { "type": "item", "ref": "code", "propertyType": "text" },
|
|
113
|
+
"name": { "type": "item", "ref": "name", "propertyType": "text" },
|
|
114
|
+
"parent_name": { "type": "item", "ref": "parent_title.name", "propertyType": "text" },
|
|
115
|
+
"status": { "type": "code", "ref": "Row.status === 'Active' ? 'Aktif' : 'Pasif'", "propertyType": "text" }
|
|
116
|
+
}
|
|
117
|
+
},
|
|
118
|
+
"value": { "type": "storage", "ref": "" }
|
|
119
|
+
}
|
|
120
|
+
]
|
|
121
|
+
},
|
|
122
|
+
"branches": [
|
|
123
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": null }
|
|
124
|
+
]
|
|
125
|
+
}
|
|
126
|
+
],
|
|
127
|
+
"errorSteps": [
|
|
128
|
+
{
|
|
129
|
+
"stepId": "error_start",
|
|
130
|
+
"name": "Error start",
|
|
131
|
+
"type": "start",
|
|
132
|
+
"kind": "normal",
|
|
133
|
+
"args": {},
|
|
134
|
+
"branches": [
|
|
135
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": "notify_error" }
|
|
136
|
+
]
|
|
137
|
+
},
|
|
138
|
+
{
|
|
139
|
+
"stepId": "notify_error",
|
|
140
|
+
"name": "Hata mesajı",
|
|
141
|
+
"type": "alert",
|
|
142
|
+
"kind": "normal",
|
|
143
|
+
"args": { "message": "Kayıtlar yüklenemedi", "alertType": "error" },
|
|
144
|
+
"branches": [
|
|
145
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": "stop_loading" }
|
|
146
|
+
]
|
|
147
|
+
},
|
|
148
|
+
{
|
|
149
|
+
"stepId": "stop_loading",
|
|
150
|
+
"name": "Yüklenmeyi kapat",
|
|
151
|
+
"type": "setter",
|
|
152
|
+
"kind": "normal",
|
|
153
|
+
"args": {
|
|
154
|
+
"iteratees": [
|
|
155
|
+
{
|
|
156
|
+
"id": "<GUID>",
|
|
157
|
+
"type": "change",
|
|
158
|
+
"element": "titles_table",
|
|
159
|
+
"property": "isLoading",
|
|
160
|
+
"source": "component",
|
|
161
|
+
"mapping": { "source": "", "mappings": {} },
|
|
162
|
+
"value": { "type": "static", "ref": false }
|
|
163
|
+
}
|
|
164
|
+
]
|
|
165
|
+
},
|
|
166
|
+
"branches": [
|
|
167
|
+
{ "id": "<GUID>", "name": "always", "nextStepId": null }
|
|
168
|
+
]
|
|
169
|
+
}
|
|
170
|
+
]
|
|
171
|
+
},
|
|
172
|
+
"requiredParameterVariant": {
|
|
173
|
+
"note": "Zorunlu (isRequired: true) filtreli uçlarda parametre adımda verilir, yoksa MissingParameterException. Sabit değer yerine sayfa değişkeni: value.type 'variable'. Kullanıcıya özel uçlarda kalıcı çözüm parametre DEĞİL middleware + user.id filtresidir (platform_guide('middleware') Tarif 3).",
|
|
174
|
+
"parameters": [
|
|
175
|
+
{ "name": "patient_id", "type": "guid", "source": "query-string", "value": { "ref": "<değer ya da değişken adı>", "type": "static" } }
|
|
176
|
+
]
|
|
177
|
+
}
|
|
178
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "hr + sms projelerinde doğrulanmış şekil. Kurallar: operation 'list' YOK → get + isSingle:false; kökte entityId zorunlu; selectFields'ta fieldId zorunlu + alias = entity slug; join'li alanlarda path = ilişki alanı id zinciri (kök alanlarda path yazılmaz), joins[].path = kökEntityId + aynı zincir — path boş bırakılırsa runtime çalışır ama Studio join alanlarını seçili gösteremez; sort/limit her zaman verilir yoksa Studio formu null'da patlar; filtre value alanı JavaScript ifadesidir."
|
|
5
|
+
},
|
|
6
|
+
"definition": {
|
|
7
|
+
"name": "Get Products",
|
|
8
|
+
"slug": "get-products",
|
|
9
|
+
"type": "rest-api",
|
|
10
|
+
"httpMethod": "get",
|
|
11
|
+
"url": "products",
|
|
12
|
+
"source": "table",
|
|
13
|
+
"returnType": "flat",
|
|
14
|
+
"enableDynamicFilters": true,
|
|
15
|
+
"sourceParameters": {
|
|
16
|
+
"operation": "get",
|
|
17
|
+
"entityId": "<products entity id>",
|
|
18
|
+
"alias": "products",
|
|
19
|
+
"isSingle": false,
|
|
20
|
+
"selectFields": [
|
|
21
|
+
{
|
|
22
|
+
"alias": "products",
|
|
23
|
+
"fieldSlug": "name",
|
|
24
|
+
"fieldId": "<name alanı id>"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"alias": "products",
|
|
28
|
+
"fieldSlug": "price",
|
|
29
|
+
"fieldId": "<price alanı id>"
|
|
30
|
+
}
|
|
31
|
+
],
|
|
32
|
+
"joins": [],
|
|
33
|
+
"filters": [],
|
|
34
|
+
"sort": {
|
|
35
|
+
"name": "sortField",
|
|
36
|
+
"type": "sortType",
|
|
37
|
+
"source": "query-string"
|
|
38
|
+
},
|
|
39
|
+
"limit": {
|
|
40
|
+
"name": "limit",
|
|
41
|
+
"offset": "offset",
|
|
42
|
+
"source": "query-string"
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
"entityId": "<products entity id>",
|
|
46
|
+
"isReturnSchemaArray": true,
|
|
47
|
+
"isParameterSchemaArray": false
|
|
48
|
+
}
|
|
49
|
+
}
|