@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,194 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": true,
|
|
4
|
+
"note": "qm projesindeki gerçek 'Authenticate' middleware'inden damıtılmış iskelet (22 step'ten çekirdek akış). Kurallar: type='middleware'; returnDtoId GEREKMEZ ve state=unready normaldir; middleware'ler ada göre ALFABETİK koşar; non-2xx response hedefi hiç çalıştırmaz; set-user değişken adı sabit 'user', variable step'i rule.name ile keyfî ad kurar; girdi HTTP bağlamından okunur (cookie/header/queryString/body), 'input' YOKTUR. system.validateToken dönüşü { isValid, claims } — claim anahtarları system.generateToken'a verilen sözlüğün anahtarlarıdır (bu örnekte userId), token.data.id diye bir alan YOKTUR. Bağlama: microservice definition.middlewareIds / page_create middlewareIds. Ayrıntı: platform_guide('middleware'). Rol kapısı AYRI bir middleware'e ayrılmaz — kapı bu akışın içinde, setUser'dan sonra durur (roleGateVariant); rol slug'ları user_roles.role_id DÜZ FK olduğu için ayrı bir roles okumasıyla eşleştirilir.",
|
|
5
|
+
"harvestedFrom": "qm Authenticate (0198c959-eb84-7d2b-88be-9cd46bd39318), 2026-08-16"
|
|
6
|
+
},
|
|
7
|
+
"definition": {
|
|
8
|
+
"name": "Authenticate",
|
|
9
|
+
"slug": "authenticate",
|
|
10
|
+
"type": "middleware",
|
|
11
|
+
"steps": [
|
|
12
|
+
{
|
|
13
|
+
"id": "<GUID-1 üret: crypto.randomUUID()>",
|
|
14
|
+
"name": "start",
|
|
15
|
+
"type": "start",
|
|
16
|
+
"branches": [
|
|
17
|
+
{ "id": "<GUID-2>", "name": "start", "type": "success", "nextStepId": "<GUID-3>" }
|
|
18
|
+
]
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "<GUID-3>",
|
|
22
|
+
"name": "token",
|
|
23
|
+
"type": "code",
|
|
24
|
+
"rule": {
|
|
25
|
+
"stepType": "code",
|
|
26
|
+
"expression": "return system.validateToken(cookie.APP_Token)"
|
|
27
|
+
},
|
|
28
|
+
"branches": [
|
|
29
|
+
{ "id": "<GUID-4>", "name": "success", "type": "success", "nextStepId": "<GUID-5>" }
|
|
30
|
+
]
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "<GUID-5>",
|
|
34
|
+
"name": "token_check",
|
|
35
|
+
"type": "condition",
|
|
36
|
+
"rule": {
|
|
37
|
+
"stepType": "condition",
|
|
38
|
+
"items": [
|
|
39
|
+
{ "expression": "token.data.isValid && token.data.claims.userId", "branchId": "<GUID-6>" }
|
|
40
|
+
]
|
|
41
|
+
},
|
|
42
|
+
"branches": [
|
|
43
|
+
{ "id": "<GUID-6>", "name": "<GUID-6>", "type": "success", "nextStepId": "<GUID-8>" },
|
|
44
|
+
{ "id": "<GUID-7>", "name": "else", "type": "success", "nextStepId": "<GUID-14>" }
|
|
45
|
+
]
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "<GUID-8>",
|
|
49
|
+
"name": "userTable",
|
|
50
|
+
"type": "table",
|
|
51
|
+
"rule": {
|
|
52
|
+
"stepType": "table",
|
|
53
|
+
"datasourceId": "<datasource id>",
|
|
54
|
+
"entityId": "<users entity id>",
|
|
55
|
+
"operation": "get",
|
|
56
|
+
"isSingle": true,
|
|
57
|
+
"selectFields": [
|
|
58
|
+
{ "alias": "users", "fieldId": "<id alanı id>", "fieldSlug": "id" },
|
|
59
|
+
{ "alias": "users", "fieldId": "<email alanı id>", "fieldSlug": "email" },
|
|
60
|
+
{ "alias": "users", "fieldId": "<first_name alanı id>", "fieldSlug": "first_name" }
|
|
61
|
+
],
|
|
62
|
+
"filters": [
|
|
63
|
+
{ "alias": "users", "fieldId": "<id alanı id>", "fieldSlug": "id", "operator": "eq", "source": "expression", "value": "token.data.claims.userId", "filterType": "single", "conditionType": "and", "isRequired": true, "children": [], "order": 0 }
|
|
64
|
+
],
|
|
65
|
+
"joins": [],
|
|
66
|
+
"sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
|
|
67
|
+
"limit": { "name": "limit", "offset": "offset", "source": "query-string" }
|
|
68
|
+
},
|
|
69
|
+
"branches": [
|
|
70
|
+
{ "id": "<GUID-9>", "name": "success", "type": "success", "nextStepId": "<GUID-10>" }
|
|
71
|
+
]
|
|
72
|
+
},
|
|
73
|
+
{
|
|
74
|
+
"id": "<GUID-10>",
|
|
75
|
+
"name": "setUser",
|
|
76
|
+
"type": "set-user",
|
|
77
|
+
"rule": {
|
|
78
|
+
"stepType": "set-user",
|
|
79
|
+
"expression": "{\n ...userTable.data\n}",
|
|
80
|
+
"dataTransferObjectId": "<user DTO id — opsiyonel; verilirse workflow_middleware_variables şemayı listeler>"
|
|
81
|
+
},
|
|
82
|
+
"branches": [
|
|
83
|
+
{ "id": "<GUID-11>", "name": "success", "type": "success", "nextStepId": "<GUID-12>" }
|
|
84
|
+
]
|
|
85
|
+
},
|
|
86
|
+
{
|
|
87
|
+
"id": "<GUID-12>",
|
|
88
|
+
"name": "successResponse",
|
|
89
|
+
"type": "response",
|
|
90
|
+
"rule": {
|
|
91
|
+
"stepType": "response",
|
|
92
|
+
"bodyExpression": "{ success: true }",
|
|
93
|
+
"httpStatusCode": 200,
|
|
94
|
+
"headerExpression": "{ }"
|
|
95
|
+
},
|
|
96
|
+
"branches": []
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
"id": "<GUID-14>",
|
|
100
|
+
"name": "unauthorizedResponse",
|
|
101
|
+
"type": "response",
|
|
102
|
+
"rule": {
|
|
103
|
+
"stepType": "response",
|
|
104
|
+
"bodyExpression": "{ success: false, message: 'Oturum geçersiz.' }",
|
|
105
|
+
"httpStatusCode": 401,
|
|
106
|
+
"headerExpression": "{ }"
|
|
107
|
+
},
|
|
108
|
+
"branches": []
|
|
109
|
+
}
|
|
110
|
+
]
|
|
111
|
+
},
|
|
112
|
+
"settingsSetterVariant": {
|
|
113
|
+
"note": "Genel amaçlı setter (qm '[SY] Settings Setter', 3 step): start -> settings [table get, ayarlar tablosu] -> setVar [type:'variable', rule.name:'settings', rule.expression:'{ ...settings.data }', rule.dataTransferObjectId:<ayarlar DTO>]. Response YOK — sadece değişken kurar; bağlandığı her uçta settings.<alan> okunur. Ad öneki ([A]/[SY]) alfabetik koşum sırasını kurmak içindir."
|
|
114
|
+
},
|
|
115
|
+
"roleGateVariant": {
|
|
116
|
+
"note": "Rol kapısını AYRI bir middleware'e ayırma: kimlik middleware'i set-user'a ULAŞMADAN biterse (çerez yok, koşul else dalı) 'user' hiç tanımlanmaz ve rolü okuyan middleware ham 'user is not defined' ile 500 verir — kullanıcı 401 yerine 500 görür (crm koşumu B-3). Kapıyı bu akışın İÇİNE, setUser'dan SONRA koy. Authorized hedefin rolleri BOŞSA true döndüğü için tek middleware her uca bağlanabilir; kapı yalnız roleIds dolu uçlarda kapanır.",
|
|
117
|
+
"relationFieldTrap": "user_roles.role_id bir table adımında NESNE DEĞİL düz FK'dır (row_list her ilişki için join'i kendisi üretir, table adımı yalnız joins'te bildirileni çevirir). 'p.role_id.slug' undefined döner, roller sessizce boşalır ve Authorized her zaman false olur. Aşağıdaki ifade iki şekle de dayanır.",
|
|
118
|
+
"steps": [
|
|
119
|
+
{
|
|
120
|
+
"id": "<GUID-R1>", "name": "rolesTable", "type": "table",
|
|
121
|
+
"rule": {
|
|
122
|
+
"stepType": "table", "datasourceId": "<datasource id>", "entityId": "<user_roles entity id>",
|
|
123
|
+
"operation": "get", "alias": "user_roles", "isSingle": false,
|
|
124
|
+
"selectFields": [
|
|
125
|
+
{ "alias": "user_roles", "fieldId": "<user_roles.id>", "fieldSlug": "id" },
|
|
126
|
+
{ "alias": "user_roles", "fieldId": "<user_roles.role_id>", "fieldSlug": "role_id" }
|
|
127
|
+
],
|
|
128
|
+
"filters": [
|
|
129
|
+
{ "alias": "user_roles", "fieldId": "<user_roles.user_id>", "operator": "eq", "source": "expression",
|
|
130
|
+
"value": "userTable.data.id", "filterType": "single", "conditionType": "and",
|
|
131
|
+
"isRequired": true, "children": [], "order": 0 }
|
|
132
|
+
],
|
|
133
|
+
"joins": [], "fields": [], "returnFields": [],
|
|
134
|
+
"sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
|
|
135
|
+
"limit": { "name": "limit", "offset": "offset", "source": "query-string" }
|
|
136
|
+
},
|
|
137
|
+
"branches": [
|
|
138
|
+
{ "id": "<GUID-R2>", "name": "success", "type": "success", "nextStepId": "<GUID-R3>" },
|
|
139
|
+
{ "id": "<GUID-R2b>", "name": "fail", "type": "fail", "nextStepId": "<GUID-14>" }
|
|
140
|
+
]
|
|
141
|
+
},
|
|
142
|
+
{
|
|
143
|
+
"id": "<GUID-R3>", "name": "allRoles", "type": "table",
|
|
144
|
+
"rule": {
|
|
145
|
+
"stepType": "table", "datasourceId": "<datasource id>", "entityId": "<roles entity id>",
|
|
146
|
+
"operation": "get", "alias": "roles", "isSingle": false,
|
|
147
|
+
"selectFields": [
|
|
148
|
+
{ "alias": "roles", "fieldId": "<roles.id>", "fieldSlug": "id" },
|
|
149
|
+
{ "alias": "roles", "fieldId": "<roles.slug>", "fieldSlug": "slug" }
|
|
150
|
+
],
|
|
151
|
+
"filters": [], "joins": [], "fields": [], "returnFields": [],
|
|
152
|
+
"sort": { "name": "sortField", "type": "sortType", "source": "query-string" },
|
|
153
|
+
"limit": { "name": "limit", "offset": "offset", "source": "query-string" }
|
|
154
|
+
},
|
|
155
|
+
"branches": [
|
|
156
|
+
{ "id": "<GUID-R4>", "name": "success", "type": "success", "nextStepId": "<GUID-R5>" },
|
|
157
|
+
{ "id": "<GUID-R4b>", "name": "fail", "type": "fail", "nextStepId": "<GUID-14>" }
|
|
158
|
+
]
|
|
159
|
+
},
|
|
160
|
+
{
|
|
161
|
+
"id": "<GUID-R5>", "name": "setUser", "type": "set-user",
|
|
162
|
+
"rule": {
|
|
163
|
+
"stepType": "set-user",
|
|
164
|
+
"expression": "(function () {\n var link = rolesTable.data || [];\n var all = allRoles.data || [];\n var slugs = link.map(function (ur) {\n var rid = ur.role_id && ur.role_id.id ? ur.role_id.id : ur.role_id;\n var match = all.filter(function (r) { return r.id === rid; })[0];\n return match && match.slug;\n }).filter(function (s) { return !!s; });\n return Object.assign({}, userTable.data, { roles: slugs, roles_csv: ',' + slugs.join(',') + ',' });\n})()",
|
|
165
|
+
"dataTransferObjectId": "<user DTO id — opsiyonel>"
|
|
166
|
+
},
|
|
167
|
+
"branches": [{ "id": "<GUID-R6>", "name": "success", "type": "success", "nextStepId": "<GUID-R7>" }]
|
|
168
|
+
},
|
|
169
|
+
{
|
|
170
|
+
"id": "<GUID-R7>", "name": "roleGate", "type": "condition",
|
|
171
|
+
"rule": { "stepType": "condition", "items": [{ "expression": "Authorized(user.roles)", "branchId": "<GUID-R8>" }] },
|
|
172
|
+
"branches": [
|
|
173
|
+
{ "id": "<GUID-R8>", "name": "<GUID-R8>", "type": "success", "nextStepId": null },
|
|
174
|
+
{ "id": "<GUID-R9>", "name": "else", "type": "fail", "nextStepId": "<GUID-F>" }
|
|
175
|
+
]
|
|
176
|
+
},
|
|
177
|
+
{
|
|
178
|
+
"id": "<GUID-F>", "name": "forbiddenResponse", "type": "response",
|
|
179
|
+
"rule": {
|
|
180
|
+
"stepType": "response",
|
|
181
|
+
"bodyExpression": "{ success: false, message: 'Bu işlem için yetkiniz yok.' }",
|
|
182
|
+
"httpStatusCode": 403, "headerExpression": "{ }"
|
|
183
|
+
},
|
|
184
|
+
"branches": []
|
|
185
|
+
}
|
|
186
|
+
],
|
|
187
|
+
"acceptance": "Dört testle kabul edilir: (1) rolü olan kullanıcı korumalı ucu çağırır → 200; (2) rolü olmayan kullanıcı → 403; (3) kimliksiz çağrı (microservice_execute anonymous:true) → 401; (4) roleIds boş bir uç aynı middleware'e bağlıyken kimliği doğrulanmış herkese açık kalır (Authorized boş rol listesinde true döner)."
|
|
188
|
+
},
|
|
189
|
+
"consumptionExample": {
|
|
190
|
+
"note": "Kimlik kapsaması: uca middleware'i bağla (definition.middlewareIds) + owner filtresi expression yap. qm kanıtı: sy-users-get-my-profile.",
|
|
191
|
+
"middlewareIds": ["<authenticate workflow id>"],
|
|
192
|
+
"filter": { "fieldId": "<owner alanı id>", "operator": "eq", "source": "expression", "value": "user.id", "filterType": "single", "conditionType": "and", "isRequired": false, "children": [], "order": 0 }
|
|
193
|
+
}
|
|
194
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": false,
|
|
4
|
+
"note": "Detay / profil sayfası: form arketipinin salt-okuma akrabası. Kimliği query parametresinden DEĞİL middleware'den alır, ilişkili kayıtları alt tabloda gösterir, ikincil işlemleri (şifre değiştirme) modal içinde kendi formuyla yapar.",
|
|
5
|
+
"harvestedFrom": "qm projesi '[SY] Profil bilgilerim' sayfası, 2026-08-17; ayrıntı: zeyrek-mcp/docs/18-sayfa-arketipleri.md §4",
|
|
6
|
+
"pairsWith": "page-form-upsert (düzenlenebilir kardeşi), middleware-authenticate (kimliği kuran guard)"
|
|
7
|
+
},
|
|
8
|
+
"_rules": {
|
|
9
|
+
"rootLayers": "modal ve drawer page_container'ın İÇİNDE değil, şemanın KÖKÜNDE onun kardeşi olarak durur. Kapsayıcıya konursa sayfa akışının içine girer ve katman olarak açılmaz. Gateway kök eklemeleri kapsayıcıya yönlendirir ama bu iki tipi istisna tutar.",
|
|
10
|
+
"identityFromMiddleware": "Profil/‘benim kayıtlarım’ sayfalarında kimlik istemciden gelmez: sayfaya ve uca auth middleware'i bağlanır, ucun filtresi source:'expression' + value:'user.id' olur. Sabit id ya da query parametresi kullanmak sahtelenebilir. Bkz. platform_guide('middleware') Tarif 3.",
|
|
11
|
+
"viewMode": "Salt okuma için input'ların isViewmode prop'u kullanılır — ayrı bir 'detay' komponent ailesi yoktur, aynı form komponentleri görüntüleme kipine alınır.",
|
|
12
|
+
"secondaryForm": "Sayfa içi ikinci iş (şifre güncelleme) modalın children'ındaki kendi form'u ve kendi submit akışıyla yaşar; ana formun akışına karıştırılmaz.",
|
|
13
|
+
"relatedList": "İlişkili kayıtlar alt tabloda listelenir ve kendi mount akışıyla dolar; satıra tıklama ilgili detay sayfasına/drawer'a gider (flow-list-to-form deseni)."
|
|
14
|
+
},
|
|
15
|
+
"pageCreateArgs": {
|
|
16
|
+
"name": "Profil bilgilerim",
|
|
17
|
+
"slug": "profil-bilgilerim",
|
|
18
|
+
"browserTitle": "Profil bilgilerim",
|
|
19
|
+
"layoutId": "<layout id>",
|
|
20
|
+
"middlewareIds": ["<auth middleware id>"]
|
|
21
|
+
},
|
|
22
|
+
"schemaPatchOps": [
|
|
23
|
+
{
|
|
24
|
+
"op": "add_component",
|
|
25
|
+
"parent": "page_container",
|
|
26
|
+
"component": {
|
|
27
|
+
"elementId": "profile_form", "kind": "component", "type": "form", "name": "profile_form",
|
|
28
|
+
"props": { "title": { "type": "static", "data": "Bilgilerim" } },
|
|
29
|
+
"states": { "isSticky": false, "isDroppable": true, "isScrollable": false, "isWrapper": true },
|
|
30
|
+
"blocks": [{ "breakpoint": 1024, "size": { "h": 24, "w": 12 }, "position": { "x": 0, "y": 0 } }],
|
|
31
|
+
"children": [], "events": [], "validations": [], "stateBindings": []
|
|
32
|
+
}
|
|
33
|
+
},
|
|
34
|
+
{
|
|
35
|
+
"op": "add_component",
|
|
36
|
+
"parent": "profile_form",
|
|
37
|
+
"component": {
|
|
38
|
+
"elementId": "in_full_name", "kind": "component", "type": "textinput", "name": "in_full_name",
|
|
39
|
+
"props": { "label": { "type": "static", "data": "Ad Soyad" }, "isViewmode": { "type": "static", "data": true } },
|
|
40
|
+
"states": { "isSticky": false, "isDroppable": false, "isScrollable": false, "isWrapper": false },
|
|
41
|
+
"blocks": [{ "breakpoint": 1024, "size": { "h": 5, "w": 6 }, "position": { "x": 0, "y": 0 } }],
|
|
42
|
+
"children": [], "events": [], "validations": [], "stateBindings": []
|
|
43
|
+
}
|
|
44
|
+
},
|
|
45
|
+
{
|
|
46
|
+
"op": "add_component",
|
|
47
|
+
"parent": "root",
|
|
48
|
+
"component": {
|
|
49
|
+
"elementId": "password_modal", "kind": "component", "type": "modal", "name": "password_modal",
|
|
50
|
+
"props": { "title": { "type": "static", "data": "Şifre güncelle" } },
|
|
51
|
+
"states": { "isSticky": false, "isDroppable": true, "isScrollable": true, "isWrapper": true },
|
|
52
|
+
"blocks": [{ "breakpoint": 1024, "size": { "h": 24, "w": 6 }, "position": { "x": 0, "y": 0 } }],
|
|
53
|
+
"children": [], "events": [], "validations": [], "stateBindings": []
|
|
54
|
+
}
|
|
55
|
+
},
|
|
56
|
+
{
|
|
57
|
+
"op": "add_component",
|
|
58
|
+
"parent": "password_modal",
|
|
59
|
+
"component": {
|
|
60
|
+
"elementId": "password_form", "kind": "component", "type": "form", "name": "password_form",
|
|
61
|
+
"props": {},
|
|
62
|
+
"states": { "isSticky": false, "isDroppable": true, "isScrollable": false, "isWrapper": true },
|
|
63
|
+
"blocks": [{ "breakpoint": 1024, "size": { "h": 20, "w": 12 }, "position": { "x": 0, "y": 0 } }],
|
|
64
|
+
"children": [], "events": [], "validations": [], "stateBindings": []
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
{ "op": "set_event", "target": "page_container", "event": "mount", "eventValue": "<profil yükleme flow id>" },
|
|
68
|
+
{ "op": "set_event", "target": "password_form", "event": "submit", "eventValue": "<şifre güncelleme flow id>" }
|
|
69
|
+
],
|
|
70
|
+
"mountFlowCreateArgs": {
|
|
71
|
+
"host": "page",
|
|
72
|
+
"hostId": "<sayfa id>",
|
|
73
|
+
"name": "Page - mount",
|
|
74
|
+
"slug": "page-mount",
|
|
75
|
+
"steps": [
|
|
76
|
+
{
|
|
77
|
+
"stepId": "start", "name": "start", "type": "start", "kind": "normal", "args": {},
|
|
78
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": "get_profile" }]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"stepId": "get_profile", "name": "get_profile", "type": "microservice", "kind": "normal",
|
|
82
|
+
"args": { "microserviceId": "<user.id filtreli profil ucu id>", "isRaw": false, "parameters": [] },
|
|
83
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": "fill_profile" }]
|
|
84
|
+
},
|
|
85
|
+
{
|
|
86
|
+
"stepId": "fill_profile", "name": "fill_profile", "type": "setter", "kind": "normal",
|
|
87
|
+
"args": { "iteratees": [{
|
|
88
|
+
"id": "<GUID>", "type": "change", "source": "component", "element": "in_full_name", "property": "value",
|
|
89
|
+
"mapping": { "source": "", "mappings": {} },
|
|
90
|
+
"value": { "type": "storage", "ref": "get_profile.data.full_name" } }] },
|
|
91
|
+
"branches": [{ "id": "<GUID>", "name": "always", "nextStepId": null }]
|
|
92
|
+
}
|
|
93
|
+
]
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
{
|
|
2
|
+
"_meta": {
|
|
3
|
+
"synthetic": false,
|
|
4
|
+
"note": "Dinamik form sayfası: alan SAYISI ve her alanın TİPİ çalışma anında veriden gelir (anket, kural tabanlı başvuru formu, denetim listesi). html kaçış kapısı KULLANILMAZ — repeater + `item` iki yönlü bağı bunu hazır komponentlerle yapar. Bu dosya asıl olarak İKİ AKIŞIN tam gövdesini taşır; şema/stil tarafı platform_guide('page') Kural 1g + 1h'te (hazır stil blokları 1h-3'te).",
|
|
5
|
+
"harvestedFrom": "anket projesi 'Anket Doldur' sayfası (anket-public/anket-doldur), 2026-08-19; 4 soru tipi (single_choice / multi_choice / yes_no / free_text) tek gönderimde uçtan uca doğrulandı",
|
|
6
|
+
"pairsWith": "page-form-upsert (alanları tasarım anında bilinen klasik form)"
|
|
7
|
+
},
|
|
8
|
+
"_rules": {
|
|
9
|
+
"order": "Kuruluş sırası bağlayıcı: page_create → page_schema_patch (tüm add_component) → page_schema_patch (tüm set_binding) → TÜM flow_create'ler → TEK page_schema_patch (bütün set_event'ler). Sayfada bağlı akış varken yeni akış eklenemez.",
|
|
10
|
+
"template": "Repeater'ın children ağacı ŞABLONDUR; rows dizisindeki her satır için yinelenir. Satır bağlamı çocuklara item (eşanlamlısı Row, sırası RowIndex) olarak iner ve iç içe HER seviyeye taşınır.",
|
|
11
|
+
"twoWayBinding": "Çocuk komponentin prop'u {type:'code', ref:'{{ item.<alan> }}'} kalıbına TAM uyuyorsa kullanıcı değiştirdiğinde runtime değeri rows.data[<index>].<alan>'a geri yazar (repeater.handler.ts → handleRepeaterItemPropertySet). Flow gerekmez. Kalıp bozulursa ('{{ item.answer || \"\" }}') okur ama YAZMAZ.",
|
|
12
|
+
"rowId": "Satırlara kalıcı bir id ver: getRepeaterRowId sırayla row._id / row.id / index'e bakar; id yoksa index'e düşer ve liste değişince girilen değerler kayar.",
|
|
13
|
+
"variantsSameCell": "Satır tipine göre farklı alan göstermek için TÜM varyantları AYNI blocks[].position'a koy ve isHidden ile ayır — alt alta dizersen gizli olanların ızgara satırları boş kalır. Bu yalnız page_schema_patch yolunda güvenlidir (micro-op hattı çakışma normalizasyonu uygulamaz).",
|
|
14
|
+
"isHiddenGap": "isHidden her komponentte YOK (radio/textarea/container/form/button var; text ve multi-select yok). Gizlenecek komponenti isHidden'ı olan bir container'a sar ve koşulu SARMALAYICIYA bağla. Sarmalayıcı states.isWrapper:false olmalı (isWrapper → min-height:80vh).",
|
|
15
|
+
"readBack": "Toplu okuma flow'da <repeaterAdı>.rows'tur; microservice parametresinin value.type:'code' ifadesinde ve condition expression'ında komponentlere ADIYLA erişilir. Kod bağlamında satırlar HAM gelir; machine.getNode ile bakarsan {data,type} sarılı görünür.",
|
|
16
|
+
"perRowValidation": "Satır BAŞINA validation kurulamaz — validations komponentin kendisindedir ve tüm satırlar paylaşır. Zorunluluğu satır verisinde taşı (required) ve gönderim akışının ilk condition adımında denetle (guard adımı).",
|
|
17
|
+
"flowGuids": "branches[].id ve iteratees[].id GUID'leri TÜM projede tekildir. Başka bir akıştan kopyaladığın gövdeyi aynı GUID'lerle gönderirsen platform RowAlreadyExists döner ve hata slug çakışması gibi okunur — bütün id alanlarını yenile.",
|
|
18
|
+
"rebuildBreaksFlows": "Bir düğümü remove_component + add_component ile (aynı elementId ile bile) yeniden kurarsan DesignComponentId değişir ve o düğümü hedefleyen setter'lar sessizce ölür. Etkilenen her akışı flow_update ile yeniden kaydet.",
|
|
19
|
+
"submitTrigger": "Gönderimi ya btn_submit.click'e ya q_form.submit'e bağla — İKİSİNİ BİRDEN kurma (buton type:'submit' + formId iken click akışı da varsa gönderim iki kez tetiklenir).",
|
|
20
|
+
"storage": "Gönderimi tek TEXT kolonda anlık görüntü olarak sakla: yazarken JSON.stringify, okurken platform JSON'a benzeyen TEXT'i kendisi nesneye çevirir. Studio tablosunda [object Object] görünmesi NORMALDİR, veri sağlamdır (platform_guide('entity') → 'TEXT kolonunda JSON')."
|
|
21
|
+
},
|
|
22
|
+
"schemaShape": {
|
|
23
|
+
"_note": "Ağaç. Komponent gövdelerinin tam şekli Kural 1'de, style blokları Kural 1h-3'te. Üç varyantın da position'ı y:2 — aynı hücre.",
|
|
24
|
+
"tree": [
|
|
25
|
+
"page_container (container, isWrapper:true — tema değişkenleri ve ortalanmış sütun BURADA)",
|
|
26
|
+
"├─ t_title (text, h1)",
|
|
27
|
+
"├─ t_desc (text, p)",
|
|
28
|
+
"├─ q_form (form, isWrapper:false, hideBorders+hidePadding)",
|
|
29
|
+
"│ └─ q_list (repeater, isWrapper:false, rows:[], autoRowSize:true, verticalSpacing:14)",
|
|
30
|
+
"│ └─ q_card (container, isWrapper:false) ← ŞABLONUN KÖKÜ",
|
|
31
|
+
"│ ├─ q_head (text) y:0 content: {{ item.no + '. ' + item.text + (item.required ? ' *' : '') }}",
|
|
32
|
+
"│ ├─ q_help (text) y:1 content: {{ item.help || '' }}",
|
|
33
|
+
"│ ├─ a_choice (radio) y:2 isHidden: {{ !(item.is_single || item.is_yesno) }}",
|
|
34
|
+
"│ ├─ a_text (textarea) y:2 isHidden: {{ !item.is_text }}",
|
|
35
|
+
"│ └─ box_multi (container) y:2 isHidden: {{ !item.is_multi }}",
|
|
36
|
+
"│ └─ a_multi (multi-select) ← kendi isHidden'ı yok, o yüzden sarmalı",
|
|
37
|
+
"├─ box_state (container, isHidden:true) → t_state (text) ← hata şeridi",
|
|
38
|
+
"└─ btn_submit (button, type:'button')"
|
|
39
|
+
]
|
|
40
|
+
},
|
|
41
|
+
"bindingPatchOps": [
|
|
42
|
+
{ "op": "set_binding", "target": "q_head", "prop": "content", "expression": "{{ item.no + '. ' + item.text + (item.required ? ' *' : '') }}" },
|
|
43
|
+
{ "op": "set_binding", "target": "q_help", "prop": "content", "expression": "{{ item.help || '' }}" },
|
|
44
|
+
{ "op": "set_binding", "target": "a_choice", "prop": "options", "expression": "{{ item.options }}" },
|
|
45
|
+
{ "op": "set_binding", "target": "a_choice", "prop": "value", "expression": "{{ item.answer }}", "_note": "GERİ YAZAN bağ — kalıp tam olarak {{ item.<alan> }} olmalı" },
|
|
46
|
+
{ "op": "set_binding", "target": "a_choice", "prop": "isHidden", "expression": "{{ !(item.is_single || item.is_yesno) }}" },
|
|
47
|
+
{ "op": "set_binding", "target": "a_text", "prop": "value", "expression": "{{ item.answer }}" },
|
|
48
|
+
{ "op": "set_binding", "target": "a_text", "prop": "isHidden", "expression": "{{ !item.is_text }}" },
|
|
49
|
+
{ "op": "set_binding", "target": "box_multi", "prop": "isHidden", "expression": "{{ !item.is_multi }}", "_note": "koşul SARMALAYICIDA — multi-select'te isHidden prop'u yok" },
|
|
50
|
+
{ "op": "set_binding", "target": "a_multi", "prop": "options", "expression": "{{ item.options }}" },
|
|
51
|
+
{ "op": "set_binding", "target": "a_multi", "prop": "value", "expression": "{{ item.answer }}" }
|
|
52
|
+
],
|
|
53
|
+
"rowShape": {
|
|
54
|
+
"_note": "Yükleme akışının repeater'a yazdığı satır şekli. is_* bayrakları isHidden ifadelerini kısa tutar; answer iki yönlü bağın hedefidir ve tipe göre '' ya da [] başlar. options {label,value} olarak ÖNCEDEN hazırlanır — radio da multi-select de bu iki alanı okur.",
|
|
55
|
+
"id": "<soru id — kalıcı>",
|
|
56
|
+
"no": 1,
|
|
57
|
+
"text": "Hizmetimizden genel memnuniyetiniz nedir?",
|
|
58
|
+
"help": "Son 3 aydaki deneyiminizi düşünün",
|
|
59
|
+
"required": true,
|
|
60
|
+
"kind": "single_choice",
|
|
61
|
+
"is_single": true,
|
|
62
|
+
"is_multi": false,
|
|
63
|
+
"is_text": false,
|
|
64
|
+
"is_yesno": false,
|
|
65
|
+
"options": [{ "label": "Çok memnunum", "value": "<seçenek id>" }],
|
|
66
|
+
"answer": ""
|
|
67
|
+
},
|
|
68
|
+
"loadFlowCreateArgs": {
|
|
69
|
+
"host": "page",
|
|
70
|
+
"hostId": "<page id>",
|
|
71
|
+
"name": "Formu yukle",
|
|
72
|
+
"slug": "formu-yukle",
|
|
73
|
+
"_note": "page_container.mount'a bağlanır. condition adımı `always` dalını kural dallarıyla BİRLİKTE taşır: always yükleniyor bayrağını her koşumda kapatır, kural dalları içeriği kurar.",
|
|
74
|
+
"steps": [
|
|
75
|
+
{
|
|
76
|
+
"stepId": "start", "name": "start", "type": "start", "kind": "normal",
|
|
77
|
+
"args": { "iteratees": [], "rules": [], "parameters": [] },
|
|
78
|
+
"branches": [{ "id": "<GUID-1>", "name": "always", "nextStepId": "begin_loading" }]
|
|
79
|
+
},
|
|
80
|
+
{
|
|
81
|
+
"stepId": "begin_loading", "name": "begin_loading", "type": "setter", "kind": "normal",
|
|
82
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
83
|
+
{ "id": "<GUID-2>", "type": "change", "source": "component", "element": "page_container", "property": "isLoading",
|
|
84
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } } ] },
|
|
85
|
+
"branches": [{ "id": "<GUID-3>", "name": "always", "nextStepId": "load" }]
|
|
86
|
+
},
|
|
87
|
+
{
|
|
88
|
+
"stepId": "load", "name": "load", "type": "microservice", "kind": "normal",
|
|
89
|
+
"args": { "microserviceId": "<tanım ucu id>", "slug": "<uç slug>", "httpMethod": "post", "isRaw": false, "iteratees": [], "rules": [],
|
|
90
|
+
"parameters": [ { "id": "<GUID-4>", "name": "slug", "type": "string", "source": "body",
|
|
91
|
+
"value": { "type": "code", "ref": "Page.queryParams.s || '<varsayılan>'" } } ] },
|
|
92
|
+
"branches": [{ "id": "<GUID-5>", "name": "always", "nextStepId": "check" }]
|
|
93
|
+
},
|
|
94
|
+
{
|
|
95
|
+
"stepId": "check", "name": "check", "type": "condition", "kind": "normal",
|
|
96
|
+
"args": { "iteratees": [], "parameters": [], "rules": [
|
|
97
|
+
{ "id": "<RULE-OK>", "expression": "!!(Steps.load && Steps.load.data && Steps.load.data.ok)", "type": "if" },
|
|
98
|
+
{ "id": "<RULE-NO>", "expression": "", "type": "else" } ] },
|
|
99
|
+
"branches": [
|
|
100
|
+
{ "id": "<GUID-6>", "name": "always", "nextStepId": "end_loading" },
|
|
101
|
+
{ "id": "<GUID-7>", "name": "<RULE-OK>", "nextStepId": "apply" },
|
|
102
|
+
{ "id": "<GUID-8>", "name": "<RULE-NO>", "nextStepId": "not_found" } ]
|
|
103
|
+
},
|
|
104
|
+
{
|
|
105
|
+
"stepId": "apply", "name": "apply", "type": "setter", "kind": "normal",
|
|
106
|
+
"_note": "Deseni taşıyan adım: uç yanıtını rowShape'e çevirip repeater'a yazar. yes/no seçenekleri burada enjekte edilir — o soru tipinin veritabanında seçeneği yoktur.",
|
|
107
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
108
|
+
{ "id": "<GUID-9>", "type": "change", "source": "component", "element": "t_title", "property": "content",
|
|
109
|
+
"mapping": { "source": "", "mappings": {} },
|
|
110
|
+
"value": { "type": "code", "ref": "(Steps.load && Steps.load.data && Steps.load.data.title) || ''" } },
|
|
111
|
+
{ "id": "<GUID-10>", "type": "change", "source": "component", "element": "t_desc", "property": "content",
|
|
112
|
+
"mapping": { "source": "", "mappings": {} },
|
|
113
|
+
"value": { "type": "code", "ref": "(Steps.load && Steps.load.data && Steps.load.data.description) || ''" } },
|
|
114
|
+
{ "id": "<GUID-11>", "type": "change", "source": "component", "element": "q_list", "property": "rows",
|
|
115
|
+
"mapping": { "source": "", "mappings": {} },
|
|
116
|
+
"value": { "type": "code", "ref": "(function(){ var d = (Steps.load && Steps.load.data) || {}; var qs = d.questions || []; var os = d.options || []; return qs.map(function(q, i){ var t = q.question_type; var opts = os.filter(function(o){ var oq = (o.survey_question_id && o.survey_question_id.id) ? o.survey_question_id.id : o.survey_question_id; return oq === q.id; }).map(function(o){ return { label: o.label, value: o.id }; }); if (t === 'yes_no') { opts = [{ label: 'Evet', value: 'yes' }, { label: 'Hayır', value: 'no' }]; } return { id: q.id, no: i + 1, text: q.text, help: q.help_text || '', required: !!q.is_required, kind: t, is_single: t === 'single_choice', is_multi: t === 'multi_choice', is_text: t === 'free_text', is_yesno: t === 'yes_no', options: opts, answer: (t === 'multi_choice' ? [] : '') }; }); })()" } } ] },
|
|
117
|
+
"branches": [{ "id": "<GUID-12>", "name": "always", "nextStepId": null }]
|
|
118
|
+
},
|
|
119
|
+
{
|
|
120
|
+
"stepId": "not_found", "name": "not_found", "type": "setter", "kind": "normal",
|
|
121
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
122
|
+
{ "id": "<GUID-13>", "type": "change", "source": "component", "element": "t_title", "property": "content",
|
|
123
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": "Bulunamadı" } },
|
|
124
|
+
{ "id": "<GUID-14>", "type": "change", "source": "component", "element": "t_state", "property": "content",
|
|
125
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": "Bu bağlantıya ait yayında bir kayıt yok." } },
|
|
126
|
+
{ "id": "<GUID-15>", "type": "change", "source": "component", "element": "box_state", "property": "isHidden",
|
|
127
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": false } },
|
|
128
|
+
{ "id": "<GUID-16>", "type": "change", "source": "component", "element": "q_form", "property": "isHidden",
|
|
129
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } },
|
|
130
|
+
{ "id": "<GUID-17>", "type": "change", "source": "component", "element": "btn_submit", "property": "isHidden",
|
|
131
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } } ] },
|
|
132
|
+
"branches": [{ "id": "<GUID-18>", "name": "always", "nextStepId": null }]
|
|
133
|
+
},
|
|
134
|
+
{
|
|
135
|
+
"stepId": "end_loading", "name": "end_loading", "type": "setter", "kind": "normal",
|
|
136
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
137
|
+
{ "id": "<GUID-19>", "type": "change", "source": "component", "element": "page_container", "property": "isLoading",
|
|
138
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": false } } ] },
|
|
139
|
+
"branches": [{ "id": "<GUID-20>", "name": "always", "nextStepId": null }]
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
},
|
|
143
|
+
"submitFlowCreateArgs": {
|
|
144
|
+
"host": "page",
|
|
145
|
+
"hostId": "<page id>",
|
|
146
|
+
"name": "Formu gonder",
|
|
147
|
+
"slug": "formu-gonder",
|
|
148
|
+
"_note": "btn_submit.click'e bağlanır. guard adımında `always` dalı YOKTUR — uyarı dalıyla gönderim dalı birbirini dışlar; always olsaydı eksik formda da gönderim koşardı.",
|
|
149
|
+
"steps": [
|
|
150
|
+
{
|
|
151
|
+
"stepId": "start", "name": "start", "type": "start", "kind": "normal",
|
|
152
|
+
"args": { "iteratees": [], "rules": [], "parameters": [] },
|
|
153
|
+
"branches": [{ "id": "<GUID-1>", "name": "always", "nextStepId": "guard" }]
|
|
154
|
+
},
|
|
155
|
+
{
|
|
156
|
+
"stepId": "guard", "name": "guard", "type": "condition", "kind": "normal",
|
|
157
|
+
"_note": "Satır başına validation kurulamadığı için zorunluluk BURADA denetlenir. Boşluk tanımı tipe göre değişir: metin '' , çoklu seçim [].",
|
|
158
|
+
"args": { "iteratees": [], "parameters": [], "rules": [
|
|
159
|
+
{ "id": "<RULE-MISSING>", "expression": "((q_list.rows || []).filter(function(r){ var v = r.answer; return r.required && (v === undefined || v === null || v === '' || (Array.isArray(v) && v.length === 0)); }).length > 0)", "type": "if" },
|
|
160
|
+
{ "id": "<RULE-OK>", "expression": "", "type": "else" } ] },
|
|
161
|
+
"branches": [
|
|
162
|
+
{ "id": "<GUID-2>", "name": "<RULE-MISSING>", "nextStepId": "warn" },
|
|
163
|
+
{ "id": "<GUID-3>", "name": "<RULE-OK>", "nextStepId": "busy" } ]
|
|
164
|
+
},
|
|
165
|
+
{
|
|
166
|
+
"stepId": "warn", "name": "warn", "type": "setter", "kind": "normal",
|
|
167
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
168
|
+
{ "id": "<GUID-4>", "type": "change", "source": "component", "element": "t_state", "property": "content",
|
|
169
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": "Lütfen yıldızlı (*) soruların hepsini yanıtlayın." } },
|
|
170
|
+
{ "id": "<GUID-5>", "type": "change", "source": "component", "element": "box_state", "property": "isHidden",
|
|
171
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": false } } ] },
|
|
172
|
+
"branches": [{ "id": "<GUID-6>", "name": "always", "nextStepId": null }]
|
|
173
|
+
},
|
|
174
|
+
{
|
|
175
|
+
"stepId": "busy", "name": "busy", "type": "setter", "kind": "normal",
|
|
176
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
177
|
+
{ "id": "<GUID-7>", "type": "change", "source": "component", "element": "box_state", "property": "isHidden",
|
|
178
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } },
|
|
179
|
+
{ "id": "<GUID-8>", "type": "change", "source": "component", "element": "btn_submit", "property": "isLoading",
|
|
180
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } } ] },
|
|
181
|
+
"branches": [{ "id": "<GUID-9>", "name": "always", "nextStepId": "post" }]
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
"stepId": "post", "name": "post", "type": "microservice", "kind": "normal",
|
|
185
|
+
"_note": "answers_json TEXT alandır: JSON.stringify ile STRING gönder. option_ids + value_text birlikte tutulur — id'ler raporlamayı, etiketler insan okumasını taşır; etiketler satırdaki options'tan çözülür, ikinci bir uç çağrısı gerekmez.",
|
|
186
|
+
"args": { "microserviceId": "<yazma ucu id>", "slug": "<uç slug>", "httpMethod": "post", "isRaw": false, "iteratees": [], "rules": [],
|
|
187
|
+
"parameters": [
|
|
188
|
+
{ "id": "<GUID-10>", "name": "submitted_at", "type": "string", "source": "body",
|
|
189
|
+
"value": { "type": "code", "ref": "new Date().toISOString()" } },
|
|
190
|
+
{ "id": "<GUID-11>", "name": "answers_json", "type": "string", "source": "body",
|
|
191
|
+
"value": { "type": "code", "ref": "JSON.stringify((q_list.rows || []).map(function(r){ var v = r.answer; var vals = Array.isArray(v) ? v.map(function(x){ return (x && x.value !== undefined) ? x.value : x; }) : ((v === '' || v === undefined || v === null) ? [] : [v]); var labels = (r.options || []).filter(function(o){ return vals.indexOf(o.value) >= 0; }).map(function(o){ return o.label; }); return { question_id: r.id, question_text: r.text, type: r.kind, value_text: r.is_text ? (v || '') : labels.join(', '), option_ids: r.is_text ? [] : vals }; }))" } } ] },
|
|
192
|
+
"branches": [{ "id": "<GUID-12>", "name": "always", "nextStepId": "done" }]
|
|
193
|
+
},
|
|
194
|
+
{
|
|
195
|
+
"stepId": "done", "name": "done", "type": "setter", "kind": "normal",
|
|
196
|
+
"args": { "rules": [], "parameters": [], "iteratees": [
|
|
197
|
+
{ "id": "<GUID-13>", "type": "change", "source": "component", "element": "t_title", "property": "content",
|
|
198
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": "Teşekkürler!" } },
|
|
199
|
+
{ "id": "<GUID-14>", "type": "change", "source": "component", "element": "t_desc", "property": "content",
|
|
200
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": "Yanıtınız kaydedildi." } },
|
|
201
|
+
{ "id": "<GUID-15>", "type": "change", "source": "component", "element": "q_form", "property": "isHidden",
|
|
202
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } },
|
|
203
|
+
{ "id": "<GUID-16>", "type": "change", "source": "component", "element": "btn_submit", "property": "isHidden",
|
|
204
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": true } },
|
|
205
|
+
{ "id": "<GUID-17>", "type": "change", "source": "component", "element": "btn_submit", "property": "isLoading",
|
|
206
|
+
"mapping": { "source": "", "mappings": {} }, "value": { "type": "static", "ref": false } } ] },
|
|
207
|
+
"branches": [{ "id": "<GUID-18>", "name": "always", "nextStepId": null }]
|
|
208
|
+
}
|
|
209
|
+
]
|
|
210
|
+
},
|
|
211
|
+
"eventPatchOps": [
|
|
212
|
+
{ "op": "set_event", "target": "page_container", "event": "mount", "eventValue": "<load flow id>" },
|
|
213
|
+
{ "op": "set_event", "target": "btn_submit", "event": "click", "eventValue": "<submit flow id>" }
|
|
214
|
+
],
|
|
215
|
+
"verification": {
|
|
216
|
+
"render": "page_runtime_get ile paketi doğrula. Tarayıcı ölçümü (1280×720): gövde 1292px, kartlar 680px genişlik / 14px ara, seçenek satırları 46px, buton 48px, boşluk-çakışma yok; 375px'te yatay taşma yok.",
|
|
217
|
+
"binding": "Bir girdiyi değiştir, sonra machine.getNode('q_list').props.rows.data[i].answer'a bak — dolmuyorsa kod prop'unun ref'i {{ item.<alan> }} kalıbından sapmıştır.",
|
|
218
|
+
"guard": "Zorunlu alanlar boşken butona bas: kırmızı şerit çıkmalı ve HİÇBİR uç çağrılmamalı.",
|
|
219
|
+
"submit": "Gönderim sonrası satırı row_list ile oku (grid'de [object Object] görünmesi beklenen davranıştır)."
|
|
220
|
+
}
|
|
221
|
+
}
|