@appaflytech/zeyrek-ai-mcp 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -2
- package/.env.production.example +9 -1
- package/README.md +14 -38
- package/dist/config.js +36 -3
- package/dist/knowledge/backendConstraints.js +982 -0
- package/dist/knowledge/componentCatalog.js +549 -92
- package/dist/knowledge/componentEvents.js +120 -25
- package/dist/knowledge/coverage.js +43 -0
- package/dist/knowledge/flowGuards.js +30 -0
- package/dist/knowledge/functionGuards.js +134 -0
- package/dist/knowledge/guideSections.js +49 -0
- package/dist/knowledge/hydrate.js +97 -0
- package/dist/knowledge/languageCodes.js +36 -0
- package/dist/knowledge/languages.js +105 -0
- package/dist/knowledge/layoutSummary.js +10 -0
- package/dist/knowledge/localization.js +51 -0
- package/dist/knowledge/normalize.js +500 -16
- package/dist/knowledge/outlines.js +167 -12
- package/dist/knowledge/policy.js +14 -5
- package/dist/knowledge/propTypes.js +80 -0
- package/dist/knowledge/rules.js +961 -44
- package/dist/knowledge/schemaGuard.js +199 -0
- package/dist/knowledge/styleResolve.js +63 -4
- package/dist/observability/trace.js +27 -3
- package/dist/platform/csv.js +123 -0
- package/dist/platform/errors.js +107 -6
- package/dist/platform/http.js +34 -4
- package/dist/platform/patch.js +27 -0
- package/dist/platform/refs.js +104 -0
- package/dist/platform/responses.js +25 -6
- package/dist/platform/screenshot.js +544 -16
- package/dist/platform/secrets.js +40 -0
- package/dist/platform/tokenVault.js +89 -0
- package/dist/platform/writeDiff.js +281 -0
- package/dist/resources/examples/flow-call-function.json +66 -0
- package/dist/resources/examples/function-total.json +70 -0
- package/dist/resources/examples/layout-app-shell.json +1 -1
- package/dist/resources/examples/microfunction-node-sum.json +56 -0
- package/dist/resources/examples/microservice-table-list.json +2 -4
- package/dist/resources/examples/page-dynamic-form.json +3 -3
- package/dist/resources/examples/page-flex-sections.json +99 -0
- package/dist/resources/examples/page-sign-in.json +4 -3
- package/dist/resources/guides/component.md +81 -8
- package/dist/resources/guides/entity.md +712 -20
- package/dist/resources/guides/expressions.md +47 -2
- package/dist/resources/guides/external-component.md +9 -0
- package/dist/resources/guides/functions.md +262 -0
- package/dist/resources/guides/getting-started.md +82 -7
- package/dist/resources/guides/layout.md +53 -10
- package/dist/resources/guides/merge.md +231 -0
- package/dist/resources/guides/microfunction.md +166 -0
- package/dist/resources/guides/microservice.md +287 -15
- package/dist/resources/guides/middleware.md +89 -1
- package/dist/resources/guides/modify-existing.md +23 -0
- package/dist/resources/guides/page.md +402 -49
- package/dist/resources/guides/translation.md +150 -8
- package/dist/resources/guides/view.md +16 -1
- package/dist/resources/guides/workflow.md +199 -28
- package/dist/resources/knowledge.js +5 -0
- package/dist/resources/rules.js +27 -0
- package/dist/resources/schema-fingerprint.json +8 -0
- package/dist/resources/schemas/AnalyzeImportQuery.json +51 -0
- package/dist/resources/schemas/ApplyMergePlanCommand.json +19 -0
- package/dist/resources/schemas/CreateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/CreateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/CreateDiscoveredEntitiesCommand.json +19 -8
- package/dist/resources/schemas/CreateEntityCommand.json +52 -12
- package/dist/resources/schemas/CreateEnvironmentCommand.json +31 -0
- package/dist/resources/schemas/CreateGlobalFunctionCommand.json +95 -0
- package/dist/resources/schemas/CreateImportPlanCommand.json +278 -0
- package/dist/resources/schemas/CreateMergePlanCommand.json +34 -0
- package/dist/resources/schemas/CreateMicrofunctionCommand.json +117 -0
- package/dist/resources/schemas/CreateMicrofunctionRuntimeCommand.json +51 -0
- package/dist/resources/schemas/CreateMicroserviceCommand.json +145 -3
- package/dist/resources/schemas/CreateQueryCommand.json +56 -3
- package/dist/resources/schemas/CreateRowCommand.json +38 -0
- package/dist/resources/schemas/CreateViewCommand.json +13 -10
- package/dist/resources/schemas/CreateWorkflowCommand.json +106 -14
- package/dist/resources/schemas/DataJobDto.json +267 -0
- package/dist/resources/schemas/ExportRowsCommand.json +172 -0
- package/dist/resources/schemas/Filter.json +3 -1
- package/dist/resources/schemas/GetRowsQuery.json +6 -1
- package/dist/resources/schemas/GlobalFunctionDto.json +89 -0
- package/dist/resources/schemas/GlobalFunctionUpdateResultDto.json +249 -0
- package/dist/resources/schemas/ImportAnalysisDto.json +401 -0
- package/dist/resources/schemas/ImportPlanDto.json +260 -0
- package/dist/resources/schemas/ImportSampleDto.json +90 -0
- package/dist/resources/schemas/InvokeMicrofunctionCommand.json +25 -0
- package/dist/resources/schemas/PreviewMicroserviceCommand.json +135 -1
- package/dist/resources/schemas/PreviewQueryCommand.json +5 -2
- package/dist/resources/schemas/PreviewWorkflowCommand.json +45 -9
- package/dist/resources/schemas/ResolveMergeConflictsCommand.json +58 -0
- package/dist/resources/schemas/RevertMergePlanCommand.json +19 -0
- package/dist/resources/schemas/RunImportCommand.json +62 -0
- package/dist/resources/schemas/StepDto.json +34 -3
- package/dist/resources/schemas/UpdateBulkRowCommand.json +36 -0
- package/dist/resources/schemas/UpdateDatasourceCommand.json +15 -6
- package/dist/resources/schemas/UpdateEntityCommand.json +52 -11
- package/dist/resources/schemas/UpdateGlobalFunctionCommand.json +66 -0
- package/dist/resources/schemas/UpdateMicrofunctionCommand.json +121 -0
- package/dist/resources/schemas/UpdateMicrofunctionRuntimeCommand.json +54 -0
- package/dist/resources/schemas/UpdateMicroserviceCommand.json +147 -4
- package/dist/resources/schemas/UpdateQueryCommand.json +13 -3
- package/dist/resources/schemas/UpdateRowCommand.json +40 -0
- package/dist/resources/schemas/UpdateViewCommand.json +15 -10
- package/dist/resources/schemas/UpdateWorkflowCommand.json +106 -14
- package/dist/server.js +17 -1
- package/dist/tools/access.js +3 -3
- package/dist/tools/activities.js +40 -0
- package/dist/tools/applications.js +1 -1
- package/dist/tools/automations.js +76 -34
- package/dist/tools/common.js +55 -0
- package/dist/tools/components.js +16 -7
- package/dist/tools/datasources.js +83 -21
- package/dist/tools/dependencies.js +1 -1
- package/dist/tools/deployments.js +34 -2
- package/dist/tools/dtos.js +25 -12
- package/dist/tools/entities.js +113 -28
- package/dist/tools/environments.js +52 -0
- package/dist/tools/externalComponents.js +9 -3
- package/dist/tools/files.js +57 -42
- package/dist/tools/flows.js +205 -13
- package/dist/tools/folders.js +7 -2
- package/dist/tools/functions.js +331 -0
- package/dist/tools/globalFunctions.js +63 -0
- package/dist/tools/importSchemas.js +79 -0
- package/dist/tools/imports.js +241 -0
- package/dist/tools/layouts.js +103 -29
- package/dist/tools/merges.js +113 -0
- package/dist/tools/microfunctions.js +155 -0
- package/dist/tools/microservices.js +270 -40
- package/dist/tools/organizations.js +5 -6
- package/dist/tools/pages.js +421 -104
- package/dist/tools/platformInfo.js +155 -11
- package/dist/tools/projects.js +183 -33
- package/dist/tools/queries.js +32 -10
- package/dist/tools/revisions.js +94 -0
- package/dist/tools/rows.js +484 -50
- package/dist/tools/screenWrite.js +68 -0
- package/dist/tools/screens.js +28 -0
- package/dist/tools/templates.js +32 -5
- package/dist/tools/translations.js +24 -8
- package/dist/tools/views.js +20 -10
- package/dist/tools/workflows.js +56 -21
- package/package.json +7 -2
|
@@ -0,0 +1,982 @@
|
|
|
1
|
+
import { schemaLimits } from './schemaGuard.js';
|
|
2
|
+
export const EMPTY_GUID = '00000000-0000-0000-0000-000000000000';
|
|
3
|
+
/** `ProjectConstants.SupportedLanguages` — ISO 639-1; proje dilleri ve çeviri anahtarları bu listeyle doğrulanır. */
|
|
4
|
+
export const SUPPORTED_LANGUAGES = [
|
|
5
|
+
'aa', 'ab', 'ae', 'af', 'ak', 'am', 'an', 'ar', 'as', 'av', 'ay', 'az',
|
|
6
|
+
'ba', 'be', 'bg', 'bh', 'bi', 'bm', 'bn', 'bo', 'br', 'bs', 'ca', 'ce',
|
|
7
|
+
'ch', 'co', 'cr', 'cs', 'cu', 'cv', 'cy', 'da', 'de', 'dv', 'dz', 'ee',
|
|
8
|
+
'el', 'en', 'eo', 'es', 'et', 'eu', 'fa', 'ff', 'fi', 'fj', 'fo', 'fr',
|
|
9
|
+
'fy', 'ga', 'gd', 'gl', 'gn', 'gu', 'gv', 'ha', 'he', 'hi', 'ho', 'hr',
|
|
10
|
+
'ht', 'hu', 'hy', 'hz', 'ia', 'id', 'ie', 'ig', 'ii', 'ik', 'io', 'is',
|
|
11
|
+
'it', 'iu', 'ja', 'jv', 'ka', 'kg', 'ki', 'kj', 'kk', 'kl', 'km', 'kn',
|
|
12
|
+
'ko', 'kr', 'ks', 'ku', 'kv', 'kw', 'ky', 'la', 'lb', 'lg', 'li', 'ln',
|
|
13
|
+
'lo', 'lt', 'lu', 'lv', 'mg', 'mh', 'mi', 'mk', 'ml', 'mn', 'mr', 'ms',
|
|
14
|
+
'mt', 'my', 'na', 'nb', 'nd', 'ne', 'ng', 'nl', 'nn', 'no', 'nr', 'nv',
|
|
15
|
+
'ny', 'oc', 'oj', 'om', 'or', 'os', 'pa', 'pi', 'pl', 'ps', 'pt', 'qu',
|
|
16
|
+
'rm', 'rn', 'ro', 'ru', 'rw', 'sa', 'sc', 'sd', 'se', 'sg', 'si', 'sk',
|
|
17
|
+
'sl', 'sm', 'sn', 'so', 'sq', 'sr', 'ss', 'st', 'su', 'sv', 'sw', 'ta',
|
|
18
|
+
'te', 'tg', 'th', 'ti', 'tk', 'tl', 'tn', 'to', 'tr', 'ts', 'tt', 'tw',
|
|
19
|
+
'ty', 'ug', 'uk', 'ur', 'uz', 've', 'vi', 'vo', 'wa', 'wo', 'xh', 'yi',
|
|
20
|
+
'yo', 'za', 'zh', 'zu'
|
|
21
|
+
];
|
|
22
|
+
/** `StringExtensions.IsContainsSpecialChar` — adı doğru olmayan bir yardımcı: değer bu karakterlerin HİÇBİRİNİ içermiyorsa true döner. */
|
|
23
|
+
export const FORBIDDEN_NAME_CHARACTERS = "\\|!#~$%&/=?»«@£§€{};'<>,";
|
|
24
|
+
/** `ExternalComponentRules` (backend) — slug deseni tool'un kendisinde, paket boyutu da orada denetleniyor; burada kalanı. */
|
|
25
|
+
export const EXTERNAL_COMPONENT_PROTOCOL_VERSION = 1;
|
|
26
|
+
export const EXTERNAL_COMPONENT_PROP_TYPES = [
|
|
27
|
+
'text', 'long-text', 'switch', 'boolean', 'number', 'icon', 'link', 'image',
|
|
28
|
+
'color', 'date', 'datetime', 'time', 'file', 'flow', 'field', 'self',
|
|
29
|
+
'dropdown', 'switcher', 'radio', 'multi-select', 'object', 'object-form',
|
|
30
|
+
'array', 'views', 'component'
|
|
31
|
+
];
|
|
32
|
+
export const EXTERNAL_COMPONENT_RESERVED_PROPS = [
|
|
33
|
+
'attributes', 'children', 'class', 'classList', 'className', 'dataset', 'id',
|
|
34
|
+
'innerHTML', 'innerText', 'name', 'outerHTML', 'shadowRoot', 'slot', 'style',
|
|
35
|
+
'textContent'
|
|
36
|
+
];
|
|
37
|
+
const COMMON_FIELD_TYPES = ['text', 'number', 'bool', 'date', 'time', 'datetime', 'guid', 'float', 'relation', 'custom', 'object', 'array', 'file', 'incremental'];
|
|
38
|
+
const DEFAULT_VALUE_TYPES = ['none', 'default-value', 'sql-expression'];
|
|
39
|
+
const RELATION_TYPES = ['none', 'one-to-one', 'one-to-many', 'many-to-many'];
|
|
40
|
+
const DTO_TYPES = ['object', 'array', 'bool', 'string', 'number', 'float', 'guid', 'date', 'time', 'datetime', 'file', 'anonymous'];
|
|
41
|
+
const DTO_BEHAVIORS = ['primitive', 'object', 'array'];
|
|
42
|
+
const QUERY_OUTPUT_TYPES = ['array', 'object'];
|
|
43
|
+
const VIEW_PROVIDER_TYPES = ['postgres'];
|
|
44
|
+
const MONGO_AUTHENTICATION_TYPES = ['none', 'password', 'ldap', 'kerberos', 'x.509'];
|
|
45
|
+
/** `Query.RawQueryParameterPattern` birebir: `{{ name }}` ve `@name` (`::cast`, `@@`, e-posta ve string içi hariç). */
|
|
46
|
+
const RAW_QUERY_PARAMETER_PATTERN = /\{\{\s*([A-Za-z_][A-Za-z0-9_]*)\s*\}\}|(?<![:@\w'])@([A-Za-z_][A-Za-z0-9_]*)/g;
|
|
47
|
+
function asObjects(value) {
|
|
48
|
+
return (Array.isArray(value) ? value : []).filter((entry) => Boolean(entry) && typeof entry === 'object' && !Array.isArray(entry));
|
|
49
|
+
}
|
|
50
|
+
function asObject(value) {
|
|
51
|
+
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
|
|
52
|
+
}
|
|
53
|
+
function hasText(value) {
|
|
54
|
+
return typeof value === 'string' && value.trim() !== '';
|
|
55
|
+
}
|
|
56
|
+
function isBlankValue(value) {
|
|
57
|
+
return value === undefined || value === null || (typeof value === 'string' && value.trim() === '');
|
|
58
|
+
}
|
|
59
|
+
function isEmptyGuid(value) {
|
|
60
|
+
return typeof value === 'string' && (value.trim() === '' || value === EMPTY_GUID);
|
|
61
|
+
}
|
|
62
|
+
function isPresent(value) {
|
|
63
|
+
return value !== undefined && value !== null;
|
|
64
|
+
}
|
|
65
|
+
/** FluentValidation `{PropertyName}` görünümü: `rawQuery` → `Raw Query`, `fieldIds` → `Field Ids`. */
|
|
66
|
+
function displayName(path) {
|
|
67
|
+
const last = path.split('.').pop()?.replace(/\[\d*\]$/, '') ?? path;
|
|
68
|
+
return last.replace(/([a-z0-9])([A-Z])/g, '$1 $2').replace(/^./, first => first.toUpperCase());
|
|
69
|
+
}
|
|
70
|
+
function locate(node, segments, label, into) {
|
|
71
|
+
const head = segments[0];
|
|
72
|
+
if (head === undefined) {
|
|
73
|
+
into.push({ path: label, value: node });
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
const rest = segments.slice(1);
|
|
77
|
+
const isArray = head.endsWith('[]');
|
|
78
|
+
const key = isArray ? head.slice(0, -2) : head;
|
|
79
|
+
const record = asObject(node);
|
|
80
|
+
// Üst düğüm yoksa kural susar — backend de `When(x is not null)` ile aynı şeyi yapar.
|
|
81
|
+
if (record === undefined)
|
|
82
|
+
return;
|
|
83
|
+
const value = record[key];
|
|
84
|
+
const childLabel = label === '' ? key : `${label}.${key}`;
|
|
85
|
+
if (!isArray) {
|
|
86
|
+
locate(value, rest, childLabel, into);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (!Array.isArray(value))
|
|
90
|
+
return;
|
|
91
|
+
value.forEach((entry, index) => locate(entry, rest, `${childLabel}[${index}]`, into));
|
|
92
|
+
}
|
|
93
|
+
function flattenFilters(filters) {
|
|
94
|
+
const flat = [];
|
|
95
|
+
const pending = [...asObjects(filters)];
|
|
96
|
+
while (pending.length > 0) {
|
|
97
|
+
const filter = pending.shift();
|
|
98
|
+
flat.push(filter);
|
|
99
|
+
pending.push(...asObjects(filter.children));
|
|
100
|
+
}
|
|
101
|
+
return flat;
|
|
102
|
+
}
|
|
103
|
+
/** `FilterType` enum'unun ilk değeri `single`; alan yazılmamışsa backend onu single okur. */
|
|
104
|
+
function isSingleFilter(filter) {
|
|
105
|
+
return filter.filterType === undefined || filter.filterType === null || String(filter.filterType).toLowerCase() === 'single';
|
|
106
|
+
}
|
|
107
|
+
function flattenJoins(joins) {
|
|
108
|
+
const flat = [];
|
|
109
|
+
const pending = [...asObjects(joins)];
|
|
110
|
+
while (pending.length > 0) {
|
|
111
|
+
const join = pending.shift();
|
|
112
|
+
flat.push(join);
|
|
113
|
+
pending.push(...asObjects(join.joins));
|
|
114
|
+
}
|
|
115
|
+
return flat;
|
|
116
|
+
}
|
|
117
|
+
function duplicateKeys(values) {
|
|
118
|
+
const counts = new Map();
|
|
119
|
+
for (const value of values)
|
|
120
|
+
counts.set(value, (counts.get(value) ?? 0) + 1);
|
|
121
|
+
return [...counts.entries()].filter(([, count]) => count > 1).map(([value]) => value);
|
|
122
|
+
}
|
|
123
|
+
function outOfDictionary(label, value, dictionary) {
|
|
124
|
+
if (!isPresent(value))
|
|
125
|
+
return [];
|
|
126
|
+
return dictionary.includes(String(value))
|
|
127
|
+
? []
|
|
128
|
+
: [`${label}: Please use a valid value for '${label}': ${dictionary.join(', ')}. Received: '${String(value)}'. — platform tanımadığı enum metnini 400 InvalidEnumTokenException ile reddeder (StringEnumConverter.ReadJson).`];
|
|
129
|
+
}
|
|
130
|
+
export function nameCharacterFindings(label, value) {
|
|
131
|
+
if (!hasText(value))
|
|
132
|
+
return [`${label}: '${displayName(label)}' must not be empty.`];
|
|
133
|
+
const found = [...FORBIDDEN_NAME_CHARACTERS].filter(character => value.includes(character));
|
|
134
|
+
return found.length === 0
|
|
135
|
+
? []
|
|
136
|
+
: [`${label}: '${value}' şu karakterleri içeremez: ${found.map(character => `'${character}'`).join(' ')} — yasak küme \\|!#~$%&/=?»«@£§€{};'<>, (StringExtensions.IsContainsSpecialChar). Platform 422 döner.`];
|
|
137
|
+
}
|
|
138
|
+
/**
|
|
139
|
+
* Tek satır sözlüğünün backend kuralı (Create/Update/Bulk `Row` validator'ları): boş olamaz,
|
|
140
|
+
* boş anahtar taşıyamaz, bütün değerleri null olamaz. `UpdateRowCommand` id'yi de satırda arar
|
|
141
|
+
* ama bu doğrulayıcı değil handler işidir; burada yalnız validator aynası var.
|
|
142
|
+
*/
|
|
143
|
+
export function rowShapeFindings(label, row) {
|
|
144
|
+
const record = asObject(row);
|
|
145
|
+
if (record === undefined || Object.keys(record).length === 0) {
|
|
146
|
+
return [`${label}: '${displayName(label)}' must not be empty. — satır sözlüğü boş; alan SLUG'ı → değer çiftleri verin.`];
|
|
147
|
+
}
|
|
148
|
+
const findings = [];
|
|
149
|
+
const blankKeys = Object.keys(record).filter(key => key.trim() === '');
|
|
150
|
+
if (blankKeys.length > 0)
|
|
151
|
+
findings.push(`${label}: boş (whitespace) anahtar var — her anahtar bir alan slug'ı olmalı.`);
|
|
152
|
+
if (Object.values(record).every(value => value === null || value === undefined)) {
|
|
153
|
+
findings.push(`${label}: bütün değerler null — en az bir alana değer yazılmalı (backend: !Values.All(null)).`);
|
|
154
|
+
}
|
|
155
|
+
return findings;
|
|
156
|
+
}
|
|
157
|
+
export const fieldLimits = [
|
|
158
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'name', required: true, maxLength: 255, sourceRef: 'CreateEntityCommandValidator: RuleFor(Name).NotEmpty().MaximumLength(255)' },
|
|
159
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'description', maxLength: 1024, sourceRef: 'CreateEntityCommandValidator: RuleFor(Description).MaximumLength(1024)' },
|
|
160
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'slug', required: true, sourceRef: 'CreateEntityCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
161
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'fields[].name', maxLength: 50, sourceRef: 'FieldDtoValidator: RuleFor(Name).MaximumLength(50)' },
|
|
162
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'fields[].description', maxLength: 512, sourceRef: 'FieldDtoValidator: RuleFor(Description).MaximumLength(512)' },
|
|
163
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'fields[].slug', required: true, sourceRef: 'FieldDtoValidator: RuleFor(Slug).NotEmpty()' },
|
|
164
|
+
{ ruleId: 'ENT-015', command: 'CreateEntityCommand', path: 'fields[].dbType', required: true, maxLength: 50, sourceRef: 'FieldDtoValidator: RuleFor(DbType).NotEmpty().MaximumLength(50)' },
|
|
165
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'name', required: true, maxLength: 255, sourceRef: 'UpdateEntityCommandValidator: RuleFor(Name).NotEmpty().MaximumLength(255)' },
|
|
166
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'description', maxLength: 1024, sourceRef: 'UpdateEntityCommandValidator: RuleFor(Description).MaximumLength(1024)' },
|
|
167
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'fields[].name', maxLength: 50, sourceRef: 'FieldDtoValidator: RuleFor(Name).MaximumLength(50)' },
|
|
168
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'fields[].description', maxLength: 512, sourceRef: 'FieldDtoValidator: RuleFor(Description).MaximumLength(512)' },
|
|
169
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'fields[].slug', required: true, sourceRef: 'FieldDtoValidator: RuleFor(Slug).NotEmpty()' },
|
|
170
|
+
{ ruleId: 'ENT-015', command: 'UpdateEntityCommand', path: 'fields[].dbType', required: true, maxLength: 50, sourceRef: 'FieldDtoValidator: RuleFor(DbType).NotEmpty().MaximumLength(50)' },
|
|
171
|
+
{ ruleId: 'MS-017', command: 'CreateMicroserviceCommand', path: 'name', required: true, sourceRef: 'CreateMicroserviceCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
172
|
+
{ ruleId: 'MS-017', command: 'CreateMicroserviceCommand', path: 'url', required: true, sourceRef: 'CreateMicroserviceCommandValidator: RuleFor(Url).NotEmpty()' },
|
|
173
|
+
{ ruleId: 'MS-017', command: 'UpdateMicroserviceCommand', path: 'name', required: true, sourceRef: 'UpdateMicroserviceCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
174
|
+
{ ruleId: 'MS-017', command: 'UpdateMicroserviceCommand', path: 'slug', required: true, sourceRef: 'UpdateMicroserviceCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
175
|
+
{ ruleId: 'MS-017', command: 'UpdateMicroserviceCommand', path: 'url', required: true, sourceRef: 'UpdateMicroserviceCommandValidator: RuleFor(Url).NotEmpty()' },
|
|
176
|
+
{ ruleId: 'WF-026', command: 'CreateWorkflowCommand', path: 'name', required: true, sourceRef: 'CreateWorkflowCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
177
|
+
{ ruleId: 'WF-026', command: 'CreateWorkflowCommand', path: 'slug', required: true, sourceRef: 'CreateWorkflowCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
178
|
+
{ ruleId: 'WF-026', command: 'UpdateWorkflowCommand', path: 'name', required: true, sourceRef: 'UpdateWorkflowCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
179
|
+
{ ruleId: 'WF-026', command: 'UpdateWorkflowCommand', path: 'slug', required: true, sourceRef: 'UpdateWorkflowCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
180
|
+
{ ruleId: 'WF-026', command: 'PreviewWorkflowCommand', path: 'name', required: true, sourceRef: 'PreviewWorkflowCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
181
|
+
{ ruleId: 'WF-026', command: 'PreviewWorkflowCommand', path: 'slug', required: true, sourceRef: 'PreviewWorkflowCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
182
|
+
{ ruleId: 'QRY-001', command: 'CreateQueryCommand', path: 'slug', required: true, sourceRef: 'CreateQueryCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
183
|
+
{ ruleId: 'QRY-001', command: 'CreateQueryCommand', path: 'rawQuery', required: true, sourceRef: 'CreateQueryCommandValidator: RuleFor(RawQuery).NotEmpty()' },
|
|
184
|
+
{ ruleId: 'QRY-001', command: 'CreateQueryCommand', path: 'datasourceId', required: true, guid: true, sourceRef: 'CreateQueryCommandValidator: RuleFor(DatasourceId).NotEmpty().NotEqual(Guid.Empty)' },
|
|
185
|
+
{ ruleId: 'QRY-001', command: 'UpdateQueryCommand', path: 'slug', required: true, sourceRef: 'UpdateQueryCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
186
|
+
{ ruleId: 'QRY-001', command: 'UpdateQueryCommand', path: 'rawQuery', required: true, sourceRef: 'UpdateQueryCommandValidator: RuleFor(RawQuery).NotEmpty()' },
|
|
187
|
+
{ ruleId: 'QRY-001', command: 'UpdateQueryCommand', path: 'datasourceId', required: true, guid: true, sourceRef: 'UpdateQueryCommandValidator: RuleFor(DatasourceId).NotEmpty().NotEqual(Guid.Empty)' },
|
|
188
|
+
{ ruleId: 'QRY-001', command: 'PreviewQueryCommand', path: 'rawQuery', required: true, sourceRef: 'PreviewQueryCommandValidator: RuleFor(RawQuery).NotEmpty()' },
|
|
189
|
+
{ ruleId: 'QRY-001', command: 'PreviewQueryCommand', path: 'parameters[].name', required: true, sourceRef: 'PreviewQueryCommandValidator: RuleForEach(Parameters).ChildRules(Name.NotEmpty())' },
|
|
190
|
+
{ ruleId: 'VIEW-001', command: 'CreateViewCommand', path: 'name', required: true, sourceRef: 'CreateViewCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
191
|
+
{ ruleId: 'VIEW-001', command: 'UpdateViewCommand', path: 'name', required: true, sourceRef: 'UpdateViewCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
192
|
+
{ ruleId: 'DTO-001', command: 'CreateDataTransferObjectCommand', path: 'name', required: true, sourceRef: 'CreateDataTransferObjectCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
193
|
+
{ ruleId: 'DTO-001', command: 'CreateDataTransferObjectCommand', path: 'slug', required: true, sourceRef: 'CreateDataTransferObjectCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
194
|
+
{ ruleId: 'DTO-001', command: 'UpdateDataTransferObjectCommand', path: 'name', required: true, sourceRef: 'UpdateDataTransferObjectCommandValidator: RuleFor(Name).NotEmpty()' },
|
|
195
|
+
{ ruleId: 'DTO-001', command: 'UpdateDataTransferObjectCommand', path: 'slug', required: true, sourceRef: 'UpdateDataTransferObjectCommandValidator: RuleFor(Slug).NotEmpty()' },
|
|
196
|
+
{ ruleId: 'DS-001', command: 'CreateDatasourceCommand', path: 'name', required: true, maxLength: 50, sourceRef: 'CreateDatasourceCommandValidator: RuleFor(Name).NotNull().NotEmpty().MaximumLength(50)' },
|
|
197
|
+
{ ruleId: 'DS-001', command: 'CreateDatasourceCommand', path: 'description', maxLength: 255, sourceRef: 'CreateDatasourceCommandValidator: RuleFor(Description).MaximumLength(255)' },
|
|
198
|
+
{ ruleId: 'DS-001', command: 'CreateDatasourceCommand', path: 'dbName', required: true, maxLength: 63, sourceRef: 'CreateDatasourceCommandValidator: RuleFor(DbName).NotNull().NotEmpty().MaximumLength(63)' },
|
|
199
|
+
{ ruleId: 'DS-001', command: 'CreateDatasourceCommand', path: 'host', required: true, maxLength: 255, sourceRef: 'CreateDatasourceCommandValidator: RuleFor(Host).NotNull().NotEmpty().MaximumLength(255)' },
|
|
200
|
+
{ ruleId: 'DS-001', command: 'CreateDatasourceCommand', path: 'port', range: [0, 65535], sourceRef: 'CreateDatasourceCommandValidator: RuleFor(Port).Must(0 ≤ p ≤ 65535)' },
|
|
201
|
+
{ ruleId: 'DS-001', command: 'UpdateDatasourceCommand', path: 'name', required: true, maxLength: 50, sourceRef: 'UpdateDatasourceCommandValidator: RuleFor(Name).NotNull().NotEmpty().MaximumLength(50)' },
|
|
202
|
+
{ ruleId: 'DS-001', command: 'UpdateDatasourceCommand', path: 'description', maxLength: 255, sourceRef: 'UpdateDatasourceCommandValidator: RuleFor(Description).MaximumLength(255)' },
|
|
203
|
+
{ ruleId: 'DS-001', command: 'UpdateDatasourceCommand', path: 'dbName', required: true, maxLength: 63, sourceRef: 'UpdateDatasourceCommandValidator: RuleFor(DbName).NotNull().NotEmpty().MaximumLength(63)' },
|
|
204
|
+
{ ruleId: 'DS-001', command: 'UpdateDatasourceCommand', path: 'host', required: true, maxLength: 255, sourceRef: 'UpdateDatasourceCommandValidator: RuleFor(Host).NotNull().NotEmpty().MaximumLength(255)' },
|
|
205
|
+
{ ruleId: 'DS-001', command: 'UpdateDatasourceCommand', path: 'port', range: [0, 65535], sourceRef: 'UpdateDatasourceCommandValidator: RuleFor(Port).Must(0 ≤ p ≤ 65535)' },
|
|
206
|
+
{ ruleId: 'FLD-001', command: 'CreateFolderCommand', path: 'name', required: true, sourceRef: 'CreateFolderCommandValidator: RuleFor(Name).NotEmpty().Must(IsContainsSpecialChar)' }
|
|
207
|
+
];
|
|
208
|
+
function limitMessages(limit, located) {
|
|
209
|
+
const { path, value } = located;
|
|
210
|
+
const label = displayName(path);
|
|
211
|
+
const messages = [];
|
|
212
|
+
const empty = isBlankValue(value) || (Array.isArray(value) && value.length === 0) || (limit.guid === true && isEmptyGuid(value));
|
|
213
|
+
if (limit.required && empty) {
|
|
214
|
+
messages.push(`${path}: '${label}' must not be empty.`);
|
|
215
|
+
return messages;
|
|
216
|
+
}
|
|
217
|
+
if (typeof value === 'string') {
|
|
218
|
+
if (limit.maxLength !== undefined && value.length > limit.maxLength) {
|
|
219
|
+
messages.push(`${path}: The length of '${label}' must be ${limit.maxLength} characters or fewer. You entered ${value.length} characters. (${value.length - limit.maxLength} karakter fazla)`);
|
|
220
|
+
}
|
|
221
|
+
if (limit.minLength !== undefined && value.length < limit.minLength) {
|
|
222
|
+
messages.push(`${path}: The length of '${label}' must be at least ${limit.minLength} characters. You entered ${value.length} characters.`);
|
|
223
|
+
}
|
|
224
|
+
if (limit.pattern !== undefined && value !== '') {
|
|
225
|
+
try {
|
|
226
|
+
if (!new RegExp(limit.pattern).test(value))
|
|
227
|
+
messages.push(`${path}: '${label}' is not in the correct format. — beklenen desen: ${limit.pattern}`);
|
|
228
|
+
}
|
|
229
|
+
catch {
|
|
230
|
+
// .NET'e özgü bir desen JS'te derlenmezse denetim atlanır; karar platforma kalır.
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
if (typeof value === 'number' && limit.range !== undefined && (value < limit.range[0] || value > limit.range[1])) {
|
|
235
|
+
messages.push(`${path}: '${label}' must be between ${limit.range[0]} and ${limit.range[1]}. You entered ${value}.`);
|
|
236
|
+
}
|
|
237
|
+
if (Array.isArray(value) && limit.minItems !== undefined && value.length < limit.minItems) {
|
|
238
|
+
messages.push(`${path}: '${label}' must contain at least ${limit.minItems} item(s). You entered ${value.length}.`);
|
|
239
|
+
}
|
|
240
|
+
return messages;
|
|
241
|
+
}
|
|
242
|
+
/**
|
|
243
|
+
* Önlem: `EntityIndexDtoValidator` ad uzunluğunu ÖNEK HARİÇ ölçer (`NameWithoutGeneratedPrefix`,
|
|
244
|
+
* `OverridePropertyName(Name)`). MicroElements 6.1.0 bu kuralı `name`'e YAZMIYOR (ölçüldü
|
|
245
|
+
* 2026-09-08: EntityIndexDto.name yalnız minLength 1) — bir sürüm yazarsa gidiş-dönüşte
|
|
246
|
+
* `IX_<5 hane>_` öneki taşıyan 41 karakterlik ad yanlış bloklanır. Öneki düşerek ölçen ENT-013
|
|
247
|
+
* doğru kuraldır; şemanın o satırı her hâlükârda atlanır.
|
|
248
|
+
*/
|
|
249
|
+
const ignoredSchemaLimits = [
|
|
250
|
+
{ command: /EntityCommand$/, path: 'indexes[].name', kind: 'maxLength' }
|
|
251
|
+
];
|
|
252
|
+
const bulkRowCommands = ['CreateBulkRowCommand', 'UpdateBulkRowCommand'];
|
|
253
|
+
function ownerRuleId(command) {
|
|
254
|
+
if (bulkRowCommands.includes(command))
|
|
255
|
+
return 'ROW-002';
|
|
256
|
+
return backendRules.find(rule => Object.values(rule.commands).includes(command))?.id;
|
|
257
|
+
}
|
|
258
|
+
function fromSchema(command, limit, ruleId) {
|
|
259
|
+
const range = limit.minimum !== undefined || limit.maximum !== undefined
|
|
260
|
+
? [limit.minimum ?? Number.NEGATIVE_INFINITY, limit.maximum ?? Number.POSITIVE_INFINITY]
|
|
261
|
+
: undefined;
|
|
262
|
+
return {
|
|
263
|
+
ruleId,
|
|
264
|
+
command,
|
|
265
|
+
path: limit.path,
|
|
266
|
+
...(limit.required ? { required: true } : {}),
|
|
267
|
+
...(limit.maxLength !== undefined ? { maxLength: limit.maxLength } : {}),
|
|
268
|
+
...(limit.minLength !== undefined ? { minLength: limit.minLength } : {}),
|
|
269
|
+
...(range !== undefined ? { range } : {}),
|
|
270
|
+
...(limit.pattern !== undefined ? { pattern: limit.pattern } : {}),
|
|
271
|
+
...(limit.minItems !== undefined ? { minItems: limit.minItems } : {}),
|
|
272
|
+
sourceRef: `ngapps://schemas/${command} (swagger ← FluentValidation)`
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
/**
|
|
276
|
+
* Bir komutun geçerli sınır listesi: paketlenmiş swagger şemasındaki kısıtlar (backend'in kendi
|
|
277
|
+
* doğrulayıcısından aktığı için birincil) + el yazımı katalog (şemada olmayan yolu tamamlar; aynı
|
|
278
|
+
* yolda şema kazanır). Şema yenilenmemişse liste yalnız katalogtan oluşur — iki kaynak aynı ihlali
|
|
279
|
+
* iki kez raporlamaz.
|
|
280
|
+
*/
|
|
281
|
+
export function effectiveLimits(command) {
|
|
282
|
+
const owner = ownerRuleId(command) ?? 'SCHEMA';
|
|
283
|
+
const merged = new Map();
|
|
284
|
+
for (const limit of schemaLimits(command)) {
|
|
285
|
+
const ignored = ignoredSchemaLimits.some(entry => entry.command.test(command) && entry.path === limit.path);
|
|
286
|
+
const kept = ignored ? Object.fromEntries(Object.entries(limit).filter(([key]) => !ignoredSchemaLimits.some(entry => entry.command.test(command) && entry.path === limit.path && entry.kind === key))) : limit;
|
|
287
|
+
if (Object.keys(kept).length > 1)
|
|
288
|
+
merged.set(kept.path, fromSchema(command, kept, owner));
|
|
289
|
+
}
|
|
290
|
+
for (const catalog of fieldLimits.filter(entry => entry.command === command)) {
|
|
291
|
+
const existing = merged.get(catalog.path);
|
|
292
|
+
if (existing === undefined) {
|
|
293
|
+
merged.set(catalog.path, catalog);
|
|
294
|
+
continue;
|
|
295
|
+
}
|
|
296
|
+
merged.set(catalog.path, {
|
|
297
|
+
...existing,
|
|
298
|
+
ruleId: catalog.ruleId,
|
|
299
|
+
...(existing.required === undefined && catalog.required ? { required: true } : {}),
|
|
300
|
+
...(catalog.guid ? { guid: true } : {}),
|
|
301
|
+
...(existing.maxLength === undefined && catalog.maxLength !== undefined ? { maxLength: catalog.maxLength } : {}),
|
|
302
|
+
...(existing.minLength === undefined && catalog.minLength !== undefined ? { minLength: catalog.minLength } : {}),
|
|
303
|
+
...(existing.range === undefined && catalog.range !== undefined ? { range: catalog.range } : {}),
|
|
304
|
+
sourceRef: `${existing.sourceRef} · ${catalog.sourceRef}`
|
|
305
|
+
});
|
|
306
|
+
}
|
|
307
|
+
return [...merged.values()];
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Bir komutun bütün uzunluk/zorunluluk sınırlarını gövdeye uygular; mesajlar FluentValidation'ın
|
|
311
|
+
* İngilizce cümleleridir (ajan 422'de aynısını görürdü). `ruleId` verilirse yalnız o kurala bağlı
|
|
312
|
+
* sınırlar koşar — aynı komutu paylaşan kurallar (ENT-015…018) aynı ihlali dört kez raporlamasın.
|
|
313
|
+
*/
|
|
314
|
+
export function limitFindings(command, body, ruleId) {
|
|
315
|
+
const findings = [];
|
|
316
|
+
for (const limit of effectiveLimits(command).filter(entry => ruleId === undefined || entry.ruleId === ruleId)) {
|
|
317
|
+
const located = [];
|
|
318
|
+
locate(body, limit.path.split('.'), '', located);
|
|
319
|
+
for (const entry of located)
|
|
320
|
+
findings.push(...limitMessages(limit, entry));
|
|
321
|
+
}
|
|
322
|
+
return findings;
|
|
323
|
+
}
|
|
324
|
+
function entityFieldSetFindings(body, operation) {
|
|
325
|
+
const fields = asObjects(body.fields);
|
|
326
|
+
if (!Array.isArray(body.fields))
|
|
327
|
+
return [];
|
|
328
|
+
const findings = [];
|
|
329
|
+
const primaryKeys = fields.filter(field => field.primaryKey === true);
|
|
330
|
+
if (primaryKeys.length !== 1) {
|
|
331
|
+
findings.push(`fields: Please select exactly one primary key. — primaryKey:true olan alan sayısı ${primaryKeys.length}; tam 1 olmalı. Kalıp: { slug:'id', name:'Id', dbType:'UUID', commonType:'guid', primaryKey:true, unique:true, identity:true, nullable:false, properties:{ generationType: 0 } }.`);
|
|
332
|
+
}
|
|
333
|
+
// Create yolu adı `name` üzerinden, update yolu `slug` üzerinden tekil arar. Create'te `name`
|
|
334
|
+
// yazılmayan iki alan aynı (boş) gruba düşer ve 422 "Duplicate value 'Slug'" gelir — mesaj
|
|
335
|
+
// slug'ı suçlar, sebep ad eksikliğidir.
|
|
336
|
+
const keyField = operation === 'create' ? 'name' : 'slug';
|
|
337
|
+
const keys = fields.map(field => (typeof field[keyField] === 'string' ? field[keyField] : '').toLowerCase());
|
|
338
|
+
const duplicates = duplicateKeys(keys);
|
|
339
|
+
if (duplicates.length > 0) {
|
|
340
|
+
const shown = duplicates.map(key => key === '' ? `(${keyField} yazılmamış)` : `'${key}'`).join(', ');
|
|
341
|
+
findings.push(`fields: Duplicate value: 'Slug'. — ${operation === 'create' ? "create yolu alanları 'name' üzerinden" : "update yolu alanları 'slug' üzerinden"} büyük/küçük harf duyarsız tekil ister; çakışan: ${shown}.${operation === 'create' && duplicates.includes('') ? " Create'te her alana 'name' yazın; name'siz iki alan aynı boş gruba düşer ve platformun mesajı slug'ı suçlar." : ''}`);
|
|
342
|
+
}
|
|
343
|
+
return findings;
|
|
344
|
+
}
|
|
345
|
+
function entityFieldFlagFindings(body) {
|
|
346
|
+
const findings = [];
|
|
347
|
+
asObjects(body.fields).forEach((field, index) => {
|
|
348
|
+
const label = `fields[${index}]${hasText(field.slug) ? ` ('${field.slug}')` : ''}`;
|
|
349
|
+
const commonType = isPresent(field.commonType) ? String(field.commonType) : 'text';
|
|
350
|
+
findings.push(...outOfDictionary(`${label}.commonType`, field.commonType, COMMON_FIELD_TYPES));
|
|
351
|
+
findings.push(...outOfDictionary(`${label}.defaultValueType`, field.defaultValueType, DEFAULT_VALUE_TYPES));
|
|
352
|
+
findings.push(...outOfDictionary(`${label}.relationType`, field.relationType, RELATION_TYPES));
|
|
353
|
+
if (field.primaryKey === true) {
|
|
354
|
+
if (field.nullable === true)
|
|
355
|
+
findings.push(`${label}: primaryKey:true olan alan nullable:true olamaz ('Nullable' must be equal to 'False').`);
|
|
356
|
+
if (field.hidden === true)
|
|
357
|
+
findings.push(`${label}: primaryKey:true olan alan hidden:true olamaz ('Hidden' must be equal to 'False').`);
|
|
358
|
+
}
|
|
359
|
+
if (field.identity === true && field.nullable === true) {
|
|
360
|
+
findings.push(`${label}: identity:true olan alan nullable:true olamaz ('Nullable' must be equal to 'False').`);
|
|
361
|
+
}
|
|
362
|
+
if (field.isLocalized === true) {
|
|
363
|
+
const offending = ['primaryKey', 'identity', 'unique'].filter(flag => field[flag] === true);
|
|
364
|
+
if (offending.length > 0)
|
|
365
|
+
findings.push(`${label}: isLocalized:true olan alan ${offending.join('/')} taşıyamaz — çeviri satırı yan entity'de tutulur, kısıt oraya gider.`);
|
|
366
|
+
if (isPresent(field.relationType) && String(field.relationType).toLowerCase() !== 'none') {
|
|
367
|
+
findings.push(`${label}: isLocalized:true olan alan ilişki olamaz (relationType 'none' olmalı).`);
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
if (isPresent(field.folderId) && !isEmptyGuid(field.folderId) && commonType !== 'file') {
|
|
371
|
+
findings.push(`${label}: folderId yalnız commonType:'file' alanında verilir ('Folder Id' must be empty); bu alan '${commonType}'.`);
|
|
372
|
+
}
|
|
373
|
+
const properties = asObject(field.properties);
|
|
374
|
+
const hasGenerationType = properties !== undefined && Object.keys(properties).some(key => key.toLowerCase() === 'generationtype');
|
|
375
|
+
if (hasGenerationType && commonType !== 'guid' && commonType !== 'number') {
|
|
376
|
+
findings.push(`${label}: properties.generationType yalnız commonType 'guid' ya da 'number' alanında geçerli; bu alan '${commonType}'. Otomatik üretim istemiyorsanız anahtarı kaldırın.`);
|
|
377
|
+
}
|
|
378
|
+
});
|
|
379
|
+
return findings;
|
|
380
|
+
}
|
|
381
|
+
function entityTransferFindings(body) {
|
|
382
|
+
const findings = [];
|
|
383
|
+
const mode = isPresent(body.recordTransferMode) ? String(body.recordTransferMode).toLowerCase() : undefined;
|
|
384
|
+
const ids = Array.isArray(body.transferRowIds) ? body.transferRowIds : undefined;
|
|
385
|
+
if (mode !== undefined && !['all', 'specific'].includes(mode)) {
|
|
386
|
+
findings.push(...outOfDictionary('recordTransferMode', body.recordTransferMode, ['all', 'specific']));
|
|
387
|
+
}
|
|
388
|
+
if (mode === 'specific' && (ids === undefined || ids.length === 0)) {
|
|
389
|
+
findings.push("transferRowIds: Transfer records are required. — recordTransferMode 'specific' iken taşınacak satır id listesi boş olamaz.");
|
|
390
|
+
}
|
|
391
|
+
if (ids !== undefined) {
|
|
392
|
+
const strings = ids.map(id => typeof id === 'string' ? id : String(id ?? ''));
|
|
393
|
+
const duplicates = duplicateKeys(strings);
|
|
394
|
+
if (duplicates.length > 0)
|
|
395
|
+
findings.push(`transferRowIds: Duplicate value: 'Transfer Row Id' — tekrar eden: ${duplicates.join(', ')}.`);
|
|
396
|
+
if (strings.some(id => id.trim() === ''))
|
|
397
|
+
findings.push("transferRowIds: 'Transfer Row Ids' must not be empty. — boş id var.");
|
|
398
|
+
}
|
|
399
|
+
return findings;
|
|
400
|
+
}
|
|
401
|
+
function guidReferenceFindings(body, keys) {
|
|
402
|
+
return keys
|
|
403
|
+
.filter(key => isPresent(body[key]) && isEmptyGuid(body[key]))
|
|
404
|
+
.map(key => `${key}: '${displayName(key)}' must not be empty / must not be equal to '${EMPTY_GUID}'. — alan verilecekse gerçek bir GUID olmalı; vermeyecekseniz anahtarı hiç yazmayın.`);
|
|
405
|
+
}
|
|
406
|
+
function microserviceFindings(body) {
|
|
407
|
+
const findings = guidReferenceFindings(body, ['queryId', 'entityId', 'workflowId']);
|
|
408
|
+
const parameters = asObject(body.sourceParameters);
|
|
409
|
+
if (parameters === undefined)
|
|
410
|
+
return findings;
|
|
411
|
+
const operation = isPresent(parameters.operation) ? String(parameters.operation).toLowerCase() : undefined;
|
|
412
|
+
asObjects(parameters.fields).forEach((field, index) => {
|
|
413
|
+
if (String(field.source ?? '').toLowerCase() === 'expression' && isBlankValue(field.value)) {
|
|
414
|
+
findings.push(`sourceParameters.fields[${index}]: source:'expression' olan alanın 'value' ifadesi boş — ifade kaynağı değer ister (CreateMicroserviceCommandValidator: Fields.Where(Expression).Must(Value dolu)).`);
|
|
415
|
+
}
|
|
416
|
+
if (body.source === 'table' && operation !== 'upsert' && isPresent(field.writeOn) && String(field.writeOn).toLowerCase() !== 'always') {
|
|
417
|
+
findings.push(`sourceParameters.fields[${index}]: writeOn:'${String(field.writeOn)}' yalnız operation:'upsert' ucunda anlamlı; bu uç '${operation ?? '?'}' — writeOn'u kaldırın ya da 'always' bırakın (Fields.All(WriteOn == Always) when Operation != Upsert).`);
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
flattenFilters(parameters.filters).forEach((filter, index) => {
|
|
421
|
+
if (!isSingleFilter(filter))
|
|
422
|
+
return;
|
|
423
|
+
if (String(filter.source ?? '').toLowerCase() === 'expression' && isBlankValue(filter.value)) {
|
|
424
|
+
findings.push(`sourceParameters.filters (düz sıra ${index}): source:'expression' olan filtrenin 'value' ifadesi boş — ifade kaynağı değer ister.`);
|
|
425
|
+
}
|
|
426
|
+
if (isBlankValue(filter.fieldId) || isEmptyGuid(filter.fieldId)) {
|
|
427
|
+
findings.push(`sourceParameters.filters (düz sıra ${index}): tekil filtrede 'fieldId' zorunlu ve gerçek bir alan GUID'i olmalı (Filters.Where(Single).All(FieldId dolu)); alan id'lerini entity_get view:'outline' ile okuyun.`);
|
|
428
|
+
}
|
|
429
|
+
});
|
|
430
|
+
if (body.source === 'table') {
|
|
431
|
+
flattenJoins(parameters.joins).forEach((join, index) => {
|
|
432
|
+
const missing = ['entityId', 'fieldId'].filter(key => isBlankValue(join[key]) || isEmptyGuid(join[key]));
|
|
433
|
+
if (missing.length > 0)
|
|
434
|
+
findings.push(`sourceParameters.joins (düz sıra ${index}): ${missing.join(' ve ')} boş — join tanımı eksik (TableSourceJoinIncomplete, 422). Alias kuralı MS-005'te.`);
|
|
435
|
+
});
|
|
436
|
+
}
|
|
437
|
+
return findings;
|
|
438
|
+
}
|
|
439
|
+
function workflowFindings(body, operation) {
|
|
440
|
+
const findings = [];
|
|
441
|
+
const type = String(body.type ?? '').toLowerCase();
|
|
442
|
+
for (const [dtoKey, behaviorKey] of [['returnDtoId', 'returnBehavior'], ['parameterDtoId', 'parameterBehavior']]) {
|
|
443
|
+
if (isPresent(body[dtoKey])) {
|
|
444
|
+
if (isEmptyGuid(body[dtoKey]))
|
|
445
|
+
findings.push(`${dtoKey}: '${displayName(dtoKey)}' must not be equal to '${EMPTY_GUID}'.`);
|
|
446
|
+
if (isBlankValue(body[behaviorKey]))
|
|
447
|
+
findings.push(`${behaviorKey}: '${displayName(behaviorKey)}' must not be empty. — ${dtoKey} verilen yerde ${behaviorKey} da zorunlu: primitive | object | array.`);
|
|
448
|
+
else
|
|
449
|
+
findings.push(...outOfDictionary(behaviorKey, body[behaviorKey], DTO_BEHAVIORS));
|
|
450
|
+
}
|
|
451
|
+
else if (operation !== 'create' && isPresent(body[behaviorKey])) {
|
|
452
|
+
findings.push(`${behaviorKey}: '${displayName(behaviorKey)}' must be empty. — ${dtoKey} yokken ${behaviorKey} null olmalı (Update/Preview doğrulayıcısı).`);
|
|
453
|
+
}
|
|
454
|
+
}
|
|
455
|
+
if (operation !== 'create' && type === 'workflow' && !isPresent(body.parameterDtoId) && isPresent(body.previewPayload)) {
|
|
456
|
+
findings.push("previewPayload: Preview payload requires a parameter DTO. — type:'workflow' akışta parameterDtoId yokken previewPayload verilemez; girdi DTO'sunu bağlayın ya da alanı kaldırın (PreviewPayloadRequiresParameterDto).");
|
|
457
|
+
}
|
|
458
|
+
if (operation !== 'preview') {
|
|
459
|
+
asObjects(body.steps).forEach((step, index) => {
|
|
460
|
+
const rule = asObject(step.rule);
|
|
461
|
+
const stepLabel = `steps[${index}]${hasText(step.name) ? ` ('${step.name}')` : ''}`;
|
|
462
|
+
if (String(step.type ?? '').toLowerCase() === 'variable' && rule !== undefined && String(rule.name ?? '').toLowerCase() === 'user') {
|
|
463
|
+
findings.push(`${stepLabel}: 'user' adı variable adımına verilemez — set-user adımına ayrılmıştır (Steps.Must(!Variable && Name == "user")). Başka bir ad seçin ya da set-user kullanın.`);
|
|
464
|
+
}
|
|
465
|
+
if (rule !== undefined) {
|
|
466
|
+
flattenFilters(rule.filters).forEach((filter, filterIndex) => {
|
|
467
|
+
if (isSingleFilter(filter) && (isBlankValue(filter.fieldId) || isEmptyGuid(filter.fieldId))) {
|
|
468
|
+
findings.push(`${stepLabel}.rule.filters (düz sıra ${filterIndex}): tekil filtrede 'fieldId' zorunlu ve gerçek bir GUID olmalı; alan id'lerini entity_get view:'outline' ile okuyun.`);
|
|
469
|
+
}
|
|
470
|
+
});
|
|
471
|
+
}
|
|
472
|
+
});
|
|
473
|
+
if (type === 'automation') {
|
|
474
|
+
if (isPresent(body.parameterDtoId))
|
|
475
|
+
findings.push("parameterDtoId: 'Parameter Dto Id' must be empty. — automation girdi DTO'su almaz; olayı tetikleyen satır girdidir.");
|
|
476
|
+
const automation = asObject(body.automation);
|
|
477
|
+
if (automation !== undefined && automation.isQueue === true) {
|
|
478
|
+
const order = automation.queueOrder;
|
|
479
|
+
if (typeof order !== 'number' || Number.isNaN(order))
|
|
480
|
+
findings.push("automation.queueOrder: 'Queue Order' must not be empty. — isQueue:true iken queueOrder (0 ya da pozitif tam sayı) zorunlu.");
|
|
481
|
+
else if (order < 0)
|
|
482
|
+
findings.push(`automation.queueOrder: 'Queue Order' must be greater than or equal to '0'. You entered ${order}.`);
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
return findings;
|
|
487
|
+
}
|
|
488
|
+
export function rawQueryParameterNames(rawQuery) {
|
|
489
|
+
if (typeof rawQuery !== 'string')
|
|
490
|
+
return [];
|
|
491
|
+
const names = new Map();
|
|
492
|
+
for (const match of rawQuery.matchAll(RAW_QUERY_PARAMETER_PATTERN)) {
|
|
493
|
+
const name = match[1] ?? match[2];
|
|
494
|
+
if (name !== undefined && !names.has(name.toLowerCase()))
|
|
495
|
+
names.set(name.toLowerCase(), name);
|
|
496
|
+
}
|
|
497
|
+
return [...names.values()];
|
|
498
|
+
}
|
|
499
|
+
function queryFindings(body, operation) {
|
|
500
|
+
const findings = outOfDictionary('outputType', body.outputType, QUERY_OUTPUT_TYPES);
|
|
501
|
+
if (operation !== 'create' || !hasText(body.rawQuery))
|
|
502
|
+
return findings;
|
|
503
|
+
const declared = new Set(asObjects(body.parameters).map(parameter => String(parameter.name ?? '').toLowerCase()));
|
|
504
|
+
const missing = rawQueryParameterNames(body.rawQuery).filter(name => !declared.has(name.toLowerCase()));
|
|
505
|
+
if (missing.length > 0) {
|
|
506
|
+
findings.push(`parameters: Query parameters are missing: ${missing.map(name => `'${name}'`).join(', ')}. — rawQuery'deki her @ad / {{ad}} 'parameters' listesinde { name, type } ile bildirilmeli (yalnız create yolu denetler; update sessiz geçer, çalışma anında düşer).`);
|
|
507
|
+
}
|
|
508
|
+
return findings;
|
|
509
|
+
}
|
|
510
|
+
function viewFindings(body) {
|
|
511
|
+
return outOfDictionary('providerType', body.providerType, VIEW_PROVIDER_TYPES);
|
|
512
|
+
}
|
|
513
|
+
function dtoChildrenFindings(children, label, findings) {
|
|
514
|
+
if (!Array.isArray(children))
|
|
515
|
+
return;
|
|
516
|
+
const nodes = asObjects(children);
|
|
517
|
+
const duplicates = duplicateKeys(nodes.map(child => String(child.slug ?? '').toLowerCase()).filter(slug => slug !== ''));
|
|
518
|
+
if (duplicates.length > 0)
|
|
519
|
+
findings.push(`${label}: Duplicate value: 'Slug' — kardeş alanlar arasında tekrar eden: ${duplicates.join(', ')} (büyük/küçük harf duyarsız).`);
|
|
520
|
+
nodes.forEach((child, index) => {
|
|
521
|
+
const childLabel = `${label}[${index}]${hasText(child.slug) ? ` ('${child.slug}')` : ''}`;
|
|
522
|
+
if (!hasText(child.slug))
|
|
523
|
+
findings.push(`${childLabel}: çocuk alanın 'slug'ı zorunlu (DataTransferObjectChildSlugRequired).`);
|
|
524
|
+
findings.push(...outOfDictionary(`${childLabel}.type`, child.type, DTO_TYPES));
|
|
525
|
+
if (isPresent(child.referenceId) && !['object', 'array'].includes(String(child.type ?? '').toLowerCase())) {
|
|
526
|
+
findings.push(`${childLabel}: referenceId (başka bir DTO'ya bağ) yalnız type 'object' ya da 'array' ile verilir; bu alan '${String(child.type ?? '?')}'.`);
|
|
527
|
+
}
|
|
528
|
+
dtoChildrenFindings(child.children, `${childLabel}.children`, findings);
|
|
529
|
+
});
|
|
530
|
+
}
|
|
531
|
+
function dtoFindings(body) {
|
|
532
|
+
const findings = outOfDictionary('type', body.type, DTO_TYPES);
|
|
533
|
+
dtoChildrenFindings(body.children, 'children', findings);
|
|
534
|
+
return findings;
|
|
535
|
+
}
|
|
536
|
+
function rowFindings(body) {
|
|
537
|
+
if (Array.isArray(body.rows)) {
|
|
538
|
+
return body.rows.flatMap((row, index) => rowShapeFindings(`rows[${index}]`, row));
|
|
539
|
+
}
|
|
540
|
+
return rowShapeFindings('row', body.row);
|
|
541
|
+
}
|
|
542
|
+
function datasourceFindings(body) {
|
|
543
|
+
const type = String(body.type ?? '').toLowerCase();
|
|
544
|
+
const properties = asObject(body.properties) ?? {};
|
|
545
|
+
const findings = [];
|
|
546
|
+
if (type === 'postgresql') {
|
|
547
|
+
for (const [key, max] of [['initialDatabase', 63], ['userName', 63], ['password', 1024]]) {
|
|
548
|
+
if (!hasText(properties[key]))
|
|
549
|
+
findings.push(`properties.${key}: '${displayName(key)}' must not be empty. — postgresql datasource'unda zorunlu (PostgreSqlPropertyValidator).`);
|
|
550
|
+
else if (properties[key].length > max)
|
|
551
|
+
findings.push(`properties.${key}: The length of '${displayName(key)}' must be ${max} characters or fewer. You entered ${properties[key].length} characters.`);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
if (type === 'mongodb') {
|
|
555
|
+
const authenticationType = isPresent(properties.authenticationType) ? String(properties.authenticationType) : undefined;
|
|
556
|
+
findings.push(...outOfDictionary('properties.authenticationType', authenticationType, MONGO_AUTHENTICATION_TYPES));
|
|
557
|
+
if (authenticationType === 'password') {
|
|
558
|
+
for (const [key, max] of [['authenticationDatabase', 63], ['userName', 63], ['password', 1024]]) {
|
|
559
|
+
if (!hasText(properties[key]))
|
|
560
|
+
findings.push(`properties.${key}: '${displayName(key)}' must not be empty. — mongodb + authenticationType 'password' iken zorunlu (MongoDbPropertyValidator).`);
|
|
561
|
+
else if (properties[key].length > max)
|
|
562
|
+
findings.push(`properties.${key}: The length of '${displayName(key)}' must be ${max} characters or fewer. You entered ${properties[key].length} characters.`);
|
|
563
|
+
}
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
return findings;
|
|
567
|
+
}
|
|
568
|
+
const MICROFUNCTION_ENTRYPOINT_PATTERN = /^[A-Za-z_][\w./-]*\.[A-Za-z_]\w*$/;
|
|
569
|
+
const MICROFUNCTION_ENVIRONMENT_KEY_PATTERN = /^[A-Z_][A-Z0-9_]*$/;
|
|
570
|
+
const MICROFUNCTION_ROLE_ARN_PATTERN = /^arn:aws:iam::\d{12}:role\/.+$/;
|
|
571
|
+
/**
|
|
572
|
+
* `MicrofunctionOptions.Limits` ile de sınırlanır (kök `.env`); buradaki değerler
|
|
573
|
+
* `MicrofunctionConstants`'ın sabit tavanıdır — ayar daha dar bir tavan koyduysa platform onu
|
|
574
|
+
* uygular ve gateway'in geçirdiği gövde 422 döner. Ters yön olmaz: buradan geçmeyen gövde
|
|
575
|
+
* platformda da geçmez.
|
|
576
|
+
*/
|
|
577
|
+
function microfunctionFindings(body) {
|
|
578
|
+
const provider = isPresent(body.provider) ? String(body.provider) : 'built-in';
|
|
579
|
+
const sourceKind = isPresent(body.sourceKind) ? String(body.sourceKind) : 'inline';
|
|
580
|
+
const findings = [];
|
|
581
|
+
if (provider === 'azure-functions') {
|
|
582
|
+
findings.push("provider: 'azure-functions' v1'de seçilemez (MicrofunctionProviderNotSupported). Enum değeri tanımlı ama connector'ı yok — 'built-in' (platformun kendi Fission kümesi) ya da 'aws-lambda' kullanın.");
|
|
583
|
+
}
|
|
584
|
+
if (provider === 'aws-lambda' && !isPresent(body.runtimeId)) {
|
|
585
|
+
findings.push("runtimeId: provider 'aws-lambda' iken zorunlu (MicrofunctionRuntimeRequired) — fonksiyonun hangi AWS hesabında koşacağı kayıttan gelir. microfunction_runtime_list ile hesabı seçin.");
|
|
586
|
+
}
|
|
587
|
+
if (provider === 'built-in' && isPresent(body.runtimeId)) {
|
|
588
|
+
findings.push("runtimeId: provider 'built-in' iken VERİLMEZ (MicrofunctionRuntimeNotAllowedForBuiltIn) — Built-In fonksiyon platformun kendi kümesinde koşar, bulut hesabı kaydı yoktur.");
|
|
589
|
+
}
|
|
590
|
+
if (sourceKind === 'inline' && !hasText(body.code)) {
|
|
591
|
+
findings.push("code: sourceKind 'inline' iken zorunlu (MicrofunctionCodeRequired).");
|
|
592
|
+
}
|
|
593
|
+
if (sourceKind === 'archive' && !(hasText(body.entrypoint) && MICROFUNCTION_ENTRYPOINT_PATTERN.test(body.entrypoint))) {
|
|
594
|
+
findings.push(`entrypoint: sourceKind 'archive' iken 'modül.fonksiyon' biçiminde zorunlu (MicrofunctionEntrypointInvalid, desen ${MICROFUNCTION_ENTRYPOINT_PATTERN.source}), ör. 'handlers/sum.handler'.`);
|
|
595
|
+
}
|
|
596
|
+
// Komut alanları non-nullable int: gönderilmezse gövdeye 0 gider ve platform 422 döner. Gateway
|
|
597
|
+
// değer UYDURMAZ (0 ile 30 arasındaki fark faturalandırmayı da davranışı da değiştirir), yalnız
|
|
598
|
+
// eksikliği çağrıdan önce söyler.
|
|
599
|
+
const timeoutCeiling = provider === 'aws-lambda' ? 900 : 300;
|
|
600
|
+
if (!isPresent(body.timeoutSeconds)) {
|
|
601
|
+
findings.push(`timeoutSeconds: alan non-nullable int — gönderilmezse 0 gider ve platform 422 döner (MicrofunctionTimeoutOutOfRange). 1–${timeoutCeiling} arası bir değer verin (ürün varsayılanı 30).`);
|
|
602
|
+
}
|
|
603
|
+
else if (Number(body.timeoutSeconds) < 1 || Number(body.timeoutSeconds) > timeoutCeiling) {
|
|
604
|
+
findings.push(`timeoutSeconds: ${String(body.timeoutSeconds)} — provider '${provider}' için geçerli aralık 1–${timeoutCeiling} (MicrofunctionTimeoutOutOfRange).`);
|
|
605
|
+
}
|
|
606
|
+
if (!isPresent(body.memoryMb)) {
|
|
607
|
+
findings.push('memoryMb: alan non-nullable int — gönderilmezse 0 gider ve platform 422 döner (MicrofunctionMemoryOutOfRange). 128–3008 arası bir değer verin (ürün varsayılanı 256).');
|
|
608
|
+
}
|
|
609
|
+
const environmentVariables = asObject(body.environmentVariables);
|
|
610
|
+
if (environmentVariables !== undefined) {
|
|
611
|
+
const invalidKeys = Object.keys(environmentVariables).filter(key => !MICROFUNCTION_ENVIRONMENT_KEY_PATTERN.test(key));
|
|
612
|
+
const bytes = Object.entries(environmentVariables)
|
|
613
|
+
.reduce((total, [key, value]) => total + Buffer.byteLength(key, 'utf8') + Buffer.byteLength(value === null || value === undefined ? '' : String(value), 'utf8'), 0);
|
|
614
|
+
if (invalidKeys.length > 0) {
|
|
615
|
+
findings.push(`environmentVariables: anahtarlar ${MICROFUNCTION_ENVIRONMENT_KEY_PATTERN.source} desenine uymalı (MicrofunctionEnvironmentVariableKeyInvalid) — uymayanlar: ${invalidKeys.join(', ')}.`);
|
|
616
|
+
}
|
|
617
|
+
if (bytes > 4096) {
|
|
618
|
+
findings.push(`environmentVariables: anahtar+değer toplamı ${bytes} bayt, üst sınır 4096 (Lambda sınırı; MicrofunctionEnvironmentVariablesTooLarge).`);
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
return findings;
|
|
622
|
+
}
|
|
623
|
+
function microfunctionRuntimeFindings(body) {
|
|
624
|
+
const findings = [];
|
|
625
|
+
if (isPresent(body.provider) && String(body.provider) !== 'aws-lambda') {
|
|
626
|
+
findings.push(`provider: runtime hesabı v1'de yalnız 'aws-lambda' olabilir (MicrofunctionProviderNotSupported); '${String(body.provider)}' reddedilir. Built-In fonksiyonların runtime kaydı YOKTUR.`);
|
|
627
|
+
}
|
|
628
|
+
if (!(hasText(body.roleArn) && MICROFUNCTION_ROLE_ARN_PATTERN.test(body.roleArn))) {
|
|
629
|
+
findings.push(`roleArn: zorunlu ve ${MICROFUNCTION_ROLE_ARN_PATTERN.source} desenine uymalı (MicrofunctionRuntimeRoleArnInvalid) — fonksiyonun koşacağı Lambda execution rolü, ör. 'arn:aws:iam::123456789012:role/zeyrek-microfunction'.`);
|
|
630
|
+
}
|
|
631
|
+
return findings;
|
|
632
|
+
}
|
|
633
|
+
function projectCreateFindings(body) {
|
|
634
|
+
if (!isPresent(body.templateOptions) && !isPresent(body.application)) {
|
|
635
|
+
return ["application: 'Application' must not be empty. — templateOptions verilmeyen projede ilk application projeyle birlikte açılır: application { slug, name } zorunlu."];
|
|
636
|
+
}
|
|
637
|
+
return [];
|
|
638
|
+
}
|
|
639
|
+
function languageFindings(label, codes) {
|
|
640
|
+
const unknown = codes.filter(code => !SUPPORTED_LANGUAGES.includes(code));
|
|
641
|
+
return unknown.length === 0
|
|
642
|
+
? []
|
|
643
|
+
: [`${label}: ${unknown.map(code => `'${code}'`).join(', ')} desteklenen dil listesinde yok — ISO 639-1 iki harfli küçük kod bekleniyor (tr, en, de, fr, …; tam liste ProjectConstants.SupportedLanguages, 184 kod). 'tr-TR' gibi bölge ekli kodlar reddedilir.`];
|
|
644
|
+
}
|
|
645
|
+
function projectUpdateFindings(body) {
|
|
646
|
+
const findings = [];
|
|
647
|
+
const languages = Array.isArray(body.languages) ? body.languages.map(String) : undefined;
|
|
648
|
+
if (languages !== undefined) {
|
|
649
|
+
if (languages.length === 0)
|
|
650
|
+
findings.push("languages: en az bir dil verilmeli (Languages.Count > 0).");
|
|
651
|
+
findings.push(...languageFindings('languages', languages));
|
|
652
|
+
}
|
|
653
|
+
if (hasText(body.defaultLanguage) && languages !== undefined && !languages.includes(body.defaultLanguage)) {
|
|
654
|
+
findings.push(`defaultLanguage: '${body.defaultLanguage}' languages listesinde değil — varsayılan dil listenin elemanı olmalı.`);
|
|
655
|
+
}
|
|
656
|
+
return findings;
|
|
657
|
+
}
|
|
658
|
+
function translationFindings(body) {
|
|
659
|
+
const findings = [];
|
|
660
|
+
const keys = asObjects(body.keys);
|
|
661
|
+
const duplicates = duplicateKeys(keys.map(entry => String(entry.key ?? '')));
|
|
662
|
+
if (duplicates.length > 0)
|
|
663
|
+
findings.push(`keys: aynı anahtar birden çok kez var: ${duplicates.map(key => `'${key}'`).join(', ')} — her anahtar tek satır (Keys.Select(Key).Distinct().Count() == Count).`);
|
|
664
|
+
keys.forEach((entry, index) => {
|
|
665
|
+
if (!hasText(entry.key))
|
|
666
|
+
findings.push(`keys[${index}].key: boş olamaz.`);
|
|
667
|
+
const values = asObject(entry.values);
|
|
668
|
+
if (values === undefined)
|
|
669
|
+
findings.push(`keys[${index}].values: dil kodu → metin sözlüğü zorunlu.`);
|
|
670
|
+
else
|
|
671
|
+
findings.push(...languageFindings(`keys[${index}].values`, Object.keys(values)));
|
|
672
|
+
});
|
|
673
|
+
if (Array.isArray(body.deletedKeys) && body.deletedKeys.some(key => !hasText(key)))
|
|
674
|
+
findings.push('deletedKeys: boş anahtar silinemez.');
|
|
675
|
+
return findings;
|
|
676
|
+
}
|
|
677
|
+
function externalComponentFindings(body) {
|
|
678
|
+
const findings = [];
|
|
679
|
+
if (isPresent(body.protocolVersion) && body.protocolVersion !== EXTERNAL_COMPONENT_PROTOCOL_VERSION) {
|
|
680
|
+
findings.push(`protocolVersion: ${String(body.protocolVersion)} desteklenmiyor — platform yalnız ${EXTERNAL_COMPONENT_PROTOCOL_VERSION} kabul eder (ExternalComponentProtocolUnsupported).`);
|
|
681
|
+
}
|
|
682
|
+
const definition = asObject(body.definition);
|
|
683
|
+
if (definition === undefined)
|
|
684
|
+
return [...findings, "definition: 'Definition' must not be empty. — manifest'te definition { blocks, props } zorunlu."];
|
|
685
|
+
if (!Array.isArray(definition.blocks) || definition.blocks.length === 0) {
|
|
686
|
+
findings.push('definition.blocks: en az bir blok zorunlu (ExternalComponentBlocksRequired).');
|
|
687
|
+
}
|
|
688
|
+
asObjects(definition.props).forEach((prop, index) => {
|
|
689
|
+
const name = typeof prop.name === 'string' ? prop.name : '';
|
|
690
|
+
const label = `definition.props[${index}]${name !== '' ? ` ('${name}')` : ''}`;
|
|
691
|
+
if (name === '')
|
|
692
|
+
findings.push(`${label}.name: boş olamaz.`);
|
|
693
|
+
else if (EXTERNAL_COMPONENT_RESERVED_PROPS.includes(name) || name.startsWith('zx') || name.startsWith('on')) {
|
|
694
|
+
findings.push(`${label}.name: '${name}' ayrılmış prop adı — DOM/host adları (${EXTERNAL_COMPONENT_RESERVED_PROPS.join(', ')}) ile 'zx'/'on' önekleri kullanılamaz (ExternalComponentPropNameReserved).`);
|
|
695
|
+
}
|
|
696
|
+
findings.push(...outOfDictionary(`${label}.type`, prop.type, EXTERNAL_COMPONENT_PROP_TYPES));
|
|
697
|
+
});
|
|
698
|
+
return findings;
|
|
699
|
+
}
|
|
700
|
+
export const backendRules = [
|
|
701
|
+
{
|
|
702
|
+
id: 'ENT-015',
|
|
703
|
+
module: 'entity',
|
|
704
|
+
commands: { create: 'CreateEntityCommand', update: 'UpdateEntityCommand' },
|
|
705
|
+
status: 422,
|
|
706
|
+
summary: '',
|
|
707
|
+
sourceRef: 'CreateEntityCommandValidator / UpdateEntityCommandValidator / FieldDtoValidator uzunluk ve NotEmpty kuralları (fieldLimits). Ölçüm: 2026-09-08 3. koşu, index adı 33 karakter → 422',
|
|
708
|
+
guide: 'ngapps://guides/entity'
|
|
709
|
+
},
|
|
710
|
+
{
|
|
711
|
+
id: 'ENT-016',
|
|
712
|
+
module: 'entity',
|
|
713
|
+
commands: { create: 'CreateEntityCommand', update: 'UpdateEntityCommand' },
|
|
714
|
+
status: 422,
|
|
715
|
+
summary: "tam 1 primaryKey · alan adları tekil (create 'name', update 'slug' üzerinden, büyük/küçük harf duyarsız)",
|
|
716
|
+
sourceRef: "CreateEntityCommandValidator: Fields.Count(PrimaryKey) == 1 (PrimaryKeyRequiredException 'Please select exactly one primary key.'); Fields.GroupBy(Name.ToLowerInvariant()).All(Count ≤ 1) (DuplicateValue 'Slug'); UpdateEntityCommandValidator aynı kural Slug üzerinden. Ölçüm: 2026-09-08 3. koşu entity_create 422 (hata #14, #15)",
|
|
717
|
+
guide: 'ngapps://guides/entity',
|
|
718
|
+
check: (body, _command, operation) => entityFieldSetFindings(body, operation)
|
|
719
|
+
},
|
|
720
|
+
{
|
|
721
|
+
id: 'ENT-017',
|
|
722
|
+
module: 'entity',
|
|
723
|
+
commands: { create: 'CreateEntityCommand', update: 'UpdateEntityCommand' },
|
|
724
|
+
status: 422,
|
|
725
|
+
summary: "primaryKey ⇒ nullable:false + hidden:false · identity ⇒ nullable:false · isLocalized ⇒ pk/identity/unique/relation yok · folderId yalnız file · properties.generationType yalnız guid/number · commonType/defaultValueType/relationType sözlükten",
|
|
726
|
+
sourceRef: 'FieldDtoValidator: When(Identity) Nullable.Equal(false); When(PrimaryKey) Nullable/Hidden.Equal(false); When(IsLocalized) PrimaryKey/Identity/Unique.Equal(false) + RelationType.Equal(None); When(CommonType != File) FolderId.Empty(); When(CommonType ∉ {Guid, Number}) Properties.GenerationType yok; CommonType/DefaultValueType.IsInEnum()',
|
|
727
|
+
guide: 'ngapps://guides/entity',
|
|
728
|
+
check: body => entityFieldFlagFindings(body)
|
|
729
|
+
},
|
|
730
|
+
{
|
|
731
|
+
id: 'ENT-018',
|
|
732
|
+
module: 'entity',
|
|
733
|
+
commands: { create: 'CreateEntityCommand', update: 'UpdateEntityCommand' },
|
|
734
|
+
status: 422,
|
|
735
|
+
summary: "recordTransferMode 'specific' ⇒ transferRowIds dolu, tekil, boşsuz",
|
|
736
|
+
sourceRef: 'CreateEntityCommandValidator / UpdateEntityCommandValidator: When(RecordTransferMode == Specific) TransferRowIds.NotEmpty() (TransferRecordsRequired); When(TransferRowIds != null) tekrarsız + RuleForEach NotEmpty',
|
|
737
|
+
guide: 'ngapps://guides/entity',
|
|
738
|
+
check: body => entityTransferFindings(body)
|
|
739
|
+
},
|
|
740
|
+
{
|
|
741
|
+
id: 'MS-017',
|
|
742
|
+
module: 'microservice',
|
|
743
|
+
commands: { create: 'CreateMicroserviceCommand', update: 'UpdateMicroserviceCommand' },
|
|
744
|
+
status: 422,
|
|
745
|
+
summary: "queryId/entityId/workflowId verilirse boş GUID olamaz · source:'expression' alan/filtrede value dolu · tekil filtrede fieldId · writeOn yalnız upsert'te · join'de entityId+fieldId",
|
|
746
|
+
sourceRef: 'CreateMicroserviceCommandValidator / UpdateMicroserviceCommandValidator: Name/Url NotEmpty (update: Slug da); QueryId/EntityId/WorkflowId NotEqual(Guid.Empty); SourceParameters.Fields/Filters.Where(Source == Expression).Must(Value dolu); Filters.Where(Single).All(FieldId dolu); Table && Operation != Upsert ⇒ Fields.All(WriteOn == Always); Joins alias+entityId+fieldId (TableSourceJoinIncomplete). enableDynamicFilters kümesi MS-010, alias MS-005, selectFields MS-006',
|
|
747
|
+
guide: 'ngapps://guides/microservice',
|
|
748
|
+
check: body => microserviceFindings(body)
|
|
749
|
+
},
|
|
750
|
+
{
|
|
751
|
+
id: 'WF-026',
|
|
752
|
+
module: 'workflow',
|
|
753
|
+
commands: { create: 'CreateWorkflowCommand', update: 'UpdateWorkflowCommand', preview: 'PreviewWorkflowCommand' },
|
|
754
|
+
status: 422,
|
|
755
|
+
summary: "returnDtoId ⇒ returnBehavior, parameterDtoId ⇒ parameterBehavior (primitive|object|array); update/preview'da DTO yokken behavior null · type:'workflow' + parameterDtoId yok ⇒ previewPayload yok · variable adımı 'user' adını alamaz · tekil filtrede fieldId · automation: parameterDtoId yok, isQueue ⇒ queueOrder ≥ 0",
|
|
756
|
+
sourceRef: 'CreateWorkflowCommandValidator / UpdateWorkflowCommandValidator / PreviewWorkflowCommandValidator: Name/Slug NotEmpty; When(ReturnDtoId.HasValue) ReturnBehavior NotEmpty (update/preview: IsInEnum, null ⇒ Null()); ParameterDtoId aynı; When(ParameterDtoId null && Type == Workflow) PreviewPayload.Null() (PreviewPayloadRequiresParameterDto); Steps.Must(!Variable && Rule.Name == "user"); RuleForEach(Steps) Rule.Filters single ⇒ FieldId; When(Automation) ParameterDtoId.Empty(); AutomationValidation: When(IsQueue) QueueOrder NotEmpty + ≥ 0. Start adımı/graf WF-001, response yasağı WF-005, isQueue/operation/returnDtoId WF-010',
|
|
757
|
+
guide: 'ngapps://guides/workflow',
|
|
758
|
+
check: (body, _command, operation) => workflowFindings(body, operation)
|
|
759
|
+
},
|
|
760
|
+
{
|
|
761
|
+
id: 'QRY-001',
|
|
762
|
+
module: 'query',
|
|
763
|
+
commands: { create: 'CreateQueryCommand', update: 'UpdateQueryCommand', preview: 'PreviewQueryCommand' },
|
|
764
|
+
status: 422,
|
|
765
|
+
summary: "rawQuery'deki her @ad / {{ad}} parameters[]'ta bildirilir (create) · outputType array|object · preview parametrelerinde name zorunlu",
|
|
766
|
+
sourceRef: "CreateQueryCommandValidator: Slug/RawQuery NotEmpty, DatasourceId NotEqual(Guid.Empty), Parameters.Must(RawQueryParameterNames ⊆ Parameters.Name) (QueryParametersMissing); UpdateQueryCommandValidator aynı, parametre denetimi YOK; PreviewQueryCommandValidator: RawQuery NotEmpty, Parameters[].Name NotEmpty; OutputType.IsInEnum(). Desen Query.RawQueryParameterPattern",
|
|
767
|
+
guide: 'ngapps://guides/microservice',
|
|
768
|
+
check: (body, _command, operation) => queryFindings(body, operation)
|
|
769
|
+
},
|
|
770
|
+
{
|
|
771
|
+
id: 'VIEW-001',
|
|
772
|
+
module: 'view',
|
|
773
|
+
commands: { create: 'CreateViewCommand', update: 'UpdateViewCommand' },
|
|
774
|
+
status: 422,
|
|
775
|
+
summary: "providerType 'postgres'",
|
|
776
|
+
sourceRef: 'CreateViewCommandValidator / UpdateViewCommandValidator: Name NotEmpty, ProviderType.IsInEnum() (tek değer: postgres)',
|
|
777
|
+
guide: 'ngapps://guides/view',
|
|
778
|
+
check: body => viewFindings(body)
|
|
779
|
+
},
|
|
780
|
+
{
|
|
781
|
+
id: 'DTO-001',
|
|
782
|
+
module: 'dto',
|
|
783
|
+
commands: { create: 'CreateDataTransferObjectCommand', update: 'UpdateDataTransferObjectCommand' },
|
|
784
|
+
status: 422,
|
|
785
|
+
summary: 'type sözlükten (object|array|bool|string|number|float|guid|date|time|datetime|file|anonymous) · children[].slug zorunlu ve kardeşler arasında tekil · referenceId yalnız object/array çocukta',
|
|
786
|
+
sourceRef: 'CreateDataTransferObjectCommandValidator / UpdateDataTransferObjectCommandValidator: Name/Slug NotEmpty, Type.IsInEnum(), Children.GroupBy(Slug.ToLowerInvariant()).All(Count ≤ 1); DataTransferObjectDtoValidator (özyineli): Slug NotEmpty (DataTransferObjectChildSlugRequired), Type.IsInEnum(), When(ReferenceId) Type ∈ {Object, Array}',
|
|
787
|
+
guide: 'ngapps://guides/workflow',
|
|
788
|
+
check: body => dtoFindings(body)
|
|
789
|
+
},
|
|
790
|
+
{
|
|
791
|
+
id: 'ROW-002',
|
|
792
|
+
module: 'row',
|
|
793
|
+
commands: { create: 'CreateRowCommand', update: 'UpdateRowCommand' },
|
|
794
|
+
status: 422,
|
|
795
|
+
summary: 'satır sözlüğü boş olamaz, boş anahtar taşıyamaz, bütün değerleri null olamaz (toplu yolda her satır için)',
|
|
796
|
+
sourceRef: 'CreateRowCommandValidator / UpdateRowCommandValidator / CreateBulkRowCommandValidator / UpdateBulkRowCommandValidator: Row NotEmpty, Keys.Count > 0, !Keys.Any(IsNullOrWhiteSpace), !Values.All(null)',
|
|
797
|
+
guide: 'ngapps://guides/entity',
|
|
798
|
+
commandFor: (body, operation) => Array.isArray(body.rows)
|
|
799
|
+
? (operation === 'create' ? 'CreateBulkRowCommand' : 'UpdateBulkRowCommand')
|
|
800
|
+
: (operation === 'create' ? 'CreateRowCommand' : 'UpdateRowCommand'),
|
|
801
|
+
check: body => rowFindings(body)
|
|
802
|
+
},
|
|
803
|
+
{
|
|
804
|
+
id: 'DS-001',
|
|
805
|
+
module: 'datasource',
|
|
806
|
+
commands: { create: 'CreateDatasourceCommand', update: 'UpdateDatasourceCommand' },
|
|
807
|
+
status: 422,
|
|
808
|
+
summary: "type postgresql|mongodb · postgresql ⇒ properties.initialDatabase ≤63, userName ≤63, password ≤1024 (üçü zorunlu) · mongodb + authenticationType 'password' ⇒ authenticationDatabase/userName/password",
|
|
809
|
+
sourceRef: 'CreateDatasourceCommandValidator / UpdateDatasourceCommandValidator (fieldLimits) + PostgreSqlPropertyValidator + MongoDbPropertyValidator (authenticationType ∈ MongoDbAuthenticationType.Values)',
|
|
810
|
+
check: body => datasourceFindings(body)
|
|
811
|
+
},
|
|
812
|
+
{
|
|
813
|
+
id: 'PRJ-001',
|
|
814
|
+
module: 'project',
|
|
815
|
+
commands: { create: 'CreateOrganizationProjectCommand' },
|
|
816
|
+
status: 409,
|
|
817
|
+
summary: "templateOptions yoksa application zorunlu · slug organizasyonda tekil (veritabanı adı zeyrek_<org>_<slug>; çakışma eski build'de 500, 2026-09-08 sonrası 409)",
|
|
818
|
+
sourceRef: "CreateOrganizationProjectCommandValidator: When(TemplateOptions null) Application.NotNull(); OrganizationProjectService.CreateAsync: ExistsByOrganizationAndDatabaseNameAsync ⇒ DatabaseNameAlreadyExistsException (ExceptionHandler eşlemesi 2026-09-08'de 409'a alındı). Gateway POST'tan önce project_list ile slug'ı arar. Ölçüm: 2026-09-08 3. koşu S-1 / P3-7",
|
|
819
|
+
check: body => projectCreateFindings(body)
|
|
820
|
+
},
|
|
821
|
+
{
|
|
822
|
+
id: 'PRJ-002',
|
|
823
|
+
module: 'project',
|
|
824
|
+
commands: { update: 'UpdateOrganizationProjectCommand' },
|
|
825
|
+
status: 422,
|
|
826
|
+
summary: 'languages ⊆ ISO 639-1 (184 kod), boş olamaz · defaultLanguage languages içinde',
|
|
827
|
+
sourceRef: 'UpdateOrganizationProjectCommandValidator: When(Languages != null) Count > 0 && All(ProjectConstants.SupportedLanguages.Contains); When(DefaultLanguage) Languages null || Languages.Contains(DefaultLanguage)',
|
|
828
|
+
check: body => projectUpdateFindings(body)
|
|
829
|
+
},
|
|
830
|
+
{
|
|
831
|
+
id: 'TR-001',
|
|
832
|
+
module: 'translation',
|
|
833
|
+
commands: { update: 'UpdateTranslationsCommand' },
|
|
834
|
+
status: 422,
|
|
835
|
+
summary: 'keys[].key boş olamaz ve tekil · values dil kodları ISO 639-1 · deletedKeys boş olamaz',
|
|
836
|
+
sourceRef: 'UpdateTranslationsCommandValidator: Keys.All(Key dolu), Keys.Select(Key).Distinct().Count() == Count, Keys.All(Values.Keys ⊆ SupportedLanguages), DeletedKeys.All(dolu)',
|
|
837
|
+
guide: 'ngapps://guides/translation',
|
|
838
|
+
check: body => translationFindings(body)
|
|
839
|
+
},
|
|
840
|
+
{
|
|
841
|
+
id: 'FLD-001',
|
|
842
|
+
module: 'folder',
|
|
843
|
+
commands: { create: 'CreateFolderCommand' },
|
|
844
|
+
status: 422,
|
|
845
|
+
summary: "name zorunlu ve \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez (dosya adı için de aynı küme: FILE-001)",
|
|
846
|
+
sourceRef: 'CreateFolderCommandValidator: Name.NotEmpty().Must(StringExtensions.IsContainsSpecialChar) — yardımcı, ad ne derse desin "yasak karakter YOK" demektir',
|
|
847
|
+
check: body => nameCharacterFindings('name', body.name)
|
|
848
|
+
},
|
|
849
|
+
{
|
|
850
|
+
id: 'FILE-001',
|
|
851
|
+
module: 'file',
|
|
852
|
+
commands: { create: 'UploadFileCommand' },
|
|
853
|
+
status: 422,
|
|
854
|
+
summary: "dosya adı \\|!#~$%&/=?»«@£§€{};'<>, karakterlerini içeremez",
|
|
855
|
+
sourceRef: 'UploadFileCommandValidator: File.NotNull(), File.Must(IsContainsSpecialChar(FileName))',
|
|
856
|
+
check: body => nameCharacterFindings('filename', body.filename)
|
|
857
|
+
},
|
|
858
|
+
{
|
|
859
|
+
id: 'MF-001',
|
|
860
|
+
module: 'microfunction',
|
|
861
|
+
commands: { create: 'CreateMicrofunctionCommand', update: 'UpdateMicrofunctionCommand' },
|
|
862
|
+
status: 422,
|
|
863
|
+
summary: "provider 'built-in' ⇒ runtimeId boş, 'aws-lambda' ⇒ runtimeId zorunlu, 'azure-functions' reddedilir · sourceKind 'inline' ⇒ code, 'archive' ⇒ 'modül.fonksiyon' biçiminde entrypoint · timeoutSeconds ve memoryMb GÖNDERİLMEK ZORUNDA (non-nullable int, eksikse 0 gider) · env anahtarları ^[A-Z_][A-Z0-9_]*$ ve toplam ≤ 4 KB",
|
|
864
|
+
sourceRef: 'CreateMicrofunctionCommandValidator / UpdateMicrofunctionCommandValidator (ikisi birebir aynı kural kümesi) + MicrofunctionConstants (EntrypointPattern, EnvironmentVariableKeyPattern, BuiltInMaxTimeoutSeconds 300, CloudMaxTimeoutSeconds 900, MinMemoryMb 128, MaxMemoryMb 3008, MaxEnvironmentVariablesBytes 4096)',
|
|
865
|
+
guide: 'ngapps://guides/microfunction',
|
|
866
|
+
check: body => microfunctionFindings(body)
|
|
867
|
+
},
|
|
868
|
+
{
|
|
869
|
+
id: 'MFR-001',
|
|
870
|
+
module: 'microfunctionRuntime',
|
|
871
|
+
commands: { create: 'CreateMicrofunctionRuntimeCommand', update: 'UpdateMicrofunctionRuntimeCommand' },
|
|
872
|
+
status: 422,
|
|
873
|
+
summary: "provider yalnız 'aws-lambda' · roleArn zorunlu ve arn:aws:iam::<12 hane>:role/<ad> desenli",
|
|
874
|
+
sourceRef: 'CreateMicrofunctionRuntimeCommandValidator / UpdateMicrofunctionRuntimeCommandValidator + MicrofunctionConstants.RoleArnPattern',
|
|
875
|
+
guide: 'ngapps://guides/microfunction',
|
|
876
|
+
check: body => microfunctionRuntimeFindings(body)
|
|
877
|
+
},
|
|
878
|
+
{
|
|
879
|
+
id: 'EXT-001',
|
|
880
|
+
module: 'externalComponent',
|
|
881
|
+
commands: { create: 'PublishExternalComponentCommand' },
|
|
882
|
+
status: 422,
|
|
883
|
+
summary: 'protocolVersion 1 · definition.blocks dolu · props[].name ayrılmış DOM adı ya da zx/on önekli olamaz · props[].type sözlükten',
|
|
884
|
+
sourceRef: 'PublishExternalComponentCommandValidator: ProtocolVersion.Must(SupportsProtocolVersion), Definition.Blocks NotEmpty, Props[].Name !IsReservedPropName, Props[].Type IsAllowedPropType (ExternalComponentRules). Slug deseni ve 2 MB paket sınırı tool\'da ayrıca denetlenir',
|
|
885
|
+
guide: 'ngapps://guides/external-component',
|
|
886
|
+
check: body => externalComponentFindings(body)
|
|
887
|
+
}
|
|
888
|
+
];
|
|
889
|
+
function ruleFindings(rule, body, operation) {
|
|
890
|
+
const command = rule.commandFor?.(body, operation) ?? rule.commands[operation];
|
|
891
|
+
if (command === undefined)
|
|
892
|
+
return [];
|
|
893
|
+
const messages = [...limitFindings(command, body, rule.id), ...(rule.check?.(body, command, operation) ?? [])];
|
|
894
|
+
return messages.map(message => ({
|
|
895
|
+
ruleId: rule.id,
|
|
896
|
+
severity: 'block',
|
|
897
|
+
message: `${message} [platform ${rule.status} ile reddederdi — ${command}]`,
|
|
898
|
+
...(rule.guide !== undefined ? { guide: rule.guide } : {})
|
|
899
|
+
}));
|
|
900
|
+
}
|
|
901
|
+
/**
|
|
902
|
+
* Her backend kuralı bir politika kuralı olarak `policyRules`'a katılır. Şiddet `block`: kural
|
|
903
|
+
* platformun kendi doğrulayıcısının aynasıdır, gövde gitseydi aynı red gelecekti; gidiş-dönüşü
|
|
904
|
+
* ve araştırma turunu kısaltmak dışında yeni bir yargı taşımaz. `policyOverride` / `POLICY=warn`
|
|
905
|
+
* kaçışları aynen geçerlidir — ayna yanlışsa istek yine gönderilebilir.
|
|
906
|
+
*/
|
|
907
|
+
export function backendPolicyRules() {
|
|
908
|
+
return backendRules.map(rule => ({
|
|
909
|
+
id: rule.id,
|
|
910
|
+
module: rule.module,
|
|
911
|
+
appliesTo: Object.keys(rule.commands),
|
|
912
|
+
sourceRef: rule.sourceRef,
|
|
913
|
+
check: (definition, context) => ruleFindings(rule, definition, context.operation)
|
|
914
|
+
}));
|
|
915
|
+
}
|
|
916
|
+
function limitLabel(limit) {
|
|
917
|
+
const parts = [limit.path];
|
|
918
|
+
if (limit.required)
|
|
919
|
+
parts.push('zorunlu');
|
|
920
|
+
if (limit.maxLength !== undefined)
|
|
921
|
+
parts.push(`≤${limit.maxLength}`);
|
|
922
|
+
if (limit.minLength !== undefined && limit.minLength > 1)
|
|
923
|
+
parts.push(`≥${limit.minLength}`);
|
|
924
|
+
if (limit.range !== undefined)
|
|
925
|
+
parts.push(`${Number.isFinite(limit.range[0]) ? limit.range[0] : '…'}–${Number.isFinite(limit.range[1]) ? limit.range[1] : '…'}`);
|
|
926
|
+
if (limit.pattern !== undefined)
|
|
927
|
+
parts.push(`desen ${limit.pattern}`);
|
|
928
|
+
if (limit.minItems !== undefined)
|
|
929
|
+
parts.push(`en az ${limit.minItems} eleman`);
|
|
930
|
+
return parts.join(' ');
|
|
931
|
+
}
|
|
932
|
+
/** Tool açıklamasına giren tek satır: ajan sınırı çağrıdan ÖNCE görür, 422 ile öğrenmez. */
|
|
933
|
+
export function constraintSummary(command) {
|
|
934
|
+
const rules = backendRules.filter(rule => Object.values(rule.commands).includes(command));
|
|
935
|
+
const limits = effectiveLimits(command).filter(limit => limitLabel(limit) !== limit.path).map(limitLabel);
|
|
936
|
+
const summaries = rules.map(rule => rule.summary).filter(summary => summary !== '');
|
|
937
|
+
const entries = [...limits, ...summaries];
|
|
938
|
+
if (entries.length === 0)
|
|
939
|
+
return '';
|
|
940
|
+
return `Backend ön kontrolü (${rules.map(rule => rule.id).join(' · ')}; block, istek gitmeden): ${entries.join(' · ')}. Tam katalog: ngapps://rules/backend-constraints.`;
|
|
941
|
+
}
|
|
942
|
+
export function constraintCatalog() {
|
|
943
|
+
return {
|
|
944
|
+
source: "zeyrek-backend FluentValidation doğrulayıcıları (Application/Contexts/**/*Command.cs, *Dto.cs) + ExceptionHandler eşlemesi; gateway'in politika lint'i aynı kuralı istek gitmeden uygular (block). Koşulsuz uzunluk/zorunluluk kuralları 2026-09-08'den beri swagger'dan (MicroElements.Swashbuckle.FluentValidation) paketlenmiş şemaya akar ve burada `sourceRef` 'swagger ← FluentValidation' olarak görünür; el yazımı satırlar şemanın ifade edemediği kuralları taşır.",
|
|
945
|
+
note: "Mesajlar platformun 422 gövdesindeki cümlelerdir; ID'ler zeyrek-mcp/docs/08-kural-envanteri.md'de. policyOverride: true ya da POLICY=warn kuralı warn'a indirir.",
|
|
946
|
+
rules: backendRules.map(rule => ({
|
|
947
|
+
id: rule.id,
|
|
948
|
+
module: rule.module,
|
|
949
|
+
status: rule.status,
|
|
950
|
+
commands: rule.commands,
|
|
951
|
+
summary: rule.summary,
|
|
952
|
+
limits: Object.values(rule.commands)
|
|
953
|
+
.flatMap(command => effectiveLimits(command).filter(limit => limit.ruleId === rule.id))
|
|
954
|
+
.map(limit => ({
|
|
955
|
+
command: limit.command,
|
|
956
|
+
path: limit.path,
|
|
957
|
+
...(limit.required ? { required: true } : {}),
|
|
958
|
+
...(limit.guid ? { guid: true } : {}),
|
|
959
|
+
...(limit.maxLength !== undefined ? { maxLength: limit.maxLength } : {}),
|
|
960
|
+
...(limit.minLength !== undefined ? { minLength: limit.minLength } : {}),
|
|
961
|
+
...(limit.range !== undefined ? { range: limit.range.map(bound => Number.isFinite(bound) ? bound : null) } : {}),
|
|
962
|
+
...(limit.pattern !== undefined ? { pattern: limit.pattern } : {}),
|
|
963
|
+
...(limit.minItems !== undefined ? { minItems: limit.minItems } : {}),
|
|
964
|
+
sourceRef: limit.sourceRef
|
|
965
|
+
})),
|
|
966
|
+
sourceRef: rule.sourceRef,
|
|
967
|
+
...(rule.guide !== undefined ? { guide: rule.guide } : {})
|
|
968
|
+
})),
|
|
969
|
+
dictionaries: {
|
|
970
|
+
supportedLanguages: SUPPORTED_LANGUAGES,
|
|
971
|
+
forbiddenNameCharacters: FORBIDDEN_NAME_CHARACTERS,
|
|
972
|
+
externalComponent: {
|
|
973
|
+
protocolVersion: EXTERNAL_COMPONENT_PROTOCOL_VERSION,
|
|
974
|
+
propTypes: EXTERNAL_COMPONENT_PROP_TYPES,
|
|
975
|
+
reservedProps: EXTERNAL_COMPONENT_RESERVED_PROPS,
|
|
976
|
+
reservedPrefixes: ['zx', 'on'],
|
|
977
|
+
slugPattern: '^x-[a-z][a-z0-9-]*$',
|
|
978
|
+
maxBundleBytes: 2 * 1024 * 1024
|
|
979
|
+
}
|
|
980
|
+
}
|
|
981
|
+
};
|
|
982
|
+
}
|