create-devoplus-one-app 0.0.0-stage → 0.1.0-next.158

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (122) hide show
  1. package/README.md +62 -2
  2. package/dist/cli.mjs +17 -0
  3. package/package.json +35 -3
  4. package/templates/0001_init.sql.tpl +10 -0
  5. package/templates/AGENTS.md.tpl +286 -0
  6. package/templates/AGENT_GUIDE.md.tpl +36 -0
  7. package/templates/README.md.tpl +58 -0
  8. package/templates/_shared/AGENTS.writes.md.tpl +38 -0
  9. package/templates/_shared/README.tail.md.tpl +20 -0
  10. package/templates/_shared/files/src/app.test.ts.tpl +36 -0
  11. package/templates/_shared/files/src/index.ts.tpl +8 -0
  12. package/templates/_shared/files/src/lib/client.ts.tpl +213 -0
  13. package/templates/_shared/files/src/lib/files.ts.tpl +37 -0
  14. package/templates/_shared/files/src/lib/http.ts.tpl +101 -0
  15. package/templates/_shared/files/src/lib/mail.ts.tpl +26 -0
  16. package/templates/_shared/files/src/lib/model.ts.tpl +31 -0
  17. package/templates/_shared/files/src/lib/page.ts.tpl +71 -0
  18. package/templates/_shared/files/src/lib/record.ts.tpl +246 -0
  19. package/templates/_shared/files/src/lib/server.ts.tpl +132 -0
  20. package/templates/_shared/files/src/shell.test.ts.tpl +56 -0
  21. package/templates/agent.ts.tpl +23 -0
  22. package/templates/bakim-takvimi/AGENTS.template.md.tpl +40 -0
  23. package/templates/bakim-takvimi/README.md.tpl +45 -0
  24. package/templates/bakim-takvimi/files/AGENT_GUIDE.md.tpl +52 -0
  25. package/templates/bakim-takvimi/files/one.data.json +45 -0
  26. package/templates/bakim-takvimi/files/src/agent.ts.tpl +22 -0
  27. package/templates/bakim-takvimi/files/src/app.ts.tpl +259 -0
  28. package/templates/bakim-takvimi/files/src/jobs.ts.tpl +170 -0
  29. package/templates/bakim-takvimi/files/src/names.ts.tpl +5 -0
  30. package/templates/bakim-takvimi/files/src/queries.ts.tpl +71 -0
  31. package/templates/bakim-takvimi/files/src/schedule.test.ts.tpl +58 -0
  32. package/templates/bakim-takvimi/files/src/schedule.ts.tpl +106 -0
  33. package/templates/bakim-takvimi/files/src/shell.ts.tpl +299 -0
  34. package/templates/bakim-takvimi/manifest.json.tpl +64 -0
  35. package/templates/egitim-sertifika/AGENTS.template.md.tpl +41 -0
  36. package/templates/egitim-sertifika/README.md.tpl +46 -0
  37. package/templates/egitim-sertifika/files/AGENT_GUIDE.md.tpl +53 -0
  38. package/templates/egitim-sertifika/files/one.data.json +43 -0
  39. package/templates/egitim-sertifika/files/src/agent.ts.tpl +22 -0
  40. package/templates/egitim-sertifika/files/src/app.ts.tpl +312 -0
  41. package/templates/egitim-sertifika/files/src/certificate.test.ts.tpl +83 -0
  42. package/templates/egitim-sertifika/files/src/certificate.ts.tpl +163 -0
  43. package/templates/egitim-sertifika/files/src/jobs.ts.tpl +219 -0
  44. package/templates/egitim-sertifika/files/src/names.ts.tpl +5 -0
  45. package/templates/egitim-sertifika/files/src/queries.ts.tpl +115 -0
  46. package/templates/egitim-sertifika/files/src/shell.ts.tpl +345 -0
  47. package/templates/egitim-sertifika/manifest.json.tpl +57 -0
  48. package/templates/envanter/AGENTS.template.md.tpl +28 -0
  49. package/templates/envanter/README.md.tpl +40 -0
  50. package/templates/envanter/files/AGENT_GUIDE.md.tpl +47 -0
  51. package/templates/envanter/files/one.data.json +62 -0
  52. package/templates/envanter/files/src/agent.ts.tpl +20 -0
  53. package/templates/envanter/files/src/app.ts.tpl +204 -0
  54. package/templates/envanter/files/src/jobs.ts.tpl +30 -0
  55. package/templates/envanter/files/src/queries.ts.tpl +53 -0
  56. package/templates/envanter/files/src/shell.ts.tpl +265 -0
  57. package/templates/envanter/files/src/stock.test.ts.tpl +31 -0
  58. package/templates/envanter/files/src/stock.ts.tpl +33 -0
  59. package/templates/envanter/manifest.json.tpl +56 -0
  60. package/templates/hedef-takibi/AGENTS.template.md.tpl +37 -0
  61. package/templates/hedef-takibi/README.md.tpl +52 -0
  62. package/templates/hedef-takibi/files/AGENT_GUIDE.md.tpl +48 -0
  63. package/templates/hedef-takibi/files/one.data.json +76 -0
  64. package/templates/hedef-takibi/files/src/agent.ts.tpl +26 -0
  65. package/templates/hedef-takibi/files/src/app.ts.tpl +740 -0
  66. package/templates/hedef-takibi/files/src/progress.test.ts.tpl +53 -0
  67. package/templates/hedef-takibi/files/src/progress.ts.tpl +81 -0
  68. package/templates/hedef-takibi/files/src/queries.ts.tpl +83 -0
  69. package/templates/hedef-takibi/files/src/shell.ts.tpl +455 -0
  70. package/templates/hedef-takibi/manifest.json.tpl +38 -0
  71. package/templates/index.ts.tpl +262 -0
  72. package/templates/markdown.d.ts.tpl +6 -0
  73. package/templates/musteri-ziyaret/AGENTS.template.md.tpl +47 -0
  74. package/templates/musteri-ziyaret/README.md.tpl +48 -0
  75. package/templates/musteri-ziyaret/files/AGENT_GUIDE.md.tpl +44 -0
  76. package/templates/musteri-ziyaret/files/one.data.json +53 -0
  77. package/templates/musteri-ziyaret/files/src/agent.ts.tpl +21 -0
  78. package/templates/musteri-ziyaret/files/src/app.ts.tpl +340 -0
  79. package/templates/musteri-ziyaret/files/src/customers.ts.tpl +85 -0
  80. package/templates/musteri-ziyaret/files/src/queries.ts.tpl +67 -0
  81. package/templates/musteri-ziyaret/files/src/shell.ts.tpl +272 -0
  82. package/templates/musteri-ziyaret/files/src/visit.test.ts.tpl +65 -0
  83. package/templates/musteri-ziyaret/files/src/visit.ts.tpl +98 -0
  84. package/templates/musteri-ziyaret/manifest.json.tpl +37 -0
  85. package/templates/saha-denetimi/AGENTS.template.md.tpl +32 -0
  86. package/templates/saha-denetimi/README.md.tpl +44 -0
  87. package/templates/saha-denetimi/files/AGENT_GUIDE.md.tpl +55 -0
  88. package/templates/saha-denetimi/files/one.data.json +87 -0
  89. package/templates/saha-denetimi/files/src/agent.ts.tpl +41 -0
  90. package/templates/saha-denetimi/files/src/app.ts.tpl +444 -0
  91. package/templates/saha-denetimi/files/src/queries.ts.tpl +143 -0
  92. package/templates/saha-denetimi/files/src/score.test.ts.tpl +48 -0
  93. package/templates/saha-denetimi/files/src/score.ts.tpl +102 -0
  94. package/templates/saha-denetimi/files/src/shell.ts.tpl +585 -0
  95. package/templates/saha-denetimi/manifest.json.tpl +49 -0
  96. package/templates/shell.test.ts.tpl +30 -0
  97. package/templates/shell.ts.tpl +201 -0
  98. package/templates/uygunsuzluk-dof/AGENTS.template.md.tpl +40 -0
  99. package/templates/uygunsuzluk-dof/README.md.tpl +46 -0
  100. package/templates/uygunsuzluk-dof/files/AGENT_GUIDE.md.tpl +56 -0
  101. package/templates/uygunsuzluk-dof/files/one.data.json +99 -0
  102. package/templates/uygunsuzluk-dof/files/src/agent.ts.tpl +45 -0
  103. package/templates/uygunsuzluk-dof/files/src/app.ts.tpl +431 -0
  104. package/templates/uygunsuzluk-dof/files/src/capa.test.ts.tpl +53 -0
  105. package/templates/uygunsuzluk-dof/files/src/capa.ts.tpl +114 -0
  106. package/templates/uygunsuzluk-dof/files/src/jobs.ts.tpl +127 -0
  107. package/templates/uygunsuzluk-dof/files/src/queries.ts.tpl +102 -0
  108. package/templates/uygunsuzluk-dof/files/src/shell.ts.tpl +441 -0
  109. package/templates/uygunsuzluk-dof/manifest.json.tpl +56 -0
  110. package/templates/ziyaretci-kaydi/AGENTS.template.md.tpl +35 -0
  111. package/templates/ziyaretci-kaydi/README.md.tpl +46 -0
  112. package/templates/ziyaretci-kaydi/files/AGENT_GUIDE.md.tpl +39 -0
  113. package/templates/ziyaretci-kaydi/files/one.data.json +42 -0
  114. package/templates/ziyaretci-kaydi/files/src/agent.ts.tpl +13 -0
  115. package/templates/ziyaretci-kaydi/files/src/app.ts.tpl +293 -0
  116. package/templates/ziyaretci-kaydi/files/src/jobs.ts.tpl +49 -0
  117. package/templates/ziyaretci-kaydi/files/src/notice.ts.tpl +48 -0
  118. package/templates/ziyaretci-kaydi/files/src/queries.ts.tpl +45 -0
  119. package/templates/ziyaretci-kaydi/files/src/shell.ts.tpl +252 -0
  120. package/templates/ziyaretci-kaydi/files/src/visitor.test.ts.tpl +34 -0
  121. package/templates/ziyaretci-kaydi/files/src/visitor.ts.tpl +57 -0
  122. package/templates/ziyaretci-kaydi/manifest.json.tpl +38 -0
package/README.md CHANGED
@@ -1,3 +1,63 @@
1
- # Temporary Holding Version
1
+ # create-devoplus-one-app
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Yeni bir **Devoplus One** uygulamasının iskeletini üretir: uygulama manifest'i, `@devoplus/one-sdk` ile yazılmış sunucu
4
+ giriş noktası, Devoplus Portal ile konuşan sayfa, veritabanı migration'ı, testler, `README.md` ve uygulamayı yazacak
5
+ yapay zeka aracı için kuralları anlatan `AGENTS.md`. Boş iskelet yerine çalışan bir iş uygulaması şablonundan da
6
+ başlayabilirsiniz.
7
+
8
+ ## Kullanım
9
+
10
+ ```bash
11
+ npm create devoplus-one-app@next -- <hedef-dizin> --slug <kısa-ad> --owner <sahibin e-postası> [--template <şablon>]
12
+ ```
13
+
14
+ `npx create-devoplus-one-app@next <hedef-dizin> --slug <kısa-ad> --owner <e-posta>` aynı işi yapar. Kararlı sürüm henüz yayımlanmadı; bu yüzden `@next`
15
+ yazın. Node 22 ya da üstü gerekir. Hedef dizin boş değilse `--force` verilmeden hiçbir dosya yazılmaz.
16
+
17
+ ⚠️ Devoplus One'ın iskelet üreticisi yalnız bu pakettir. Adı benzeyen başka paketler Devoplus'a ait değildir; onları
18
+ çalıştırmayın.
19
+
20
+ ## Şablonlar
21
+
22
+ | Şablon | Ne için |
23
+ |---|---|
24
+ | `blank` (varsayılan) | Kişisel notlar örneğiyle en küçük başlangıç; hiçbir şablon uymuyorsa |
25
+ | `envanter` | Stok kalemleri, giriş ve çıkış hareketleri, düşük stok uyarısı |
26
+ | `bakim-takvimi` | Cihaz ve ekipmanların periyodik bakımı ve hatırlatmaları |
27
+ | `saha-denetimi` | Mağaza ve şube denetimi için kontrol listesi, puan ve rapor |
28
+ | `egitim-sertifika` | Çalışanların eğitim ve sertifikaları, süre dolumu hatırlatması |
29
+ | `ziyaretci-kaydi` | Resepsiyonda ziyaretçi girişi ve çıkışı, ziyaret edilene bildirim |
30
+ | `uygunsuzluk-dof` | Kalite ve iş güvenliği uygunsuzlukları ve düzeltici faaliyetler |
31
+ | `musteri-ziyaret` | Saha ekibinin müşteri ziyaretleri ve sonraki adımlar |
32
+ | `hedef-takibi` | Ekip hedefleri, ölçülebilir sonuçlar ve aksiyonlar |
33
+
34
+ `--list-templates` şablonları kısa açıklamalarıyla listeler.
35
+
36
+ ## Seçenekler
37
+
38
+ | Seçenek | Açıklama |
39
+ |---|---|
40
+ | `--owner <e-posta>` | Zorunlu: uygulamanın sahibinin e-posta adresi |
41
+ | `--slug <kısa-ad>` | Küçük harf, rakam ve tek tire; harfle başlar, en çok 24 karakter (varsayılan: hedef dizinin adı) |
42
+ | `--template <şablon>` | Yukarıdaki şablonlardan biri (varsayılan: `blank`) |
43
+ | `--name "<ad>"` / `--name-en "<ad>"` | Türkçe ve İngilizce ad (varsayılan: kısa addan) |
44
+ | `--services <liste>` | `database` her zaman açıktır; `files` dosya deposu, `cache` önbellek ekler |
45
+ | `--scopes <liste>` | İstenen yetki alanları, ör. `orgschema.read,mail.execute` |
46
+ | `--permission-code <kod>` | Uygulamayı açan izin kodu, `One.<Ad>.<Ad>` (varsayılan: kısa addan türetilir) |
47
+ | `--sdk <aralık>` / `--version <sürüm>` | SDK sürüm aralığı ve uygulamanın başlangıç sürümü |
48
+ | `--force` | Boş olmayan dizine yazar |
49
+
50
+ ## Sonraki adımlar
51
+
52
+ ```bash
53
+ cd <hedef-dizin>
54
+ pnpm install && pnpm type-check && pnpm test
55
+ ```
56
+
57
+ Ardından `AGENTS.md`'yi okuyun ve üretilen `README.md`'deki kayıt, kurulum ve yayın adımlarını izleyin. Platformun
58
+ geliştirici belgeleri ön sürüm kanalı için https://test-one.devoplus.dev/docs adresindedir.
59
+
60
+ ## Sürümler ve lisans
61
+
62
+ Paket `@devoplus/one-sdk` ile aynı iki kanaldan yayımlanır: `next` (ön sürüm, `<sürüm>-next.<n>`) ve `latest`
63
+ (kararlı). Lisans: `UNLICENSED` (açık kaynak lisansı verilmez).
package/dist/cli.mjs ADDED
@@ -0,0 +1,17 @@
1
+ #!/usr/bin/env node
2
+ import{mkdir,readdir,stat,writeFile}from"node:fs/promises";import{basename,dirname,join as join2,resolve}from"node:path";import{readFileSync,readdirSync,statSync}from"node:fs";var MONTHS=["jan","feb","mar","apr","may","jun","jul","aug","sep","oct","nov","dec"];var DAYS=["sun","mon","tue","wed","thu","fri","sat"];var FIELDS=[{label:"dakika",min:0,max:59},{label:"saat",min:0,max:23},{label:"ayın günü",min:1,max:31},{label:"ay",min:1,max:12,names:MONTHS,nameBase:1},{label:"haftanın günü",min:0,max:7,names:DAYS,nameBase:0}];function value(raw,spec){if(/^\d{1,2}$/.test(raw)){const n=Number(raw);return n>=spec.min&&n<=spec.max?n:null}if(spec.names){const i=spec.names.indexOf(raw.toLowerCase());if(i>=0)return i+(spec.nameBase??0)}return null}function field(raw,spec){const values=new Set;const star=raw.startsWith("*");for(const part of raw.split(",")){const m=/^(\*|[A-Za-z0-9]+(?:-[A-Za-z0-9]+)?)(?:\/(\d{1,2}))?$/.exec(part);if(!m)return`${spec.label} alanında tanınmayan öğe: "${part}"`;const[,range,stepRaw]=m;const step=stepRaw===void 0?1:Number(stepRaw);if(step<1)return`${spec.label} alanında adım en az 1 olmalı: "${part}"`;let from;let to;if(range==="*"){from=spec.min;to=spec.max}else if(range.includes("-")){const[a,b]=range.split("-");const va=value(a,spec);const vb=value(b,spec);if(va===null||vb===null)return`${spec.label} alanında geçersiz aralık: "${part}" (${spec.min}-${spec.max})`;if(va>vb)return`${spec.label} alanında aralık ters: "${part}"`;from=va;to=vb}else{const v=value(range,spec);if(v===null)return`${spec.label} alanında geçersiz değer: "${part}" (${spec.min}-${spec.max})`;from=v;to=stepRaw===void 0?v:spec.max}for(let n=from;n<=to;n+=step)values.add(n)}return{values,star}}function parseCron(expression){if(typeof expression!=="string")return{ok:false,error:"cron ifadesi metin olmalı"};const parts=expression.trim().split(/\s+/);if(parts.length!==5||parts[0]==="")return{ok:false,error:"cron ifadesi tam 5 alan olmalı (dakika saat ayın-günü ay haftanın-günü)"};const parsed=[];for(let i=0;i<5;i++){const r=field(parts[i],FIELDS[i]);if(typeof r==="string")return{ok:false,error:r};parsed.push(r)}const[minute,hour,dom,month,dow]=parsed;const dayOfWeek=new Set([...dow.values].map(d=>d===7?0:d));return{ok:true,schedule:{minute:minute.values,hour:hour.values,dayOfMonth:dom.values,month:month.values,dayOfWeek,dayOfMonthStar:dom.star,dayOfWeekStar:dow.star}}}var TIME_ZONE_RE=/^[A-Za-z][A-Za-z0-9_+-]*(?:\/[A-Za-z0-9_+-]+){0,2}$/;function canonicalTimeZone(timeZone){if(typeof timeZone!=="string"||!TIME_ZONE_RE.test(timeZone))return null;try{return new Intl.DateTimeFormat("en-US",{timeZone}).resolvedOptions().timeZone}catch{return null}}import{z}from"zod";var SLUG_RE=/^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;var SLUG_MAX=24;function isValidSlug(slug2){return slug2.length<=SLUG_MAX&&SLUG_RE.test(slug2)}var JOB_NAME_RE=/^[a-z][a-z0-9-]{1,39}$/;var JOBS_MAX_SCHEDULES=10;var JOBS_MAX_QUEUES=10;var JOBS_DEFAULT_TIME_ZONE="Europe/Istanbul";var JOB_MAX_RETRIES_DEFAULT=3;var JOB_MAX_RETRIES_MAX=10;var JOB_BODY_MAX_BYTES=64*1024;var JOB_IDENTITY_MAX_AGE_MS=48*60*60*1e3;var JOB_SERVICE_SCOPES=["mail.execute","sms.execute","push.execute","dexa.read","ai.use"];var jobName=z.string().regex(JOB_NAME_RE,"ad küçük harfle başlar; yalnız küçük harf, rakam ve tire; 2-40 karakter");var description=z.string().trim().min(1).max(200).optional();var scheduleSchema=z.object({name:jobName,cron:z.string().superRefine((value2,ctx)=>{const r=parseCron(value2);if(!r.ok)ctx.addIssue({code:"custom",message:r.error})}),timeZone:z.string().default(JOBS_DEFAULT_TIME_ZONE).transform((value2,ctx)=>{const canonical=canonicalTimeZone(value2);if(!canonical){ctx.addIssue({code:"custom",message:`"${value2}" tanınan bir saat dilimi değil (ör. Europe/Istanbul, UTC)`});return z.NEVER}return canonical}),description}).strict();var queueSchema=z.object({name:jobName,maxRetries:z.number().int().min(0).max(JOB_MAX_RETRIES_MAX).default(JOB_MAX_RETRIES_DEFAULT),description}).strict();function uniqueNames(items,ctx){const seen=new Set;items.forEach((item,i)=>{if(seen.has(item.name))ctx.addIssue({code:"custom",path:[i,"name"],message:`"${item.name}" adı birden çok kez yazılmış`});seen.add(item.name)})}var jobsSchema=z.object({schedules:z.array(scheduleSchema).max(JOBS_MAX_SCHEDULES,`en çok ${JOBS_MAX_SCHEDULES} zamanlama beyan edilebilir`).superRefine(uniqueNames).default([]),queues:z.array(queueSchema).max(JOBS_MAX_QUEUES,`en çok ${JOBS_MAX_QUEUES} kuyruk beyan edilebilir`).superRefine(uniqueNames).default([]),services:z.array(z.string().refine(s=>JOB_SERVICE_SCOPES.includes(s),{message:`arka plan işi servisleri yalnız şunlar olabilir: ${JOB_SERVICE_SCOPES.join(", ")} (DEXA'ya yazma bu yolla açılmaz)`})).max(JOB_SERVICE_SCOPES.length).superRefine((items,ctx)=>{const seen=new Set;items.forEach((s,i)=>{if(seen.has(s))ctx.addIssue({code:"custom",path:[i],message:`"${s}" birden çok kez yazılmış`});seen.add(s)})}).optional()}).strict().refine(jobs=>jobs.schedules.length+jobs.queues.length>0,{message:"jobs en az bir zamanlama (schedules) ya da kuyruk (queues) taşımalıdır; hiç yoksa alanı yazmayın"});import{z as z3}from"zod";import{z as z2}from"zod";var AGENT_TOOL_NAME_RE=/^[a-z][a-z0-9-]{1,39}$/;var AGENT_TOOLS_MAX=20;var AGENT_INPUT_SCHEMA_MAX_BYTES=8*1024;var AGENT_INPUT_SCHEMA_MAX_DEPTH=8;var AGENT_INPUT_SCHEMA_MAX_PROPERTIES=50;var AGENT_INPUT_SCHEMA_MAX_ENUM=100;var AGENT_INPUT_SCHEMA_DESCRIPTION_MAX=500;var AGENT_INPUT_PROPERTY_RE=/^[A-Za-z_][A-Za-z0-9_]{0,63}$/;var AGENT_INPUT_KEYWORDS=["type","properties","required","enum","minimum","maximum","minLength","maxLength","items","description"];var AGENT_INPUT_TYPES=["object","string","number","integer","boolean","array"];var KEYWORDS=new Set(AGENT_INPUT_KEYWORDS);var TYPES=new Set(AGENT_INPUT_TYPES);function isPlainObject(value2){return value2!==null&&typeof value2==="object"&&!Array.isArray(value2)}function utf8Length(text){let n=0;for(const ch of text){const c=ch.codePointAt(0)??0;n+=c<128?1:c<2048?2:c<65536?3:4}return n}function join(path,key){return path?`${path}.${key}`:String(key)}function agentInputSchemaProblems(schema){const problems=[];let serialized;try{serialized=JSON.stringify(schema)??""}catch{return[{path:"",message:"şema JSON'a çevrilemiyor"}]}const bytes=utf8Length(serialized);if(bytes>AGENT_INPUT_SCHEMA_MAX_BYTES)problems.push({path:"",message:`şema en çok ${AGENT_INPUT_SCHEMA_MAX_BYTES} bayt olabilir (${bytes})`});if(!isPlainObject(schema))return[...problems,{path:"",message:"şema bir nesne olmalıdır"}];if(schema.type!=="object")problems.push({path:"type",message:'araç girdisinin kökü "type": "object" olmalıdır'});node(schema,"",1,problems);return problems}function node(schema,path,depth,out){if(!isPlainObject(schema)){out.push({path,message:"şema düğümü bir nesne olmalıdır"});return}if(depth>AGENT_INPUT_SCHEMA_MAX_DEPTH){out.push({path,message:`şema en çok ${AGENT_INPUT_SCHEMA_MAX_DEPTH} düzey iç içe olabilir`});return}for(const key of Object.keys(schema))if(!KEYWORDS.has(key))out.push({path:join(path,key),message:`"${key}" desteklenmiyor; izinli anahtarlar: ${AGENT_INPUT_KEYWORDS.join(", ")}`});const type=schema.type;if(typeof type!=="string"||!TYPES.has(type)){out.push({path:join(path,"type"),message:`"type" zorunludur ve şunlardan biri olmalıdır: ${AGENT_INPUT_TYPES.join(", ")}`});return}const only=(key,allowed)=>{if(key in schema&&!allowed.includes(type))out.push({path:join(path,key),message:`"${key}" yalnız ${allowed.join("/")} tipinde kullanılabilir`})};only("properties",["object"]);only("required",["object"]);only("items",["array"]);only("minimum",["number","integer"]);only("maximum",["number","integer"]);only("minLength",["string"]);only("maxLength",["string"]);if("description"in schema){const d=schema.description;if(typeof d!=="string"||d.trim().length===0||d.length>AGENT_INPUT_SCHEMA_DESCRIPTION_MAX)out.push({path:join(path,"description"),message:`"description" 1-${AGENT_INPUT_SCHEMA_DESCRIPTION_MAX} karakterlik metin olmalıdır`})}for(const key of["minimum","maximum"]){if(key in schema&&(typeof schema[key]!=="number"||!Number.isFinite(schema[key])))out.push({path:join(path,key),message:`"${key}" sonlu bir sayı olmalıdır`})}if(typeof schema.minimum==="number"&&typeof schema.maximum==="number"&&schema.minimum>schema.maximum)out.push({path:join(path,"minimum"),message:'"minimum" "maximum"dan büyük olamaz'});for(const key of["minLength","maxLength"]){const v=schema[key];if(key in schema&&(typeof v!=="number"||!Number.isSafeInteger(v)||v<0))out.push({path:join(path,key),message:`"${key}" negatif olmayan bir tam sayı olmalıdır`})}if(typeof schema.minLength==="number"&&typeof schema.maxLength==="number"&&schema.minLength>schema.maxLength)out.push({path:join(path,"minLength"),message:'"minLength" "maxLength"ten büyük olamaz'});if("enum"in schema){const values=schema.enum;if(!Array.isArray(values)||values.length===0||values.length>AGENT_INPUT_SCHEMA_MAX_ENUM){out.push({path:join(path,"enum"),message:`"enum" 1-${AGENT_INPUT_SCHEMA_MAX_ENUM} öğeli bir dizi olmalıdır`})}else{const seen=new Set;values.forEach((v,i)=>{const ok=type==="string"&&typeof v==="string"||type==="boolean"&&typeof v==="boolean"||type==="number"&&typeof v==="number"&&Number.isFinite(v)||type==="integer"&&typeof v==="number"&&Number.isSafeInteger(v);if(!ok)out.push({path:join(join(path,"enum"),i),message:`"enum" öğesi "${type}" tipinde olmalıdır`});const k=JSON.stringify(v);if(seen.has(k))out.push({path:join(join(path,"enum"),i),message:'"enum" öğesi birden çok kez yazılmış'});seen.add(k)})}}if(type==="object"){const props=schema.properties??{};if(!isPlainObject(props)){out.push({path:join(path,"properties"),message:'"properties" bir nesne olmalıdır'})}else{const names=Object.keys(props);if(names.length>AGENT_INPUT_SCHEMA_MAX_PROPERTIES)out.push({path:join(path,"properties"),message:`bir nesnede en çok ${AGENT_INPUT_SCHEMA_MAX_PROPERTIES} alan olabilir`});for(const name of names){if(!AGENT_INPUT_PROPERTY_RE.test(name))out.push({path:join(join(path,"properties"),name),message:'alan adı harf ya da "_" ile başlar, yalnız harf/rakam/"_" içerir, en çok 64 karakter'});node(props[name],join(join(path,"properties"),name),depth+1,out)}if("required"in schema){const req=schema.required;if(!Array.isArray(req)||req.some(r=>typeof r!=="string")){out.push({path:join(path,"required"),message:'"required" metin dizisi olmalıdır'})}else{const seen=new Set;req.forEach((r,i)=>{if(!(r in props))out.push({path:join(join(path,"required"),i),message:`"${r}" "properties" içinde tanımlı değil`});if(seen.has(r))out.push({path:join(join(path,"required"),i),message:`"${r}" birden çok kez yazılmış`});seen.add(r)})}}}}if(type==="array"){if(!("items"in schema))out.push({path:join(path,"items"),message:'dizi tipinde "items" zorunludur'});else node(schema.items,join(path,"items"),depth+1,out)}}var localized=z2.object({tr:z2.string().trim().min(1).max(200),en:z2.string().trim().min(1).max(200).optional()});var localizedLong=z2.object({tr:z2.string().trim().min(1).max(1e3),en:z2.string().trim().min(1).max(1e3).optional()});var agentToolSchema=z2.object({name:z2.string().regex(AGENT_TOOL_NAME_RE,'araç adı küçük harfle başlar, yalnız küçük harf/rakam/"-" içerir, 2-40 karakter'),title:localized,description:localizedLong,readOnly:z2.boolean(),permission:z2.string().optional(),input:z2.unknown().superRefine((schema,ctx)=>{for(const p of agentInputSchemaProblems(schema))ctx.addIssue({code:"custom",path:p.path?p.path.split("."):[],message:p.message})})}).strict();var agentSectionSchema=z2.object({summary:localizedLong,tools:z2.array(agentToolSchema).max(AGENT_TOOLS_MAX,`en çok ${AGENT_TOOLS_MAX} araç beyan edilebilir`).superRefine((tools,ctx)=>{const seen=new Set;tools.forEach((t,i)=>{if(seen.has(t.name))ctx.addIssue({code:"custom",path:[i,"name"],message:`"${t.name}" aracı birden çok kez yazılmış`});seen.add(t.name)})}).default([])}).strict();var localizedText=z3.object({tr:z3.string().trim().min(1).max(120),en:z3.string().trim().min(1).max(120).optional()});var SCOPE_RE=/^(?:[a-z][a-z0-9]*\.(?:read|write|manage|execute)|ai\.use)$/;var DEXA_QUERY_KEY_RE=/^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;var DEXA_QUERY_KEYS_MAX=64;function renamed(next){return z3.never({error:`bu alan kaldırıldı; yerine "services.${next}" yazın`}).optional()}var DEXA_READ_SCOPE="dexa.read";var DEXA_COMMAND_KEYS_MAX=32;var DEXA_WRITE_SCOPE="dexa.write";function dexaKeyList(kind,max){return z3.array(z3.string().regex(DEXA_QUERY_KEY_RE,"DEXA anahtarı harf ya da rakamla başlamalı, yalnız harf/rakam/./_/- içermeli ve en çok 128 karakter olmalıdır")).min(1,`en az bir DEXA ${kind} anahtarı beyan edilmelidir`).max(max,`en çok ${max} DEXA ${kind} anahtarı beyan edilebilir`).superRefine((keys,ctx)=>{const seen=new Set;keys.forEach((key,i)=>{if(seen.has(key))ctx.addIssue({code:"custom",path:[i],message:`"${key}" anahtarı birden çok kez yazılmış`});seen.add(key)})})}var NAVIGATION_MAX_ITEMS=12;var NAVIGATION_PATH_RE=/^\/(?:[A-Za-z0-9_-][A-Za-z0-9._-]*(?:\/[A-Za-z0-9_-][A-Za-z0-9._-]*)*)?$/;var NAVIGATION_PATH_MAX_LENGTH=200;var ACTING_SESSION_MODES=["read","write"];var DEVICE_PERMISSIONS=["camera","microphone","geolocation"];var semver=z3.string().regex(/^\d+\.\d+\.\d+$/,"semver biçimi x.y.z olmalı");var PERMISSION_CODE_RE=/^One\.[A-Z][A-Za-z0-9]*\.[A-Z][A-Za-z0-9]*$/;var permissionCode=z3.string().regex(PERMISSION_CODE_RE);var EXTRA_PERMISSIONS_MAX=16;function permissionNamespace(code){return code.slice(0,code.lastIndexOf(".")+1)}var manifestSchema=z3.object({schemaVersion:z3.literal(1),app:z3.object({slug:z3.string().max(SLUG_MAX).regex(SLUG_RE,"slug küçük harf/rakam ve tek tire içerebilir; harfle başlar"),name:localizedText,version:semver,description:localizedText.optional(),owner:z3.object({email:z3.string().email()})}),entry:z3.string().regex(/^[\w./-]+\.(ts|js|mjs)$/),scopes:z3.array(z3.string().regex(SCOPE_RE)).max(32).default([]),services:z3.object({database:z3.boolean().default(true),files:z3.boolean().default(false),cache:z3.boolean().default(false),d1:renamed("database"),r2:renamed("files"),kv:renamed("cache"),email:z3.boolean().default(false),sms:z3.boolean().default(false),push:z3.boolean().default(false),orgSchema:z3.boolean().default(false),dexa:z3.object({queryKeys:dexaKeyList("sorgu",DEXA_QUERY_KEYS_MAX).optional(),commandKeys:dexaKeyList("işlem",DEXA_COMMAND_KEYS_MAX).optional()}).refine(dexa=>dexa.queryKeys!==void 0||dexa.commandKeys!==void 0,{message:"services.dexa en az bir anahtar listesi taşımalıdır: queryKeys (okuma sorguları) ya da commandKeys (ERP işlemleri)"}).optional()}).strict().transform(({d1:_d1,r2:_r2,kv:_kv,...services})=>services).prefault({}),menu:z3.object({title:localizedText,icon:z3.string().max(64).optional(),group:z3.string().max(64).optional()}),permission:z3.object({code:permissionCode,name:localizedText}),permissions:z3.array(z3.object({code:permissionCode,name:localizedText}).strict()).max(EXTRA_PERMISSIONS_MAX,`en çok ${EXTRA_PERMISSIONS_MAX} ek izin beyan edilebilir`).default([]),navigation:z3.array(z3.object({path:z3.string().max(NAVIGATION_PATH_MAX_LENGTH,`yol en çok ${NAVIGATION_PATH_MAX_LENGTH} karakter olabilir`).regex(NAVIGATION_PATH_RE,"yol '/' ile başlar ve yalnız harf, rakam, '-', '_', '.' içeren parçalardan oluşur (sorgu, '#', '..' yok)"),label:localizedText,permission:z3.string().optional()}).strict()).max(NAVIGATION_MAX_ITEMS,`en çok ${NAVIGATION_MAX_ITEMS} menü öğesi beyan edilebilir`).superRefine((items,ctx)=>{const seen=new Set;items.forEach((item,i)=>{if(seen.has(item.path))ctx.addIssue({code:"custom",path:[i,"path"],message:`"${item.path}" yolu birden çok kez yazılmış`});seen.add(item.path)})}).default([]),actingSessions:z3.enum(ACTING_SESSION_MODES).default("read"),devicePermissions:z3.array(z3.enum(DEVICE_PERMISSIONS)).superRefine((items,ctx)=>{const seen=new Set;items.forEach((item,i)=>{if(seen.has(item))ctx.addIssue({code:"custom",path:[i],message:`"${item}" birden çok kez yazılmış`});seen.add(item)})}).default([]),agent:agentSectionSchema.optional(),limits:z3.object({cpuMs:z3.number().int().min(1).max(1e3).optional(),subRequests:z3.number().int().min(1).max(200).optional()}).optional(),observability:z3.object({headSamplingRate:z3.number().gt(0,"örnekleme oranı 0'dan büyük olmalıdır").max(1,"örnekleme oranı en çok 1 olabilir").optional()}).strict().optional(),jobs:jobsSchema.optional()}).strict().superRefine((manifest,ctx)=>{const ns=permissionNamespace(manifest.permission.code);const seen=new Set([manifest.permission.code]);manifest.permissions.forEach((p,i)=>{if(!p.code.startsWith(ns))ctx.addIssue({code:"custom",path:["permissions",i,"code"],message:`ek izin "${p.code}" ana iznin ad alanında olmalı (${ns}…)`});else if(seen.has(p.code))ctx.addIssue({code:"custom",path:["permissions",i,"code"],message:`"${p.code}" izni birden çok kez yazılmış`});seen.add(p.code)});const declared=new Set([manifest.permission.code,...manifest.permissions.map(p=>p.code)]);manifest.agent?.tools.forEach((tool,i)=>{if(tool.permission===void 0)return;if(!PERMISSION_CODE_RE.test(tool.permission)||!tool.permission.startsWith(ns))ctx.addIssue({code:"custom",path:["agent","tools",i,"permission"],message:`araç izni "${tool.permission}" uygulamanın ad alanında olmalı (${ns}…)`});else if(!declared.has(tool.permission))ctx.addIssue({code:"custom",path:["agent","tools",i,"permission"],message:`araç izni "${tool.permission}" manifest'te beyan edilmemiş — "permission.code" ya da "permissions" içinde olmalı`})});manifest.navigation.forEach((item,i)=>{if(item.permission===void 0)return;if(!PERMISSION_CODE_RE.test(item.permission)||!item.permission.startsWith(ns))ctx.addIssue({code:"custom",path:["navigation",i,"permission"],message:`sekme izni "${item.permission}" uygulamanın ad alanında olmalı (${ns}…)`});else if(!declared.has(item.permission))ctx.addIssue({code:"custom",path:["navigation",i,"permission"],message:`sekme izni "${item.permission}" manifest'te beyan edilmemiş — "permission.code" ya da "permissions" içinde olmalı`})});manifest.jobs?.services?.forEach((service,i)=>{if(!manifest.scopes.includes(service))ctx.addIssue({code:"custom",path:["jobs","services",i],message:`jobs.services içindeki "${service}" scopes içinde de beyan edilmelidir`})});const dexa=manifest.services.dexa;const pairs=[{scope:DEXA_READ_SCOPE,keys:dexa?.queryKeys,field:"queryKeys",what:"sorgu",verb:"DEXA sorguları"},{scope:DEXA_WRITE_SCOPE,keys:dexa?.commandKeys,field:"commandKeys",what:"işlem",verb:"DEXA işlemleri (ERP yazma)"}];for(const pair of pairs){const hasScope=manifest.scopes.includes(pair.scope);const hasKeys=pair.keys!==void 0;if(hasKeys&&!hasScope)ctx.addIssue({code:"custom",path:["scopes"],message:`services.dexa.${pair.field} beyan edildi; scopes içinde "${pair.scope}" de olmalıdır (${pair.verb} bu scope olmadan çalışmaz)`});if(hasScope&&!hasKeys)ctx.addIssue({code:"custom",path:["services","dexa"],message:`scopes içinde "${pair.scope}" var; services.dexa.${pair.field} ile çalıştırılacak ${pair.what} anahtarları da beyan edilmelidir`})}});function validateManifest(input){const r=manifestSchema.safeParse(input);if(r.success)return{ok:true,manifest:r.data};return{ok:false,errors:r.error.issues.map(i=>({path:i.path.join(".")||"(kök)",message:i.message}))}}var BLOCK_RE=/\{\{([#^])([A-Za-z0-9_]+)\}\}\n?([\s\S]*?)\{\{\/\2\}\}\n?/g;var VAR_RE=/\{\{([A-Za-z0-9_]+)\}\}/g;var MAX_NESTING=8;function lookup(vars,name){if(!Object.hasOwn(vars,name))throw new Error(`şablon değişkeni tanımsız: ${name}`);return vars[name]}function isTruthy(value2){return value2===true||typeof value2==="string"&&value2.length>0}function renderTemplate(source,vars){let out=source;for(let round=0;round<MAX_NESTING;round++){let changed=false;out=out.replace(BLOCK_RE,(_match,kind,name,body)=>{changed=true;return kind==="#"===isTruthy(lookup(vars,name))?body:""});if(!changed)break}if(/\{\{[#^/]/.test(out))throw new Error("şablon bloğu kapanmamış ya da çok derin");return out.replace(VAR_RE,(_match,name)=>{const value2=lookup(vars,name);if(typeof value2!=="string")throw new Error(`şablon değişkeni metin değil: ${name}`);return value2})}var TEMPLATE_NAMES=["blank","envanter","bakim-takvimi","saha-denetimi","egitim-sertifika","ziyaretci-kaydi","uygunsuzluk-dof","musteri-ziyaret","hedef-takibi"];var DEFAULT_TEMPLATE="blank";var TEMPLATES=[{name:"blank",title:{tr:"Boş iskelet",en:"Blank scaffold"},summary:{tr:"Kişisel notlar örneğiyle en küçük başlangıç; hiçbir şablon uymuyorsa",en:"Smallest start with a personal-notes sample; when no template fits"}},{name:"envanter",title:{tr:"Envanter",en:"Inventory"},summary:{tr:"Stok kalemleri, adet ve konum, giriş/çıkış hareketleri, düşük stok uyarısı ve günlük kontrol",en:"Stock items, quantity and location, in/out movements, low-stock alert and daily check"}},{name:"bakim-takvimi",title:{tr:"Bakım takvimi",en:"Maintenance schedule"},summary:{tr:"Cihaz ve ekipmanların periyodik bakımı; sıradaki bakım tarihi sunucuda hesaplanır, yaklaşan ve geciken bakım sorumluya hatırlatılır",en:"Periodic maintenance of devices and equipment; the next due date is computed on the server, upcoming and overdue work is reminded"}},{name:"saha-denetimi",title:{tr:"Saha denetimi",en:"Field audit"},summary:{tr:"Mağaza ve şube denetimi için kontrol listesi; madde başına sonuç, fotoğraf, sunucuda hesaplanan puan ve yazdırılabilir rapor",en:"Checklist for store and branch audits; per-item result, photo, server-computed score and printable report"}},{name:"egitim-sertifika",title:{tr:"Eğitim ve sertifika takibi",en:"Training and certificate tracking"},summary:{tr:"Çalışanların eğitim ve sertifikaları; süresi dolmak üzere olanlar kişiye ve yöneticisine hatırlatılır, yönetici ekibini görür",en:"Employee trainings and certificates; expiring ones are reminded to the person and their manager, managers see their team"}},{name:"ziyaretci-kaydi",title:{tr:"Ziyaretçi kaydı",en:"Visitor log"},summary:{tr:"Resepsiyonda ziyaretçi girişi ve çıkışı; ziyaret edilen çalışana bildirim, kişisel veriler belirli bir süre sonra kendiliğinden silinir",en:"Visitor check-in and check-out at reception; the host is notified, personal data is deleted automatically after a set period"}},{name:"uygunsuzluk-dof",title:{tr:"Uygunsuzluk ve düzeltici faaliyet",en:"Nonconformity and corrective action"},summary:{tr:"Kalite ve iş güvenliği uygunsuzlukları; kök neden, sorumlu ve terminli düzeltici faaliyetler, geciken faaliyet hatırlatması",en:"Quality and safety nonconformities; root cause, corrective actions with owner and due date, overdue reminders"}},{name:"musteri-ziyaret",title:{tr:"Müşteri ziyaret raporu",en:"Customer visit report"},summary:{tr:"Saha ekibinin müşteri ziyaretleri; müşteri listesi ERP'ye bağlanabilir, ziyaret notu ve sonraki adım kaydedilir, yönetici ekibini görür",en:"Field team customer visits; the customer list can be connected to the ERP, visit notes and next steps are recorded, managers see their team"}},{name:"hedef-takibi",title:{tr:"Hedef ve aksiyon takibi",en:"Goal and action tracking"},summary:{tr:"Ekip hedefleri, ölçülebilir sonuçlar ve aksiyonlar; ilerleme sunucuda hesaplanır, dönem panosu ve Excel",en:"Team goals, measurable results and actions; progress is computed on the server, period board and Excel"}}];function isTemplateName(value2){return TEMPLATE_NAMES.includes(value2)}var FOLD_COLUMN_SUFFIX="_fold";var FOLD_PAIRS=[["Ç","c"],["ç","c"],["Ğ","g"],["ğ","g"],["İ","i"],["ı","i"],["Ö","o"],["ö","o"],["Ş","s"],["ş","s"],["Ü","u"],["ü","u"],["Â","a"],["â","a"],["Î","i"],["î","i"],["Û","u"],["û","u"]];var FOLD_MAP=new Map(FOLD_PAIRS);var FOLD_RE=new RegExp(`[${FOLD_PAIRS.map(([from])=>from).join("")}]`,"g");function foldColumnName(column){return`${column}${FOLD_COLUMN_SUFFIX}`}var SQL_FOLD_PAIRS=[...FOLD_PAIRS.map(([from,to])=>[from.normalize("NFD"),to]).filter(([from],i)=>from!==FOLD_PAIRS[i][0]),...FOLD_PAIRS];var DATA_FIELD_TYPES=["text","longText","number","money","date","datetime","boolean","choice","person","file"];var DATA_NAME_RE=/^[a-z][a-z0-9_]{0,39}$/;var DATA_CHOICE_VALUE_RE=/^[a-z0-9][a-z0-9_-]{0,39}$/;var DATA_MAX_ENTITIES=30;var DATA_MAX_FIELDS=60;var DATA_MAX_CHOICES=100;var DATA_TITLE_MAX_LENGTH=120;var TEXT_DEFAULT_MAX_LENGTH=200;var TEXT_MAX_LENGTH=1e3;var LONG_TEXT_DEFAULT_MAX_LENGTH=1e4;var LONG_TEXT_MAX_LENGTH=5e4;var DATA_MAX_COLUMNS=100;var SYSTEM_COLUMNS=["id","created_at","created_by","updated_at","updated_by","deleted_at"];var SQL_KEYWORDS=new Set("abort action add after all alter always analyze and as asc attach autoincrement before begin between by cascade case cast check collate column commit conflict constraint create cross current current_date current_time current_timestamp database default deferrable deferred delete desc detach distinct do drop each else end escape except exclude exclusive exists explain fail filter first following for foreign from full generated glob group groups having if ignore immediate in index indexed initially inner insert instead intersect into is isnull join key last left like limit match materialized natural no not nothing notnull null nulls of offset on or order others outer over partition plan pragma preceding primary query raise range recursive references regexp reindex release rename replace restrict returning right rollback row rows savepoint select set table temp temporary then ties to transaction trigger unbounded union unique update using vacuum values view virtual when where window with without rowid oid _rowid_".split(" "));var RESERVED_TABLE_PREFIXES=["sqlite_","d1_","cf_","one_"];var DataModelError=class extends Error{issues;constructor(issues){super(issues.length===1?issues[0]:`Veri modelinde ${issues.length} sorun var:
3
+ - ${issues.join("\n- ")}`);this.name="DataModelError";this.issues=issues}};var isRecord=v=>typeof v==="object"&&v!==null&&!Array.isArray(v);function nameIssue(name,kind){if(!DATA_NAME_RE.test(name))return`${kind} adı "${name}" geçersiz: küçük harfle başlamalı, yalnız küçük harf (a-z), rakam ve alt çizgi içermeli, en çok 40 karakter olmalı.`;if(name.includes("__")||name.endsWith("_"))return`${kind} adı "${name}" çift alt çizgi içeremez ve alt çizgiyle bitemez.`;if(SQL_KEYWORDS.has(name))return`${kind} adı "${name}" veritabanı dilinde ayrılmış bir sözcüktür; başka bir ad seçin (ör. "${name}_value").`;return null}function titleIssue(raw,where){if(!isRecord(raw))return`${where}: "title" { "tr": "…", "en": "…" } biçiminde olmalı.`;for(const lang of["tr","en"]){const v=raw[lang];if(typeof v!=="string"||v.trim()==="")return`${where}: "title.${lang}" boş olamaz (Türkçe ve İngilizce ad zorunlu).`;if(v.length>DATA_TITLE_MAX_LENGTH)return`${where}: "title.${lang}" en çok ${DATA_TITLE_MAX_LENGTH} karakter olabilir.`}for(const k of Object.keys(raw))if(k!=="tr"&&k!=="en")return`${where}: "title" içinde bilinmeyen dil "${k}" (yalnız "tr" ve "en").`;return null}var FIELD_KEYS=new Set(["type","title","required","unique","index","default","choices","maxLength","min","max"]);var ENTITY_KEYS=new Set(["title","fields","softDelete"]);var NOT_UNIQUE=new Set(["longText","boolean"]);var NOT_INDEXED=new Set(["longText"]);var NO_DEFAULT=new Set(["person","file"]);function moneyToMinor(raw){let text;if(typeof raw==="number"){if(!Number.isFinite(raw))return null;text=String(raw);if(/e/i.test(text))return null}else text=raw.trim();const m=/^(-)?(\d{1,16})(?:[.,](\d{1,2}))?$/.exec(text);if(!m)return null;const minor=Number(m[2])*100+Number((m[3]??"").padEnd(2,"0")||"0");if(!Number.isSafeInteger(minor))return null;return m[1]&&minor!==0?-minor:minor}function minorToMoney(minor){const abs=Math.abs(minor);const major=Math.floor(abs/100);const cents=String(abs%100).padStart(2,"0");return`${minor<0?"-":""}${major}.${cents}`}var DATE_RE=/^(\d{4})-(\d{2})-(\d{2})$/;var DATETIME_RE=/^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}(?::\d{2}(?:\.\d{1,3})?)?(?:Z|[+-]\d{2}:\d{2})$/;function isCalendarDate(text){const m=DATE_RE.exec(text);if(!m)return false;const[y,mo,d]=[Number(m[1]),Number(m[2]),Number(m[3])];const date=new Date(Date.UTC(y,mo-1,d));return y>=1&&date.getUTCFullYear()===y&&date.getUTCMonth()===mo-1&&date.getUTCDate()===d}function normalizeDateTime(text){if(!DATETIME_RE.test(text)||!isCalendarDate(text.slice(0,10)))return null;const t=Date.parse(text);if(!Number.isFinite(t))return null;return new Date(t).toISOString()}function defaultIssue(field2,value2,where){const bad=expected=>`${where}: "default" ${expected} olmalı.`;switch(field2.type){case"text":case"longText":return typeof value2==="string"?null:bad("metin");case"number":return typeof value2==="number"&&Number.isFinite(value2)?null:bad("sayı");case"money":return(typeof value2==="number"||typeof value2==="string")&&moneyToMinor(value2)!==null?null:bad("en çok iki ondalık basamaklı tutar (ör. 12.50)");case"boolean":return typeof value2==="boolean"?null:bad("true ya da false");case"date":return typeof value2==="string"&&isCalendarDate(value2)?null:bad("YYYY-AA-GG biçiminde tarih");case"datetime":return typeof value2==="string"&&normalizeDateTime(value2)!==null?null:bad("bölgeli ISO tarih-saat (ör. 2026-01-31T09:00:00Z)");case"choice":return typeof value2==="string"&&field2.choices.some(c=>c.value===value2)?null:bad('seçeneklerden birinin "value" değeri');default:return`${where}: bu alan türünde varsayılan verilemez.`}}function parseDataModel(raw){const issues=[];if(!isRecord(raw))throw new DataModelError(['one.data.json bir JSON nesnesi olmalı: { "version": 1, "entities": { … } }.']);if(raw.version!==1)issues.push('"version" 1 olmalı.');for(const k of Object.keys(raw))if(k!=="version"&&k!=="entities"&&k!=="$schema")issues.push(`Bilinmeyen üst alan "${k}" (yalnız "version" ve "entities").`);if(!isRecord(raw.entities)){issues.push('"entities" bir nesne olmalı: { "<varlık adı>": { "title": …, "fields": { … } } }.');throw new DataModelError(issues)}const entityNames=Object.keys(raw.entities);if(entityNames.length>DATA_MAX_ENTITIES)issues.push(`En çok ${DATA_MAX_ENTITIES} varlık tanımlanabilir (${entityNames.length}).`);const entities=[];for(const entityName of entityNames){const where=`"${entityName}"`;const ni=nameIssue(entityName,"varlık");if(ni)issues.push(ni);else if(RESERVED_TABLE_PREFIXES.some(p=>entityName.startsWith(p)))issues.push(`varlık adı "${entityName}" platform için ayrılmış bir önekle başlıyor (${RESERVED_TABLE_PREFIXES.join(", ")}).`);const e=raw.entities[entityName];if(!isRecord(e)){issues.push(`${where}: varlık bir nesne olmalı.`);continue}for(const k of Object.keys(e))if(!ENTITY_KEYS.has(k))issues.push(`${where}: bilinmeyen alan "${k}" (title · fields · softDelete).`);const ti=titleIssue(e.title,where);if(ti)issues.push(ti);if(e.softDelete!==void 0&&typeof e.softDelete!=="boolean")issues.push(`${where}: "softDelete" true ya da false olmalı.`);if(!isRecord(e.fields)){issues.push(`${where}: "fields" bir nesne olmalı.`);continue}const fieldNames=Object.keys(e.fields);if(fieldNames.length===0)issues.push(`${where}: en az bir alan tanımlanmalı.`);if(fieldNames.length>DATA_MAX_FIELDS)issues.push(`${where}: bir varlıkta en çok ${DATA_MAX_FIELDS} alan olabilir (${fieldNames.length}).`);const fields=[];const columns=new Set(SYSTEM_COLUMNS);for(const fieldName of fieldNames){const fw=`${where}.${fieldName}`;const fni=nameIssue(fieldName,"alan");if(fni)issues.push(fni);else if(SYSTEM_COLUMNS.includes(fieldName))issues.push(`alan adı "${fieldName}" platformun otomatik kolonudur (${SYSTEM_COLUMNS.join(", ")}); başka bir ad seçin.`);else if(fieldName.endsWith("_minor"))issues.push(`alan adı "${fieldName}" "_minor" ile bitemez (para alanlarının kolon eki).`);else if(fieldName.endsWith(FOLD_COLUMN_SUFFIX))issues.push(`alan adı "${fieldName}" "${FOLD_COLUMN_SUFFIX}" ile bitemez (metin alanlarının arama kolonu eki).`);const f=e.fields[fieldName];if(!isRecord(f)){issues.push(`${fw}: alan bir nesne olmalı.`);continue}for(const k of Object.keys(f))if(!FIELD_KEYS.has(k))issues.push(`${fw}: bilinmeyen özellik "${k}".`);const type=f.type;if(typeof type!=="string"||!DATA_FIELD_TYPES.includes(type)){issues.push(`${fw}: "type" şunlardan biri olmalı: ${DATA_FIELD_TYPES.join(", ")}.`);continue}const t=type;const fti=titleIssue(f.title,fw);if(fti)issues.push(fti);for(const flag of["required","unique","index"])if(f[flag]!==void 0&&typeof f[flag]!=="boolean")issues.push(`${fw}: "${flag}" true ya da false olmalı.`);const unique=f.unique===true;if(unique&&NOT_UNIQUE.has(t))issues.push(`${fw}: "${t}" türündeki alan tekil (unique) olamaz.`);if(f.index===true&&NOT_INDEXED.has(t))issues.push(`${fw}: "${t}" türündeki alana dizin (index) eklenemez.`);let choices=[];if(t==="choice"){if(!Array.isArray(f.choices)||f.choices.length===0)issues.push(`${fw}: seçim alanında "choices" en az bir seçenek içermeli.`);else if(f.choices.length>DATA_MAX_CHOICES)issues.push(`${fw}: en çok ${DATA_MAX_CHOICES} seçenek olabilir.`);else{const seen=new Set;f.choices.forEach((c,i)=>{const cw=`${fw}.choices[${i}]`;if(!isRecord(c)||typeof c.value!=="string"||!DATA_CHOICE_VALUE_RE.test(c.value)){issues.push(`${cw}: "value" küçük harf, rakam, "_" ya da "-" içeren en çok 40 karakterlik bir kod olmalı (ör. "beklemede").`);return}if(seen.has(c.value))issues.push(`${cw}: "${c.value}" değeri tekrar ediyor.`);seen.add(c.value);const cti=titleIssue(c.title,cw);if(cti)issues.push(cti);for(const k of Object.keys(c))if(k!=="value"&&k!=="title")issues.push(`${cw}: bilinmeyen özellik "${k}".`);if(!cti)choices.push({value:c.value,title:{tr:c.title.tr,en:c.title.en}})})}}else if(f.choices!==void 0)issues.push(`${fw}: "choices" yalnız seçim (choice) alanında kullanılır.`);let maxLength;if(t==="text"||t==="longText"){const cap=t==="text"?TEXT_MAX_LENGTH:LONG_TEXT_MAX_LENGTH;if(f.maxLength===void 0)maxLength=t==="text"?TEXT_DEFAULT_MAX_LENGTH:LONG_TEXT_DEFAULT_MAX_LENGTH;else if(typeof f.maxLength!=="number"||!Number.isInteger(f.maxLength)||f.maxLength<1||f.maxLength>cap)issues.push(`${fw}: "maxLength" 1 ile ${cap} arasında bir tamsayı olmalı.`);else maxLength=f.maxLength}else if(f.maxLength!==void 0)issues.push(`${fw}: "maxLength" yalnız metin alanlarında kullanılır.`);let min;let max;for(const bound of["min","max"]){const v=f[bound];if(v===void 0)continue;if(t!=="number"&&t!=="money"){issues.push(`${fw}: "${bound}" yalnız sayı ve para alanlarında kullanılır.`);continue}if(typeof v!=="number"||!Number.isFinite(v)||t==="money"&&moneyToMinor(v)===null)issues.push(`${fw}: "${bound}" geçerli bir sayı olmalı${t==="money"?" (en çok iki ondalık basamak)":""}.`);else if(bound==="min")min=v;else max=v}if(min!==void 0&&max!==void 0&&min>max)issues.push(`${fw}: "min" "max"tan büyük olamaz.`);let def;if(f.default!==void 0){if(NO_DEFAULT.has(t))issues.push(`${fw}: "${t}" türündeki alanda varsayılan verilemez.`);else if(unique)issues.push(`${fw}: tekil (unique) alanda varsayılan verilemez (her kayıt aynı değeri alırdı).`);else{const di=defaultIssue({type:t,choices},f.default,fw);if(di)issues.push(di);else if(t==="money")def=minorToMoney(moneyToMinor(f.default));else if(t==="datetime")def=normalizeDateTime(f.default);else def=f.default;if(def!==void 0&&(t==="number"||t==="money")){const n=t==="money"?moneyToMinor(def)/100:def;if(min!==void 0&&n<min||max!==void 0&&n>max)issues.push(`${fw}: "default" min/max aralığının dışında.`)}if(def!==void 0&&maxLength!==void 0&&[...String(def)].length>maxLength)issues.push(`${fw}: "default" "maxLength" sınırını aşıyor.`)}}const column=t==="money"?`${fieldName}_minor`:fieldName;if(columns.has(column))issues.push(`${fw}: kolon adı "${column}" başka bir kolonla çakışıyor.`);columns.add(column);const foldColumn=t==="text"||t==="longText"?foldColumnName(column):void 0;if(foldColumn!==void 0){if(columns.has(foldColumn))issues.push(`${fw}: arama kolonu "${foldColumn}" başka bir kolonla çakışıyor.`);columns.add(foldColumn)}if(!fti)fields.push({name:fieldName,column,type:t,title:{tr:f.title.tr,en:f.title.en},required:f.required===true,unique,index:f.index===true,default:def,choices,maxLength,foldColumn,min,max})}if(columns.size>DATA_MAX_COLUMNS)issues.push(`${where}: tabloda en çok ${DATA_MAX_COLUMNS} kolon olabilir; bu varlık ${columns.size} kolon gerektiriyor (${SYSTEM_COLUMNS.length} otomatik + alanlar + her metin alanı için bir arama kolonu). Alanları iki varlığa bölün.`);if(!ti)entities.push({name:entityName,title:{tr:e.title.tr,en:e.title.en},softDelete:e.softDelete!==false,fields})}if(issues.length>0)throw new DataModelError(issues);return{version:1,entities}}function toModelInput(model){const entities={};for(const e of model.entities){const fields={};for(const f of e.fields){const out={type:f.type,title:{...f.title}};if(f.required)out.required=true;if(f.unique)out.unique=true;if(f.index)out.index=true;if(f.default!==void 0)out.default=f.default;if(f.choices.length>0)out.choices=f.choices.map(c=>({value:c.value,title:{...c.title}}));if(f.maxLength!==void 0)out.maxLength=f.maxLength;if(f.min!==void 0)out.min=f.min;if(f.max!==void 0)out.max=f.max;fields[f.name]=out}entities[e.name]={title:{...e.title},fields,softDelete:e.softDelete}}return{version:1,entities}}var DATA_SNAPSHOT_FILE=".one-data-snapshot.json";var DATA_MODEL_FILE="one.data.json";var DATA_TYPES_FILE="src/data/types.ts";var MIGRATION_NAME_RE=/^(\d{4})_[a-z0-9_]+\.sql$/;var q=name=>`"${name}"`;function sqlType(f){switch(f.type){case"number":return"REAL";case"money":case"boolean":return"INTEGER";default:return"TEXT"}}function literal(f,value2){if(f.type==="boolean")return value2===true?"1":"0";if(f.type==="money")return String(moneyToMinor(value2));if(f.type==="number")return String(value2);return`'${String(value2).replace(/'/g,"''")}'`}function checkClause(f){if(f.type==="boolean")return` CHECK (${q(f.column)} IN (0, 1))`;return""}function foldColumnDef(f){return f.foldColumn===void 0?null:`${q(f.foldColumn)} TEXT`}function columnDef(f,adding){let sql=`${q(f.column)} ${sqlType(f)}`;if(f.required){sql+=" NOT NULL";if(adding&&f.default!==void 0)sql+=` DEFAULT ${literal(f,f.default)}`}return sql+checkClause(f)}var indexName=(kind,entity,field2)=>`${kind}_${entity}__${field2}`;function indexSql(e,f){if(f.unique)return`CREATE UNIQUE INDEX ${q(indexName("ux",e.name,f.name))} ON ${q(e.name)} (${q(f.column)}) WHERE "deleted_at" IS NULL;`;if(f.index)return`CREATE INDEX ${q(indexName("ix",e.name,f.name))} ON ${q(e.name)} (${q(f.column)});`;return null}function createEntitySql(e){const cols=['"id" TEXT NOT NULL PRIMARY KEY','"created_at" TEXT NOT NULL','"created_by" TEXT NOT NULL','"updated_at" TEXT NOT NULL','"updated_by" TEXT NOT NULL','"deleted_at" TEXT',...e.fields.flatMap(f=>{const fold=foldColumnDef(f);return fold===null?[columnDef(f,false)]:[columnDef(f,false),fold]})];const out=[`CREATE TABLE ${q(e.name)} (
4
+ ${cols.join(",\n ")}
5
+ );`];out.push(`CREATE INDEX ${q(`ix_${e.name}__created_at`)} ON ${q(e.name)} ("created_at") WHERE "deleted_at" IS NULL;`);for(const f of e.fields){const ix=indexSql(e,f);if(ix)out.push(ix)}return out}var CURRENT={foldColumns:true,choiceChecks:new Set};function diffDataModels(prev,next,legacy=CURRENT){const statements=[];const changes=[];const errors=[];const prevEntities=new Map((prev?.entities??[]).map(e=>[e.name,e]));const nextNames=new Set(next.entities.map(e=>e.name));for(const old of prevEntities.values())if(!nextNames.has(old.name))errors.push(`"${old.name}" varlığı modelden kaldırılmış. Tablo ve verisi otomatik silinmez; varlığı one.data.json'a geri ekleyin (kullanmıyorsanız ekranlardan kaldırmanız yeterli).`);for(const e of next.entities){const old=prevEntities.get(e.name);if(!old){statements.push(`-- ${e.name}: ${e.title.tr}`,...createEntitySql(e));changes.push({kind:"create-table",entity:e.name});continue}const oldFields=new Map(old.fields.map(f=>[f.name,f]));const nextFieldNames=new Set(e.fields.map(f=>f.name));for(const of of old.fields)if(!nextFieldNames.has(of.name))errors.push(`"${e.name}.${of.name}" alanı kaldırılmış ya da adı değiştirilmiş. Kolon ve verisi otomatik silinmez/yeniden adlandırılmaz; alanı geri ekleyin (yeni bir ad gerekiyorsa yeni bir alan açın, eskisini kullanmayı bırakın).`);const added=[];for(const f of e.fields){const of=oldFields.get(f.name);const where=`"${e.name}.${f.name}"`;if(!of){if(f.required&&f.default===void 0)errors.push(`${where} zorunlu bir alan olarak eklenmiş ama varsayılanı yok: var olan kayıtlar bu alanı boş bırakamaz. Ya "default" verin ya da alanı isteğe bağlı ekleyin.`);if(f.unique&&f.default!==void 0)errors.push(`${where} tekil ve varsayılanlı eklenemez (var olan kayıtlar aynı değeri alırdı).`);if(f.unique&&f.required)errors.push(`${where} var olan bir tabloya tekil VE zorunlu eklenemez; isteğe bağlı tekil ekleyin.`);added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${columnDef(f,true)};`);const fold=foldColumnDef(f);if(fold)added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${fold};`);const ix=indexSql(e,f);if(ix)added.push(ix);changes.push({kind:"add-column",entity:e.name,field:f.name});continue}if(of.type!==f.type)errors.push(`${where} alanının türü "${of.type}" → "${f.type}" değiştirilmiş; tür değişikliği desteklenmez (yeni bir alan açın).`);if(of.required!==f.required)errors.push(`${where} alanı ${f.required?"zorunlu yapılmış":"isteğe bağlı yapılmış"}; var olan kolonun bu kuralı değiştirilemez (yeni bir alan açın ya da eski hâle döndürün).`);if(of.unique!==f.unique)errors.push(`${where} alanının tekillik (unique) kuralı değiştirilmiş; var olan kolonda desteklenmez.`);if(of.index&&!f.index)errors.push(`${where} alanının dizini (index) kaldırılmış; dizin silme desteklenmez, "index": true kalmalı.`);if(!of.index&&f.index&&!f.unique){added.push(indexSql(e,f));changes.push({kind:"add-index",entity:e.name,field:f.name})}if(of.type===f.type&&f.type==="choice"){const nextValues=new Set(f.choices.map(c=>c.value));const removed=of.choices.map(c=>c.value).filter(v=>!nextValues.has(v));const oldValues=new Set(of.choices.map(c=>c.value));const addedValues=f.choices.map(c=>c.value).filter(v=>!oldValues.has(v));if(removed.length>0)errors.push(`${where} seçim alanından değer kaldırılmış (${removed.join(", ")}). Var olan kayıtlar bu değeri taşıyabilir; değeri listede bırakın (artık kullanılmayacaksa ekranlardan gizleyin). Yalnız görünen adlar (title) ve sıra değişebilir.`);else if(addedValues.length>0&&legacy.choiceChecks.has(`${e.name}.${f.name}`))errors.push(`${where} seçim alanına değer eklenmiş (${addedValues.join(", ")}), ama bu alanın tablosu eski bir sürümle kurulmuş ve veritabanında değerleri bir kısıtla sabitlenmiş; kısıt ekleme yapan bir migration ile kaldırılamaz. Yeni değerler için yeni bir seçim alanı açın ya da platform ekibine danışın.`)}if(of.type===f.type&&f.foldColumn!==void 0&&!legacy.foldColumns){added.push(`ALTER TABLE ${q(e.name)} ADD COLUMN ${foldColumnDef(f)};`);changes.push({kind:"add-search-column",entity:e.name,field:f.name})}}if(added.length>0)statements.push(`-- ${e.name}: ${e.title.tr}`,...added)}return{statements,changes,errors}}var DATA_SNAPSHOT_VERSION=2;var isLegacyKey=v=>typeof v==="string"&&/^[a-z][a-z0-9_]*\.[a-z][a-z0-9_]*$/.test(v);function readSnapshot(raw){if(raw===null)return null;const broken=why=>new DataModelError([`migrations/${DATA_SNAPSHOT_FILE} ${why} — dosya elle düzenlenmez; git'teki son hâline döndürün.`]);let json;try{json=JSON.parse(raw)}catch{throw new DataModelError([`migrations/${DATA_SNAPSHOT_FILE} okunamadı (geçerli JSON değil). Bu dosya elle düzenlenmez; git'teki son hâline döndürün.`])}const doc2=json??{};let model;try{model=parseDataModel(doc2.model)}catch(e){if(e instanceof DataModelError)throw broken(`geçersiz: ${e.issues.join(" · ")}`);throw e}if(doc2.v===void 0||doc2.v===1){const checks=new Set;for(const e of model.entities)for(const f of e.fields)if(f.type==="choice")checks.add(`${e.name}.${f.name}`);return{model,legacy:{foldColumns:false,choiceChecks:checks}}}if(doc2.v!==DATA_SNAPSHOT_VERSION)throw broken(`bu SDK'nın tanımadığı bir sürümde (v: ${JSON.stringify(doc2.v)}); SDK'yı güncelleyin`);const raw2=doc2.choiceChecks??[];if(!Array.isArray(raw2)||!raw2.every(isLegacyKey))throw broken('geçersiz: "choiceChecks" "<varlık>.<alan>" listesi olmalı');return{model,legacy:{foldColumns:true,choiceChecks:new Set(raw2)}}}function renderSnapshot(model,legacy=CURRENT){const present=new Set(model.entities.flatMap(e=>e.fields.filter(f=>f.type==="choice").map(f=>`${e.name}.${f.name}`)));const choiceChecks=[...legacy.choiceChecks].filter(k=>present.has(k)).sort();return JSON.stringify({note:"one data generate tarafından yazılır; elle düzenlemeyin. Bir sonraki üretimin farkı bu modele göre alınır.",v:DATA_SNAPSHOT_VERSION,...choiceChecks.length>0?{choiceChecks}:{},model:toModelInput(model)},null,2)+"\n"}function nextMigrationName(existing2){let max=0;for(const name of existing2){const m=MIGRATION_NAME_RE.exec(name);if(m)max=Math.max(max,Number(m[1]))}if(max>=9999)throw new DataModelError(["migrations/ altında 9999 numarasına ulaşılmış; yeni numara verilemiyor."]);return`${String(max+1).padStart(4,"0")}_one_data.sql`}function tablesCreatedIn(sql){const text=sql.replace(/--[^\n]*/g," ").replace(/\/\*[\s\S]*?\*\//g," ");const out=new Set;const re=/\bcreate\s+(?:temp\s+|temporary\s+|virtual\s+)?table\s+(?:if\s+not\s+exists\s+)?(?:main\s*\.\s*)?["`[]?([A-Za-z0-9_]+)["`\]]?/gi;for(const m of text.matchAll(re))out.add(m[1].toLowerCase());return out}function planDataGeneration(input){const next=parseDataModel(input.model);const prev=readSnapshot(input.snapshot);const legacy=prev?.legacy??CURRENT;const diff=diffDataModels(prev?.model??null,next,legacy);const issues=[...diff.errors];const handwritten=new Set;for(const m of input.existing)for(const t of tablesCreatedIn(m.sql))handwritten.add(t);for(const c of diff.changes)if(c.kind==="create-table"&&handwritten.has(c.entity))issues.push(`"${c.entity}" adında bir tablo elle yazılmış bir migration'da zaten kuruluyor; varlığa başka bir ad verin.`);if(issues.length>0)throw new DataModelError(issues);const migration=diff.statements.length===0?null:{name:nextMigrationName(input.existing.map(m=>m.name)),sql:"-- one data generate tarafından one.data.json'dan üretildi.\n-- ⛔ Uygulandıktan sonra bu dosya DEĞİŞTİRİLMEZ: model değişince bir sonraki numarayla yeni dosya üretilir.\n-- Her kurulumun kendi veritabanı vardır; tablolarda portal kolonu yoktur. Kişi = kullanıcı kimliği (sub).\n"+diff.statements.join("\n")+"\n"};return{migration,types:renderTypes(next),snapshot:renderSnapshot(next,legacy),changes:diff.changes}}var pascal=name=>name.split("_").filter(Boolean).map(p=>p[0].toUpperCase()+p.slice(1)).join("");function tsValueType(f){switch(f.type){case"number":return"number";case"boolean":return"boolean";case"choice":return f.choices.map(c=>JSON.stringify(c.value)).join(" | ");default:return"string"}}function tsInputType(f){if(f.type==="money")return"string | number";if(f.type==="number")return"number | string";return tsValueType(f)}var doc=f=>{const unit=f.type==="money"?' — tutar, ondalıklı metin ("1234.50")':f.type==="date"?" — YYYY-AA-GG":f.type==="datetime"?" — ISO tarih-saat (UTC)":f.type==="person"?" — kişi (kullanıcı kimliği, sub)":f.type==="file"?" — dosya anahtarı":"";return` /** ${f.title.tr}${unit} */
6
+ `};function renderTypes(model){const lines=["// one data generate tarafından one.data.json'dan üretilir; elle düzenlemeyin (değişiklik one.data.json'a yazılır).","import type { OneDataModel } from '@devoplus/one-sdk'","",`export type EntityName = ${model.entities.length>0?model.entities.map(e=>JSON.stringify(e.name)).join(" | "):"never"}`,""];for(const e of model.entities){const name=pascal(e.name);lines.push(`/** ${e.title.tr} — API'nin döndürdüğü kayıt. */`,`export interface ${name}Record {`);lines.push(" id: string"," created_at: string"," created_by: string"," updated_at: string"," updated_by: string");for(const f of e.fields)lines.push(doc(f).trimEnd(),` ${f.name}: ${tsValueType(f)}${f.required?"":" | null"}`);lines.push("}","",`/** ${e.title.tr} — kayıt oluştururken gönderilen alanlar (güncellemede hepsi isteğe bağlıdır). */`,`export interface ${name}Input {`);for(const f of e.fields){const optional=!f.required||f.default!==void 0;lines.push(` ${f.name}${optional?"?":""}: ${tsInputType(f)}${f.required?"":" | null"}`)}lines.push("}","")}lines.push("/** Doğrulanmış veri modeli — createCrudHandler({ model: dataModel, … }) ile kullanılır. */",`export const dataModel: OneDataModel = ${JSON.stringify(toModelInput(model),null,2)}`,"");return lines.join("\n")}var SERVICES=["database","files","cache"];var DEFAULT_SDK_SPEC="^0.2.0-next.0";var DEFAULT_VERSION="0.1.0";var DEV_DEPENDENCIES={typescript:"^7.0.2",vitest:"^5.0.4"};var SEMVER_RE=/^\d+\.\d+\.\d+$/;var LOCALIZED_TEXT_MAX=120;var DESCRIPTION_SUFFIX_TR=" — Devoplus One uygulaması";var DESCRIPTION_SUFFIX_EN=" — Devoplus One app";var PERMISSION_NAME_SUFFIX_TR=" uygulamasını kullan";var PERMISSION_NAME_PREFIX_EN="Use the ";var PERMISSION_NAME_SUFFIX_EN=" app";var NAME_MAX=LOCALIZED_TEXT_MAX-Math.max(DESCRIPTION_SUFFIX_TR.length,DESCRIPTION_SUFFIX_EN.length,PERMISSION_NAME_SUFFIX_TR.length,PERMISSION_NAME_PREFIX_EN.length+PERMISSION_NAME_SUFFIX_EN.length);var TSCONFIG={compilerOptions:{target:"ES2022",module:"ESNext",moduleResolution:"Bundler",lib:["ES2022","WebWorker"],types:[],strict:true,noUncheckedIndexedAccess:true,exactOptionalPropertyTypes:true,noImplicitOverride:true,noFallthroughCasesInSwitch:true,isolatedModules:true,verbatimModuleSyntax:true,skipLibCheck:true,noEmit:true,allowImportingTsExtensions:true,resolveJsonModule:true},include:["src"]};var GITIGNORE="node_modules/\ndist/\n.one/\n.dev.vars*\n.env*\n";function template(name){return readFileSync(new URL(`../templates/${name}`,import.meta.url),"utf8")}function pascalCase(slug2){return slug2.split("-").filter(Boolean).map((part,index)=>{if(index>0&&(/^[0-9]/.test(part)||part.startsWith("x")))return`X${part}`;return part.charAt(0).toUpperCase()+part.slice(1)}).join("")}function titleCase(slug2){return slug2.split("-").filter(Boolean).map(part=>part.charAt(0).toUpperCase()+part.slice(1)).join(" ")}function permissionCodeFor(slug2){return`One.${pascalCase(slug2)}.Use`}function requireText(value2,label,max){const trimmed=value2.trim();if(trimmed.length===0)throw new Error(`${label} boş olamaz`);if(trimmed.length>max)throw new Error(`${label} en çok ${max} karakter olabilir (${trimmed.length})`);return trimmed}function buildScaffold(options){const slug2=options.slug.trim();if(!isValidSlug(slug2))throw new Error(`slug geçersiz: "${slug2}" — küçük harf/rakam ve tek tire, harfle başlar, en çok ${SLUG_MAX} karakter`);const nameTr2=requireText(options.nameTr,"ad (TR)",NAME_MAX);const nameEn2=options.nameEn===void 0?void 0:requireText(options.nameEn,"ad (EN)",NAME_MAX);const version2=options.version??DEFAULT_VERSION;if(!SEMVER_RE.test(version2))throw new Error(`sürüm semver (x.y.z) olmalı: "${version2}"`);const sdkSpec2=options.sdkSpec?.trim()||DEFAULT_SDK_SPEC;const services=new Set(["database",...options.services??[]]);for(const s of services)if(!SERVICES.includes(s))throw new Error(`bilinmeyen servis: ${s} (database · files · cache)`);const withFiles=services.has("files");const withCache=services.has("cache");const scopes2=[...new Set((options.scopes??[]).map(s=>s.trim()).filter(Boolean))];for(const scope of scopes2)if(!SCOPE_RE.test(scope))throw new Error(`scope biçimi geçersiz: "${scope}" (<modül>.<read|write|manage|execute>)`);if(scopes2.includes(DEXA_READ_SCOPE))throw new Error(`"${DEXA_READ_SCOPE}" scope'u iskeletle verilemez: one.manifest.json'da services.dexa.queryKeys ile birlikte beyan edilir ve anahtarlar uydurulmaz. İskeleti bu scope olmadan üretin; sonra manifest'e scope'u ve Devoplus Console kayıt defterindeki anahtarları ekleyin.`);if(scopes2.includes(DEXA_WRITE_SCOPE))throw new Error(`"${DEXA_WRITE_SCOPE}" scope'u iskeletle verilemez: one.manifest.json'da services.dexa.commandKeys ile birlikte beyan edilir ve ERP işlem anahtarları uydurulmaz. İskeleti bu scope olmadan üretin; sonra manifest'e scope'u ve Devoplus Console kayıt defterindeki işlem anahtarlarını ekleyin.`);const permissionCode3=options.permissionCode?.trim()||permissionCodeFor(slug2);if(!PERMISSION_CODE_RE.test(permissionCode3))throw new Error(`izin kodu geçersiz: "${permissionCode3}" — biçim One.<Ad>.<Ad> (büyük harfle başlayan harf/rakam parçaları), örnek ${permissionCodeFor(slug2)}`);const packageName=`one-app-${slug2}`;const templateName=options.template??DEFAULT_TEMPLATE;if(!isTemplateName(templateName))throw new Error(`bilinmeyen şablon: ${String(templateName)} (${TEMPLATE_NAMES.join(" · ")})`);if(templateName!=="blank")return buildTemplateApp({templateName,slug:slug2,nameTr:nameTr2,nameEn:nameEn2,version:version2,sdkSpec:sdkSpec2,services,scopes:scopes2,permissionCode:permissionCode3,packageName,ownerEmail:options.ownerEmail.trim()});const manifest={schemaVersion:1,app:{slug:slug2,name:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},version:version2,description:nameEn2?{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,en:`${nameEn2}${DESCRIPTION_SUFFIX_EN}`}:{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`},owner:{email:options.ownerEmail.trim()}},entry:"src/index.ts",scopes:scopes2,services:{database:true,files:withFiles,cache:withCache},menu:{title:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},icon:"pi pi-box"},navigation:[{path:"/",label:{tr:"Ana sayfa",en:"Home"}}],actingSessions:"read",permission:{code:permissionCode3,name:nameEn2?{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`,en:`${PERMISSION_NAME_PREFIX_EN}${nameEn2}${PERMISSION_NAME_SUFFIX_EN}`}:{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`}},limits:{cpuMs:50,subRequests:10},agent:{summary:nameEn2?{tr:`${nameTr2}: kullanıcının kişisel notları.`,en:`${nameEn2}: the user's personal notes.`}:{tr:`${nameTr2}: kullanıcının kişisel notları.`},tools:[{name:"list-notes",title:{tr:"Notlarımı listele",en:"List my notes"},description:{tr:"Kullanıcının kendi notlarını en yenisi önce döndürür. Salt okunur; hiçbir şeyi değiştirmez.",en:"Returns the user's own notes, newest first. Read-only; changes nothing."},readOnly:true,permission:permissionCode3,input:{type:"object",properties:{limit:{type:"integer",minimum:1,maximum:50,description:"En çok kaç not (varsayılan 10)"}}}}]}};const validation=validateManifest(manifest);if(!validation.ok){const detail=validation.errors.map(e=>`${e.path}: ${e.message}`).join("; ");throw new Error(`üretilen manifest geçersiz — ${detail}`)}const vars={slug:slug2,nameTr:nameTr2,nameEn:nameEn2??"",nameTrJs:JSON.stringify(nameTr2),nameEnJs:JSON.stringify(nameEn2??nameTr2),packageName,permissionCode:permissionCode3,ownerEmail:options.ownerEmail.trim(),scopesList:scopes2.length>0?scopes2.map(s=>`\`${s}\``).join(", "):"",files:withFiles,cache:withCache,hasScopes:scopes2.length>0};const packageJson={name:packageName,version:version2,private:true,description:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,type:"module",scripts:{"type-check":"tsc -p tsconfig.json",test:"vitest run",build:"one build",migrate:"one migrate",dev:"one dev"},dependencies:{"@devoplus/one-sdk":sdkSpec2},devDependencies:{...DEV_DEPENDENCIES}};const json=value2=>JSON.stringify(value2,null,2)+"\n";const files=[{path:"one.manifest.json",content:json(manifest)},{path:"package.json",content:json(packageJson)},{path:"tsconfig.json",content:json(TSCONFIG)},{path:".gitignore",content:GITIGNORE},{path:"src/index.ts",content:renderTemplate(template("index.ts.tpl"),vars)},{path:"src/shell.ts",content:renderTemplate(template("shell.ts.tpl"),vars)},{path:"src/agent.ts",content:renderTemplate(template("agent.ts.tpl"),vars)},{path:"src/markdown.d.ts",content:renderTemplate(template("markdown.d.ts.tpl"),vars)},{path:"AGENT_GUIDE.md",content:renderTemplate(template("AGENT_GUIDE.md.tpl"),vars)},{path:"src/shell.test.ts",content:renderTemplate(template("shell.test.ts.tpl"),vars)},{path:"migrations/0001_init.sql",content:renderTemplate(template("0001_init.sql.tpl"),vars)},{path:"AGENTS.md",content:renderTemplate(template("AGENTS.md.tpl"),vars)},{path:"README.md",content:renderTemplate(template("README.md.tpl"),vars)}];return{files,manifest,packageName,permissionCode:permissionCode3,template:"blank"}}var TEMPLATES_ROOT=new URL("../templates/",import.meta.url);function walk(dir,prefix=""){const out=[];for(const name of readdirSync(dir).sort()){const rel=prefix?`${prefix}/${name}`:name;if(statSync(new URL(name,dir)).isDirectory())out.push(...walk(new URL(`${name}/`,dir),rel));else out.push(rel)}return out}function templateFiles(dir,vars,skip){return walk(dir).filter(rel=>!skip(rel)).map(rel=>{const raw=readFileSync(new URL(rel,dir),"utf8");return rel.endsWith(".tpl")?{path:rel.slice(0,-".tpl".length),content:renderTemplate(raw,vars)}:{path:rel,content:raw}})}function composeAgents(common,section){const at=common.indexOf("\n## ");if(at<0)throw new Error("AGENTS.md şablonunda ikinci düzey başlık yok");return`${common.slice(0,at+1)}${section.trimEnd()}
7
+
8
+ ${common.slice(at+1)}`}function buildTemplateApp(input){const{templateName,slug:slug2,nameTr:nameTr2,nameEn:nameEn2,version:version2,sdkSpec:sdkSpec2,permissionCode:permissionCode3,packageName,ownerEmail:ownerEmail2}=input;const permissionPrefix=permissionCode3.slice(0,permissionCode3.lastIndexOf(".")+1);const baseVars={slug:slug2,nameTr:nameTr2,nameEn:nameEn2??"",nameTrJs:JSON.stringify(nameTr2),nameEnJs:JSON.stringify(nameEn2??nameTr2),packageName,permissionCode:permissionCode3,permissionPrefix,ownerEmail:ownerEmail2,templateName};const dir=new URL(`${templateName}/`,TEMPLATES_ROOT);const fragment=JSON.parse(renderTemplate(readFileSync(new URL("manifest.json.tpl",dir),"utf8"),baseVars));const services=new Set(input.services);for(const s of SERVICES)if(fragment.services[s])services.add(s);const scopes2=[...new Set([...fragment.scopes,...input.scopes])];const withFiles=services.has("files");const withCache=services.has("cache");const withMail=scopes2.includes("mail.execute");const manifest={schemaVersion:1,app:{slug:slug2,name:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},version:version2,description:nameEn2?{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,en:`${nameEn2}${DESCRIPTION_SUFFIX_EN}`}:{tr:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`},owner:{email:ownerEmail2}},entry:"src/index.ts",scopes:scopes2,services:{database:true,files:withFiles,cache:withCache},menu:{title:nameEn2?{tr:nameTr2,en:nameEn2}:{tr:nameTr2},icon:fragment.menuIcon},navigation:fragment.navigation,actingSessions:"read",permission:{code:permissionCode3,name:nameEn2?{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`,en:`${PERMISSION_NAME_PREFIX_EN}${nameEn2}${PERMISSION_NAME_SUFFIX_EN}`}:{tr:`${nameTr2}${PERMISSION_NAME_SUFFIX_TR}`}},...fragment.permissions?{permissions:fragment.permissions}:{},limits:{cpuMs:50,subRequests:10},agent:fragment.agent,...fragment.jobs?{jobs:fragment.jobs}:{}};const validation=validateManifest(manifest);if(!validation.ok){const detail=validation.errors.map(e=>`${e.path}: ${e.message}`).join("; ");throw new Error(`üretilen manifest geçersiz (${templateName}) — ${detail}`)}const vars={...baseVars,scopesList:scopes2.length>0?scopes2.map(s=>`\`${s}\``).join(", "):"",files:withFiles,cache:withCache,hasScopes:scopes2.length>0};const shared=templateFiles(new URL("_shared/files/",TEMPLATES_ROOT),vars,rel=>rel==="src/lib/mail.ts.tpl"&&!withMail||rel==="src/lib/files.ts.tpl"&&!withFiles);const own=templateFiles(new URL("files/",dir),vars,()=>false);const dataModel=own.find(f=>f.path===DATA_MODEL_FILE);if(!dataModel)throw new Error(`${templateName} şablonunda ${DATA_MODEL_FILE} yok`);const plan=planDataGeneration({model:JSON.parse(dataModel.content),snapshot:null,existing:[]});if(!plan.migration)throw new Error(`${templateName} veri modeli migration üretmedi`);const packageJson={name:packageName,version:version2,private:true,description:`${nameTr2}${DESCRIPTION_SUFFIX_TR}`,type:"module",scripts:{"type-check":"tsc -p tsconfig.json",test:"vitest run",build:"one build",migrate:"one migrate",dev:"one dev","data:generate":"one data generate"},dependencies:{"@devoplus/one-sdk":sdkSpec2},devDependencies:{...DEV_DEPENDENCIES}};const json=value2=>JSON.stringify(value2,null,2)+"\n";const files=[{path:"one.manifest.json",content:json(manifest)},{path:"package.json",content:json(packageJson)},{path:"tsconfig.json",content:json(TSCONFIG)},{path:".gitignore",content:GITIGNORE},{path:"src/markdown.d.ts",content:renderTemplate(template("markdown.d.ts.tpl"),vars)},...shared,...own,{path:`migrations/${plan.migration.name}`,content:plan.migration.sql},{path:`migrations/${DATA_SNAPSHOT_FILE}`,content:plan.snapshot},{path:DATA_TYPES_FILE,content:plan.types},{path:"AGENTS.md",content:composeAgents(renderTemplate(template("AGENTS.md.tpl"),vars),`${renderTemplate(readFileSync(new URL("AGENTS.template.md.tpl",dir),"utf8"),vars).trimEnd()}
9
+
10
+ ${renderTemplate(template("_shared/AGENTS.writes.md.tpl"),vars)}`)},{path:"README.md",content:renderTemplate(readFileSync(new URL("README.md.tpl",dir),"utf8"),vars).trimEnd()+"\n"+renderTemplate(template("_shared/README.tail.md.tpl"),vars)}];const seen=new Set;for(const f of files){if(seen.has(f.path))throw new Error(`${templateName} şablonunda aynı dosya iki kez üretildi: ${f.path}`);seen.add(f.path)}return{files,manifest,packageName,permissionCode:permissionCode3,template:templateName}}function fail(message){console.error(`create-devoplus-one-app: ${message}`);process.exit(1)}function arg(name){const i=process.argv.indexOf(`--${name}`);return i>=0?process.argv[i+1]:void 0}if(process.argv.includes("--list-templates")){for(const t of TEMPLATES)console.log(`${t.name.padEnd(12)} ${t.title.tr} — ${t.summary.tr}`);process.exit(0)}var[targetArg]=process.argv.slice(2).filter((a,i,all)=>!a.startsWith("--")&&(i===0||!all[i-1]?.startsWith("--")));if(!targetArg)fail("kullanım: create-devoplus-one-app <hedefDizin> --slug <slug> --owner <e-posta> [--template <ad> --name … --name-en … --services database,files,cache --scopes … --permission-code … --sdk … --version … --force]");var targetDir=resolve(targetArg);var slug=arg("slug")??basename(targetDir);var ownerEmail=arg("owner");if(!ownerEmail)fail("--owner <e-posta> gerekli (manifest app.owner.email; uydurulmaz)");var nameTr=arg("name")??titleCase(slug);var nameEn=arg("name-en");var servicesRaw=(arg("services")??"database").split(",").map(s=>s.trim()).filter(Boolean);for(const s of servicesRaw)if(!SERVICES.includes(s))fail(`--services bilinmeyen değer: ${s} (${SERVICES.join(" · ")})`);var scopes=(arg("scopes")??"").split(",").map(s=>s.trim()).filter(Boolean);var permissionCode2=arg("permission-code");var sdkArg=arg("sdk");var sdkSpec=sdkArg==="workspace"?"workspace:*":sdkArg;var version=arg("version");var force=process.argv.includes("--force");var templateArg=arg("template")??DEFAULT_TEMPLATE;if(!isTemplateName(templateArg))fail(`--template bilinmeyen şablon: ${templateArg} (${TEMPLATE_NAMES.join(" · ")}; liste: --list-templates)`);var existing=await stat(targetDir).catch(()=>null);if(existing&&!existing.isDirectory())fail(`hedef bir dizin değil: ${targetDir}`);if(existing&&(await readdir(targetDir)).length>0&&!force)fail(`hedef dizin boş değil: ${targetDir} (ezmek için --force)`);var scaffold;try{scaffold=buildScaffold({slug,nameTr,...nameEn!==void 0?{nameEn}:{},ownerEmail,services:servicesRaw,scopes,...permissionCode2!==void 0?{permissionCode:permissionCode2}:{},...sdkSpec!==void 0?{sdkSpec}:{},...version!==void 0?{version}:{},template:templateArg})}catch(error){fail(error instanceof Error?error.message:String(error))}for(const file of scaffold.files){const path=join2(targetDir,file.path);await mkdir(dirname(path),{recursive:true});await writeFile(path,file.content,"utf8");console.log(` + ${file.path}`)}console.log(`
11
+ ✓ ${scaffold.packageName} üretildi (şablon: ${scaffold.template}) → ${targetDir}
12
+
13
+ Sonraki adımlar:
14
+ 1. cd ${targetDir} && pnpm install && pnpm type-check && pnpm test
15
+ 2. AGENTS.md'yi okuyun (uygulamayı yazacak AI aracı da okur) ve README.md'deki kayıt/kurulum/yayın adımlarını izleyin.
16
+ 3. İzin kodu ${scaffold.permissionCode} — Devoplus Console › Devoplus One Uygulamaları ekranındaki kayıt bununla açılır.
17
+ `);
package/package.json CHANGED
@@ -1,6 +1,38 @@
1
1
  {
2
2
  "name": "create-devoplus-one-app",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
3
+ "version": "0.1.0-next.158",
4
+ "description": "Yeni bir Devoplus One uygulaması iskeleti üretir: manifest, SDK'lı giriş noktası, Portal köprülü kabuk, veritabanı migration'ı ve AGENTS.md",
5
+ "license": "UNLICENSED",
6
+ "type": "module",
7
+ "bin": {
8
+ "create-devoplus-one-app": "./dist/cli.mjs"
9
+ },
10
+ "files": [
11
+ "dist",
12
+ "templates",
13
+ "README.md"
14
+ ],
15
+ "publishConfig": {
16
+ "access": "public"
17
+ },
18
+ "engines": {
19
+ "node": ">=22"
20
+ },
21
+ "keywords": [
22
+ "devoplus",
23
+ "devoplus-one",
24
+ "create"
25
+ ],
26
+ "dependencies": {
27
+ "zod": "^4.6.7"
28
+ },
29
+ "devDependencies": {
30
+ "@types/node": "^26.6.5",
31
+ "esbuild": "0.28.2",
32
+ "@devoplus/one-manifest": "0.1.0"
33
+ },
34
+ "scripts": {
35
+ "type-check": "tsc -p tsconfig.json",
36
+ "build": "node scripts/build.mjs"
37
+ }
6
38
  }
@@ -0,0 +1,10 @@
1
+ -- {{nameTr}} — veritabanı şeması. Her kurulumun KENDİ veritabanı vardır (D-17): tabloda PortalId kolonu YOKTUR, izolasyon
2
+ -- veritabanı sınırındadır. Şema değişikliği: bu dosya DEĞİŞTİRİLMEZ; yeni numaralı dosya eklenir (0002_….sql).
3
+ -- one-ci migration'ları sürümle birlikte, sırayla uygular.
4
+ CREATE TABLE IF NOT EXISTS notes (
5
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
6
+ sub TEXT NOT NULL,
7
+ text TEXT NOT NULL,
8
+ created_at TEXT NOT NULL DEFAULT (strftime('%Y-%m-%dT%H:%M:%fZ', 'now'))
9
+ );
10
+ CREATE INDEX IF NOT EXISTS ix_notes_sub ON notes (sub);
@@ -0,0 +1,286 @@
1
+ # AGENTS.md: {{nameTr}} (Devoplus One uygulaması)
2
+
3
+ Bu dosya, bu uygulamayı geliştiren AI aracına (Claude Code, Codex vb.) yöneliktir. Kurallar bağlayıcıdır; bir
4
+ kuralın gerekçesi SDK belgesinde ya da platform karar defterindedir (aşağıda "Kaynaklar").
5
+
6
+ ## Platform sözleşmesi
7
+
8
+ - Uygulama **Devoplus One** üzerinde, **Devoplus Portal içinde iframe ile** çalışır.
9
+ Kimlik, kiracı ve backend erişimi `@devoplus/one-sdk`'dan gelir; **kendi oturum/çerez/token mekanizması YAZILMAZ.**
10
+ - `X-One-*` başlıklarını platform (dispatch) yazar; uygulama bunları `readContext(request.headers)` ile **okur**, asla
11
+ üretmez. İstemciden gelen `X-One-*` başlıkları platformda silinir.
12
+ - `ctx.user.sub` devoplus-auth kimlik uzayıdır (`UserAccount._id`); Organization Schema `User._id` DEĞİLDİR.
13
+ İkisi de 24-hex olduğu için karıştırılırsa sessizce hiç eşleşmez.
14
+ - Dış dünyaya yalnız `fetch` ile ve yalnız platformun izin verdiği adreslere çıkılır (CA backend `env.API_BASE`);
15
+ başka adrese istek **403** döner. Veritabanı sürücüsü, TCP soketi, üçüncü taraf API anahtarı kullanılmaz.
16
+ - **Sır koda/git'e yazılmaz.** Backend kimlik bilgisi platformun çıkış katmanındadır; uygulama hiçbir sır taşımaz.
17
+ `one-ci validate|plan|deploy` git'e girmiş/girecek dosyaları ve son dağıtımdan bu yana eklenen satırları tarar (sır
18
+ taraması kapısı, D-53): API anahtarı, belirteç, özel anahtar, parolalı bağlantı adresi ya da depoya girmiş
19
+ `.dev.vars`/`.env` dağıtımı durdurur. Yerel değerler `.dev.vars`'tadır ve `.gitignore`'dadır. Test sabiti gibi
20
+ gerçek olmayan bir bulgu `.one-secrets-ignore`'a parmak izi + **gerekçe** ile eklenir; sır bir kez commit'lendiyse
21
+ dosyadan silmek YETMEZ: geçmişte kalır, sağlayıcısında iptal edilip yenilenir.
22
+ - **Kod güvenlik taraması (D-65):** birleştirme isteğinde ve dağıtımdan önce Semgrep koşar; yüksek önemli bulgu
23
+ (`ERROR`, `CRITICAL`, `HIGH`) hattı durdurur. Kullanıcı girdisini `eval`/`new Function`'a ya da `innerHTML`'e veren kod
24
+ yazılmaz. Gerçek olmayan bir bulgu `.one-sast-ignore`'a parmak izi + **gerekçe** ile eklenir; kaynaktaki `nosemgrep`
25
+ yorumu sayılmaz.
26
+ - **Hat (CI) platformundur:** bu depoya `.gitlab-ci.yml` ya da başka bir CI dosyası YAZILMAZ (platform projesindeki hat
27
+ koşar, deponunki okunmaz). Testler `pnpm test` ile MR'da koşar; `pnpm-lock.yaml` commit edilir. Kurulum betiği
28
+ (`postinstall`, `.pnpmfile.cjs`) EKLENMEZ; hattın dağıtım işi bağımlılık kurmaz, kurulum yalnız sırsız işte yapılır.
29
+
30
+ ## Veri ve servisler
31
+
32
+ - **Veritabanı** `env.DB` (SDK tipi `OneDatabase`): bu kurulumun (uygulama × portal) KENDİ veritabanıdır; tabloya `PortalId` eklenmez, portal
33
+ süzgeci yazılmaz; kişiye özel satır `sub` ile bağlanır. Şema `migrations/NNNN_*.sql` ile ilerler: var olan dosya
34
+ DEĞİŞTİRİLMEZ, yeni dosya en büyük numaradan SONRA eklenir. `DROP TABLE`, `ALTER TABLE ... DROP/RENAME` ve WHERE'siz
35
+ `DELETE` yazılmaz: önceki sürüm geri alındığında çalışmaz; yeni tablo/sütun eklenir, eskisi sonraki sürümde
36
+ kaldırılır. `one-ci deploy` bu üç kuralı denetler (süreklilik kapısı) ve ihlalde dağıtımı durdurur.
37
+ - **Kayıt tutan ekranlar için önce veri modeli:** tabloyu, tipleri ve liste/kayıt/güncelleme/silme uçlarını elle yazmak
38
+ yerine varlıkları `one.data.json`'da tanımlayın, `one data generate` çalıştırın (yeni `migrations/NNNN_one_data.sql` +
39
+ `src/data/types.ts` + `migrations/.one-data-snapshot.json`; üçü de depoya girer, anlık görüntü elle düzenlenmez) ve
40
+ uçları SDK `createCrudHandler({ model: dataModel, entity, records: 'shared' | 'own', canWrite? })` ile bağlayın.
41
+ Model yalnız büyür: alan silme, ad/tür değişikliği, zorunlu yapma ve seçim değeri çıkarma üretilmez, komut durur;
42
+ seçim alanına yeni değer eklemek serbesttir (migration gerekmez). Arama Türkçe katlanmıştır: her metin alanının
43
+ `<alan>_fold` arama kolonu vardır; bir metin alanını elle yazdığınız SQL ile güncellerseniz bu kolonu da
44
+ `foldSearchText(değer)` ile yazın ya da `NULL` yapın. Excel/CSV/yazdırma uçları hazırdır
45
+ (`/api/<varlık>/export.xlsx`, `export.csv`, `report.html`); dosya arayüzde `one.download` ile Portal'a verilir, PDF
46
+ tarayıcının "PDF olarak kaydet"iyle alınır. Belgeler: SDK başvurusu "Veri modeli ve kayıt işlemleri" + "Rapor ve dışa aktarma".
47
+ {{#files}}
48
+ - **Dosya deposu** `env.FILES` (SDK `OneFiles`): kurulum başına depo. Anahtar/boyut kuralları SDK'dadır; kullanıcıdan gelen ad
49
+ `sanitizeFileName` ile tek parçaya indirilir; yükleme gövdesi `readBoundedBody` ile alınır (`request.arrayBuffer()`
50
+ YAZILMAZ, çünkü sınır gövde belleğe alındıktan sonra uygulanırdı). Liste imleci istemciye olduğu gibi döner.
51
+ Anahtar `uploads/<sub>/<uuid>-<ad>` biçimindedir: her kullanıcı yalnız KENDİ ön ekini listeler/indirir/siler
52
+ (`uploadPrefixFor`), uuid aynı adla ikinci yüklemenin öncekini ezmesini önler. Ortak ön ek ya da yalnız dosya adı
53
+ YAZILMAZ: başkasının dosyasını ezer ve anahtarı bilen herkese sildirir.
54
+ {{/files}}
55
+ {{^files}}
56
+ - Dosya saklamak gerekirse: `one.manifest.json` dosyasında `services.files: true`, `Env`'e `FILES: OneFilesBinding`, SDK `OneFiles`
57
+ (bkz. SDK başvurusu "Dosya servisi"). Başka bir depolama kullanılmaz.
58
+ {{/files}}
59
+ {{#cache}}
60
+ - **Önbellek** `env.CACHE` (SDK `OneCache`): kurulum başına, yalnız **önbellek**. TTL zorunludur (60 sn ile 30 gün arası),
61
+ değer JSON ≤ 1 MiB. Önbellek nihai tutarlıdır; sayaç, kilit ya da kalıcı kayıt için KULLANILMAZ; kalıcı veri veritabanındadır.
62
+ {{/cache}}
63
+ {{^cache}}
64
+ - Önbellek gerekirse: `one.manifest.json` dosyasında `services.cache: true`, `Env`'e `CACHE: OneCacheBinding`, SDK `OneCache`
65
+ (TTL zorunlu; kalıcı veri için değil). Başka bir önbellek kullanılmaz.
66
+ {{/cache}}
67
+ - **Backend** (`BackendClient`): CA `api/v1/*` uçları kullanıcının uygulama belirteciyle çağrılır; backend
68
+ "kullanıcı yetkisi ∩ uygulama scope'u ∩ portal" kuralını kendisi uygular. Uç adı/alanı **uydurulmaz**: belgeden
69
+ ya da kardeş depodan doğrulanır, doğrulanamıyorsa geliştiriciye sorulur.
70
+ - **E-posta/SMS/mobil bildirim** yalnız SDK `sendMail`/`sendSms`/`sendPush` ile; alıcı `sub` listesidir (ya da org
71
+ kimliği `toOrgUserIds`), serbest adres/telefon/cihaz kimliği YAZILMAZ ve başka bir bildirim servisi kullanılmaz.
72
+ Manifest `scopes` içinde `mail.execute`/`sms.execute`/`push.execute` olmalı. Bu üç scope ve `dexa.write`, kullanıcı
73
+ uygulamaya o portalda AYRICA ve süreli (30 gün) onay vermeden belirtece GİRMEZ (D-59): uygulama yine açılır, portal
74
+ üstte "İzinleri incele" düğmesini gösterir. Bu işleri yapan rotayı `requireScope` ile koruyun; arayüzde
75
+ `awaitsUserConsent(ctx, scope)` `true` ise düğmeyi kapatıp "portaldaki İzinleri incele düğmesiyle izin verin" yazın.
76
+ Mobil bildirim yalnız başlık (≤ 100) +
77
+ metin (≤ 300) taşır, bağlantı/veri yükü yoktur; bugün hiçbir ortamda açık değildir (`ONE-APP-E-029`); kodunuz bu
78
+ hatada da çalışmayı sürdürmeli, e-postayı ya da asıl işlemi düşürmemelidir (`notify` kanalları bağımsız gönderir).
79
+ - **DEXA (ERP okuma)** yalnız SDK `queryDexa(api, key, parameters?)` ile ve yalnız ÖNCEDEN tanımlı QueryStore
80
+ anahtarlarıyla; serbest SQL ya da ERP'ye yazma YOKTUR. Anahtarları Devoplus Console'da uygulamanın kayıt defterine
81
+ yönetici ekler (kayıtta olmayan anahtar 403). Manifest'te `services.dexa.queryKeys` ve `scopes` içinde `dexa.read`
82
+ birlikte beyan edilir; anahtar adı uydurulmaz, geliştiriciden alınır. `result.unavailable === true` ise sorgu
83
+ ÇALIŞAMADI; boş `rows` "kayıt yok" SAYILMAZ, kullanıcıya "şu anda getirilemiyor" gösterilir; `truncated` sonucun
84
+ 5.000 satırda kesildiğini söyler. `LangCode` platformundur, uygulama GÖNDERMEZ. `UserCode`, `CompanyCode`,
85
+ `CostCenterCode`: kurulumda yönetici "kodları uygulama belirler" onayını verdiyse uygulama bunları her kipte (kişi,
86
+ ajan, arka plan işi) gönderebilir ve sunucu kişi ile şirket kodunu kurulumun portalına karşı doğrular; onay yoksa
87
+ gönderilen kod reddedilir (`ONE-APP-E-026`). Gönderilmezse kişi/ajan kipinde değer oturumdaki kişiden çözülür, arka
88
+ plan işinde sorgu reddedilir. Boş değer ERP'ye gitmez: boş ya da yalnız boşluk değer `ONE-SDK-E-140` ile hiç
89
+ gönderilmez; kodu bilmiyorsanız parametreyi göndermeyin, uydurmayın.
90
+ - **DEXA işlemi (ERP'ye YAZMA)** yalnız SDK `runDexaCommand(api, key, parameters, { idempotencyKey })` ile ve yalnız
91
+ kayıt defterinde izinli (`dexaCommandKeys`) işlem anahtarlarıyla; manifest'te `services.dexa.commandKeys` + `scopes`
92
+ içinde `dexa.write`. Anahtar uydurulmaz. ⛔ Her kullanıcı eylemi TEK bir `Idempotency-Key` alır: `newIdempotencyKey()`
93
+ ile üretin ve isteği göndermeden ÖNCE veritabanına yazın; ağ hatası / zaman aşımı / 409 `ERPCMD-APP-E-016` / 424 sonrası
94
+ AYNI anahtarla tekrar deneyin ya da `getDexaCommandStatus` ile durumu okuyun. `outcome === 'inDoubt'` ise ERP'ye
95
+ gidip gitmediği bilinmiyor: YENİ anahtarla ASLA yeniden göndermeyin (çift kayıt). Kullanıcıya "sonuç doğrulanamadı"
96
+ deyin. 4xx yanıt ERP'ye hiçbir şey gitmediğini söyler.
97
+ - **Arka plan işi (zamanlama / kuyruk) ve Devoplus servisleri:** işte kullanıcı belirteci yoktur; servisler
98
+ UYGULAMANIN kimliğiyle çağrılır (`ctx.mode === 'job'`, `new BackendClient({ baseUrl, authorization: null })`).
99
+ Kullanılabilenler yalnız manifest `jobs.services` (ve `scopes`) ile beyan edilen `mail.execute`, `sms.execute`,
100
+ `push.execute`, `dexa.read`, `ai.use`'dur. ⛔ **Arka plan işi servisleri kurulumda yönetici onayıyla açılır**:
101
+ onaysız çağrı 403; DEXA'ya yazma bu yolla AÇILMAZ. "Kendime gönder" yoktur, alıcıyı açıkça verin.
102
+ - **Denetim kaydı:** kritik kullanıcı işlemi `logAudit` (console izi) ile işaretlenir; kalıcı kayıt gerekiyorsa
103
+ `recordAudit(api, ctx, action, target?, data?)` (scope `audit.write`). `target`/`data`ya kişisel veri ya da sır
104
+ YAZILMAZ; kayıt Devoplus Console'da görünür ve 365 gün saklanır.
105
+ - **Olay kaydı (Event Logger):** çalışma zamanı olayı `logEvent(ctx, level, message, data?)`, beklenmeyen hata
106
+ `logError(ctx, message, error, data?)`. Satırlar platform günlüğünde saklanır (platform her kurulumda açar; örnekleme
107
+ manifest `observability.headSamplingRate`). `data`daki parola/token/secret/authorization/cookie/API anahtarı adlı
108
+ alanlar otomatik "[gizli]" olur ve 8 KiB üstü `data` yerine boyutu yazılır; yine de kişisel veri ya da sır
109
+ yazmayın. `problemResponse(error, ctx)` bilinmeyen hatayı bu kayda bağlamıyla düşer.
110
+
111
+ ## Scope ve yetki
112
+
113
+ - Bir yetenek `one.manifest.json` dosyasındaki `scopes` içinde beyan edilmeden kullanılamaz; kod tarafında `requireScope(ctx,
114
+ '<scope>')` kapısı konur (yoksa 403). Scope adları devoplus-auth kataloğundandır: `<modül>.<read|write|manage|execute>`
115
+ ve ayrıca `ai.use` (Devoplus yapay zekası).
116
+ {{#hasScopes}}
117
+ Bu uygulamanın beyan ettiği scope'lar: {{scopesList}}.
118
+ {{/hasScopes}}
119
+ {{^hasScopes}}
120
+ Bu uygulama henüz scope beyan etmiyor (yalnız kimlik + kendi veritabanı).
121
+ {{/hasScopes}}
122
+ - Portal menüsünde görünme izni `{{permissionCode}}` kodudur; kimin kullanacağını portal yöneticisi Yetki Yönetimi'nden
123
+ atar; uygulama kendi içinde ikinci bir yetki modeli KURMAZ (kendi rol tablosu, e-posta listesiyle yetki vb. YAZILMAZ).
124
+ - Uygulama içinde rol gerekiyorsa (ör. yalnız onaylayanlar onaylasın) **ek izin kodu** beyan edilir:
125
+ `one.manifest.json` dosyasına `permissions: [{ "code": "<{{permissionCode}} ile aynı önek>.<Eylem>", "name": { ... } }]`.
126
+ Portal yöneticisi bu kodu da Yetki Yönetimi'nde kullanıcı gruplarına (şirket, departman, ünvan, pozisyon gibi düğümler)
127
+ atar; kod tarafında `requirePermission(ctx, '<kod>')` (403) ya da `hasPermission(ctx, '<kod>')` kullanılır. Portal
128
+ yöneticisi her izne sahip sayılır (platformla aynı kural). Platform uygulamaya yalnız KENDİ ad alanındaki izinleri
129
+ iletir; başka bir uygulamanın ya da portalın izni okunamaz.
130
+
131
+ ## Devoplus yapay zekası: iki yön (ZORUNLU adım)
132
+
133
+ Devoplus yapay zekası bu uygulamanın verisine ve işlemlerine erişebilmelidir: kullanıcı Devoplus'ta "son notlarımı
134
+ göster" dediğinde yanıt bu uygulamadan gelir. Bu bir ek değil, uygulamanın parçasıdır: **her yeni özellikte** aşağıdaki
135
+ üç dosya birlikte güncellenir:
136
+
137
+ 1. **`one.manifest.json` dosyasındaki `agent.tools`**: yapay zekanın çağırabileceği her araç: `name` (küçük harf/rakam/`-`),
138
+ TR/EN `title` ve `description` (ne yapar, ne döndürür, ne zaman KULLANILMAZ), `readOnly`, gerekiyorsa `permission`
139
+ (uygulamanın kendi izin kodlarından biri) ve `input` (JSON şeması; yalnız `type`, `properties`, `required`, `enum`,
140
+ `minimum`, `maximum`, `minLength`, `maxLength`, `items`, `description`). Şemada olmayan alan çalışma anında
141
+ reddedilir.
142
+ 2. **`src/agent.ts`**: aracın işleyicisi: `defineAgentTools({ '<ad>': { handler: async ({ input, ctx, env }) => ... } })`.
143
+ Girdi SDK'da şemaya göre doğrulanmış gelir; işleyici kullanıcının KENDİ yetkisiyle çalışır (`ctx.user`), ekrandaki
144
+ kurallar aynen uygulanır (ör. yalnız kendi kayıtları). `ctx.admin` = kişi bu portalın şirket yöneticisi, `ctx.actor`
145
+ = Vekalet / Yerine Görüntüle. Araç Devoplus servislerini kişi adına çağırabilir; süreli onay gerektiren scope'lar
146
+ (e-posta, SMS, mobil bildirim, DEXA'ya yazma) ajan aracında KULLANILAMAZ. JSON'a çevrilebilir bir değer döndürün; gereksiz kişisel veri
147
+ döndürmeyin.
148
+ 3. **`AGENT_GUIDE.md`**: yapay zekanın okuduğu kullanım rehberi. Dört başlık zorunludur: `## Uygulama ne yapar`,
149
+ `## Veri`, `## Araçlar`, `## Örnek istekler`. Her aracın adı rehberde geçer. `one-ci validate` araç beyanı olan
150
+ uygulamada bu dosyayı arar ve eksikse yayını DURDURUR; araç beyanı yoksa "Devoplus yapay zekası bu uygulamaya
151
+ erişemez" uyarısı verir.
152
+
153
+ Araç tasarım kuralları:
154
+
155
+ - Önce **salt okunur** araçlar (listeleme, arama, özet). Bir şeyi değiştiren araç `readOnly: false` beyan edilir:
156
+ Devoplus yapay zekası her çağrıdan önce kullanıcıya onay sorar ve bu araçlar Vekalet / Yerine Görüntüle oturumunda
157
+ çalışmaz. Silme gibi geri alınamaz işler araç yapılmaz; kullanıcı ekrana yönlendirilir.
158
+ - Bir araç tek bir işi yapar ve adı bunu söyler (`list-requests`, `get-request`, `approve-request`). Girdiler küçük ve
159
+ sınırlıdır (`enum`, `minimum`/`maximum`, `maxLength`); sayfa boyu gibi değerlerin bir tavanı olur.
160
+ - Araç, kullanıcının uygulama ekranında yapabileceğinden fazlasını yapmaz: aynı izin ve aynı süzgeç (`sub`) uygulanır.
161
+ - Teknik olmayan kullanıcıya araç adı, şema ya da JSON sorulmaz. Hangi soruların bu uygulamaya ait olduğunu keşif
162
+ görüşmesindeki iş listesinden siz çıkarırsınız; rehberi siz yazarsınız.
163
+ - Araç çağrısının yolu `/__one/agent/<ad>`'dır ve yalnız platformun imzalı çağrısı ulaşır (tarayıcıdan gelen 404 alır);
164
+ bu yolu ekranda kullanmayın. Katalog ve rehber `/.well-known/one-agent.json`, `/.well-known/one-agent.md`
165
+ adreslerinden kendiliğinden sunulur (`src/index.ts` başındaki iki satır); elle yazılmaz.
166
+
167
+ **Uygulamanın Devoplus yapay zekasını kullanması:** metin özetleme, sınıflandırma, alan çıkarma, metin üretme ve sohbet
168
+ için SDK `completeAi(api, ctx, { task, input, ... })` (manifest `scopes` içinde `ai.use`). Model, kota ve portalda açık
169
+ olup olmadığı Devoplus'tadır: kapalı portalda 503, kota dolunca 429 döner; uygulama bu durumda da çalışmayı sürdürür.
170
+ ⛔ Model sayı HESAPLAMAZ ve kayıt YAZMAZ: tutar/toplam/tarih farkı kodda hesaplanıp `input`'a hazır verilir; modelin
171
+ çıktısı bir öneridir, veritabanına yazmadan önce kod doğrular ve gerekirse kullanıcıya onaylatır. `input`'a sır ya da
172
+ işin gerektirmediği kişisel veri konmaz.
173
+
174
+ ## Kabuk ve Portal köprüsü (`src/shell.ts`)
175
+
176
+ - Sayfa üç dosyayı KENDİ adresinden yükler: `/one-ui.css` (görsel dil), `/one-bridge.js` (Devoplus One köprüsü,
177
+ `window.one`) ve `/shell.js` (sayfanın kendi betiği). Sunucu tarafı ilk ikisini `clientAssetResponse(pathname)`, üçüncüsünü
178
+ `scriptResponse(renderShellScript(...))` ile döndürür. ⛔ Satır içi `<script>` ve başka bir adresten (`https://...`) betik
179
+ YAZILMAZ: platform ikisini de çalıştırmaz ve `one-ci validate` yayını durdurur. Kütüphane gerekiyorsa bağımlılık olarak
180
+ eklenir ve uygulamayla birlikte sunulur. Satır içi `<style>` serbesttir.
181
+ - Portal ile HER konuşma köprüden geçer; `postMessage` doğrudan YAZILMAZ. Köprü iletinin kaynağını (`parent` sorgusundaki
182
+ origin + üst pencere) doğrular ve yalnız o origin'lere gönderir.
183
+ - Belirteç `one.onToken(fn)` / `one.token`; istekler `one.fetch(yol, init)` ile gider (belirteç eklenir). Tema, dil ve
184
+ Vekalet / Yerine Görüntüle bilgisi `one.onContext(fn)` ile gelir; sayfa yeniden yüklenmeden uygulanır.
185
+ - `one.start({ navigation })` sayfa hazır olunca BİR kez çağrılır: Portal'a hazır olduğunu ve menüyü bildirir, yükseklik
186
+ bildirimini başlatır. Menü sunucuda `navigationFromManifest(manifest)` ile `one.manifest.json`'dan üretilir.
187
+ - **Sekme izni:** bir sekme yalnız belli bir izni olanlara görünecekse manifest'te öğeye `"permission": "One.<Uygulama>.<Eylem>"`
188
+ yazın (kod `permission.code` ya da `permissions` içinde beyan edilmiş olmalı). Köprü bu sekmeyi oturumdaki kişinin
189
+ izinleri bildirilene kadar menüye KOYMAZ: sayfa oturum bilgisini okuyunca `one.setNavigationAccess({ permissions, admin })`
190
+ çağırır (yönetici her sekmeyi görür). Bu yalnız görünürlüktür; sayfanın verisi sunucu ucunda `requirePermission` ile
191
+ ayrıca korunur; adresi doğrudan açan yetkisiz kişiye "yetkiniz yok" notu gösterin.
192
+ - **Yükseklik:** köprü içerik yüksekliğini Portal'a bildirir; Portal iframe'i o boyda çizer ve sayfanın kendi taşmasını
193
+ gizler; sayfada tek kaydırma çubuğu Portal'ınkidir. ⛔ Kök öğelerde (`html`, `body`, sayfa sarmalayıcısı) `100vh` ya da
194
+ `height: 100%` KULLANMAYIN: içerik iframe boyuna bağlanırsa yükseklik hiç küçülmez. Kendi içinde kayan bir alan
195
+ gerekiyorsa (uzun çıktı, tablo) ona sabit bir `max-height` verin.
196
+ - Kabuk iki dili de gömer (`STRINGS`); yeni metin **iki dile birden** eklenir (test anahtar kümelerini karşılaştırır).
197
+ Kullanıcıya görünen metin Türkçe ve resmi "siz" dilindedir; İngilizce kavram adı bırakılmaz.
198
+ - Kullanıcı girdisi DOM'a `textContent` ile yazılır, `innerHTML` ile değil. Portal'a giden bütün metinler (bildirim,
199
+ onay sorusu, başlık) DÜZ METİNDİR; HTML yazılmaz.
200
+ - Dile bağlı her metin `one:context` gelince yeniden çevrilmelidir: statik öğe `data-i18n` taşır, sonradan
201
+ eklenen öğeye de `setAttribute('data-i18n', ...)` yazılır; durum satırı yalnız `showStatus(anahtar)` ile değişir
202
+ (`textContent = t(...)` yazılmaz, çünkü dil değişiminde eski dilde kalır).
203
+
204
+ ## Arayüz standartları (U1 ile U12 arası, bağlayıcı)
205
+
206
+ Her Devoplus One uygulaması Portal'ın bir parçası gibi görünür. Kurallar karar defterinde D-76'dır.
207
+
208
+ - **U1 Menü:** uygulama sol menü / yan çekmece ÇİZMEZ. Görünümler `one.manifest.json` dosyasındaki `navigation` alanına yazılır
209
+ (`[{ "path": "/", "label": { "tr": "...", "en": "..." }, "permission"?: "One.<Uygulama>.<Eylem>" }]`, en çok 12); sekmeleri Portal iframe'in üstünde çizer ve
210
+ Portal adresi `/One/<kısa ad>/<yol>` olur (yenileme, geri ve paylaşılan bağlantı çalışır). Portal sekmesine tıklanınca
211
+ `one.onNavigate(fn)` çağrılır; uygulama kendi içinde görünüm değiştirdiğinde `one.setRoute(yol)` çağırır. Sunucu
212
+ `navigation` yollarının hepsinde sayfayı döndürür. Yol `/` ile başlar; sorgu ve `#` taşımaz.
213
+ - **U2 Üst bilgi:** kullanıcı adı/fotoğrafı, çıkış, portal/dil/tema seçicisi, logo ve uygulama adını tekrarlayan başlık
214
+ ÇİZİLMEZ; bunları Portal gösterir, bilgi `ctx`'tedir. Alt sayfanın başlığı (ör. açılan kaydın adı)
215
+ `one.setTitle(metin | null)` ile Portal'daki sayfa yoluna yazılır (en çok 120 karakter).
216
+ - **U3 Vekalet / Yerine Görüntüle:** başkası adına açılan oturumda sunucuda `ctx.actor` doludur (`{ sub, kind }`;
217
+ `kind` `delegation` = Vekalet, `impersonation` = Yerine Görüntüle), tarayıcıda `one.actor` (`{ kind }`). Varsayılan
218
+ olarak bu oturumlar SALT-OKUNURDUR: platform GET/HEAD dışındaki isteği uygulamaya ulaşmadan 403 ile reddeder. Uygulama
219
+ bu oturumlarda da yazmak zorundaysa `one.manifest.json` dosyasında `"actingSessions": "write"` beyan eder ve kim adına kimin
220
+ yazdığını (`ctx.user.sub` + `ctx.actor.sub`) kaydeder. Bu beyan bile ERP komutu, e-posta, SMS ve mobil bildirimi bu
221
+ oturumlarda AÇMAZ; `recordAudit` her zaman yazılır ve vekili ayrıca tutar. Uyarı şeridini Portal çizer; uygulama yazma düğmelerini
222
+ `one.actor` doluyken kapatabilir.
223
+ - **U4 Pencere ve bildirim:** Portal iframe'i içerik boyunda çizdiği için `position: fixed` öğe ekranın dışında kalabilir.
224
+ Bildirim `one.toast({ severity, summary, detail? })` (`success`, `info`, `warn`, `error`), onay
225
+ `await one.confirm({ message, title?, severity?, acceptLabel?, rejectLabel? })` (sonuç `true`/`false`) ile Portal'da
226
+ çizilir. Uygulamanın kendi büyük penceresi gerekiyorsa `one.viewport.current` (`{ top, height }`, iframe pikseli) ve
227
+ `one.viewport.onChange(fn)` ile görünen alana yerleştirilir.
228
+ - **U5 Görsel dil:** `/one-ui.css` sınıfları kullanılır: `one-btn` (`one-btn-primary`, `one-btn-danger`, `one-btn-text`),
229
+ `one-field` + `one-label` + `one-input` / `one-select` / `one-textarea`, `one-table` (`one-table-wrap` içinde),
230
+ `one-card`, `one-message` (`-success`, `-info`, `-warn`, `-error`), içerik sekmeleri `one-tabs` + `one-tab`
231
+ (`aria-selected`). Renk/yazı/köşe `--one-primary`, `--one-primary-contrast`, `--one-font-family`, `--one-radius` ve
232
+ `--one-bg`, `--one-fg`, `--one-muted`, `--one-surface`, `--one-border` değişkenlerinden okunur; açık/koyu tema ve
233
+ Portal'ın marka değerleri bunlara yazılır. Sabit renk YAZILMAZ.
234
+ - **Hazır bileşenler (D-83):** liste, kayıt formu, süzgeç, kişi seçimi, dosya yükleme ve grafik için SDK'nın
235
+ `/one-components.js` bileşenleri kullanılır: `<one-table>` (sıralama, arama, sayfalama, seçim, Excel/CSV),
236
+ `<one-form>` (doğrulama, sunucu hatası alanın altında, kaydedilmemiş değişiklik), `<one-filter>`,
237
+ `<one-person-picker>`, `<one-file-upload>`, `<one-chart>`. Sayfa betiği köprüden SONRA yükler
238
+ (`<script src="/one-components.js"></script>`; `clientAssetResponse` sunar, yüklenmezse `one-ci validate` uyarır);
239
+ kolon/alan tanımları `/shell.js`'te JavaScript özelliğidir, etiketler `{ tr, en }`. Uzak liste ucu
240
+ `GET /api/<varlık>?limit=&cursor=&sort=&order=&q=&<alan>=` isteğine `{ items, nextCursor }` döndürür, dışa aktarma aynı ucu
241
+ `?format=xlsx|csv` ile çağırır. Kişi seçici için sunucuda `peopleSearchHandler(request, env, ctx)` (`/api/one/people`,
242
+ scope `orgschema.read`; dönen `id` org kimliğidir, `sub` değil). Elle tablo/form yazılmaz. Ayrıntı: SDK başvurusu
243
+ "Hazır arayüz bileşenleri".
244
+ - **U6 İndirme ve dış bağlantı:** iframe'de indirme ve yeni pencere kapalıdır. Dosya `await one.download({ name, mime,
245
+ data })` ile Portal'a verilir (en çok 25 MB); dış bağlantı `one.openExternal('https://...')` ile açılır (yalnız https,
246
+ Portal kullanıcıya sorar). `<a download>` ve `window.open` kullanılmaz.
247
+ - **U7 Kaydedilmemiş değişiklik:** formda kaydedilmemiş değişiklik varken `one.setDirty(true)`, kaydedilince
248
+ `one.setDirty(false)`; Portal kullanıcı ayrılmadan önce sorar.
249
+ - **U8 Dil ve biçim:** her metin TR + EN; tarih `gg.aa.yyyy`, sayı `tr-TR` biçiminde; telefon E.164 saklanır; hata
250
+ kullanıcının dilinde gösterilir; yükleniyor / boş / hata durumları ayrı gösterilir ("boş" ile "getirilemedi" karışmaz).
251
+ - **U9 Erişilebilirlik ve boyut:** en küçük genişlik 360 px, yatay kaydırma yok, kenar boşluğu 16 px; her işlem
252
+ klavyeyle yapılabilir, odak görünür; metin/zemin kontrastı yeterlidir.
253
+ - **U10 Kimlik:** Devoplus logosu ya da Portal menüsü taklit edilmez.
254
+ - **U11 Denetim:** `one-ci validate` sol menü, kendi üst bilgisi, kapsayıcısız `position: fixed` ve eksik TR/EN için
255
+ UYARI verir (ilk sürümde yayını durdurmaz; sonra durduracak). Uyarıyı kalıcı bırakmayın.
256
+ - **U12 Güvenlik tabanı:** platform her yanıta içerik güvenliği politikası ekler: betik, bağlantı, yazı tipi ve görsel
257
+ yalnız uygulamanın kendi adresinden (görselde ayrıca `data:`, `blob:`, Devoplus API ve Devoplus CDN, yani kullanıcı
258
+ fotoğrafı ve kurumsal görseller; veri isteği yine yalnız kendi adresinize); satır içi stil açık, satır içi betik kapalı;
259
+ başka sayfayı çerçeveleme, nesne gömme ve başka adrese form gönderme kapalı. Kamera, mikrofon ve konum varsayılan
260
+ kapalıdır; gerekiyorsa `one.manifest.json` dosyasında `"devicePermissions": ["camera" | "microphone" | "geolocation"]` beyan
261
+ edilir. Uygulama bu politikayı kendi başlığıyla yalnız DARALTABİLİR.
262
+
263
+ ## Çalışma kuralları
264
+
265
+ - `pnpm type-check && pnpm test && pnpm build` yeşil olmadan commit yapılmaz.
266
+ - Hata yolu tek yerdedir: `catch (error) { return problemResponse(error) }`. `OneError` RFC 9457 gövdeye çevrilir,
267
+ bilinmeyen hata 500 döner ve ayrıntı sızdırılmaz. `OneError.retryAfterSeconds` pozitif tam sayıysa (ör. 409
268
+ `ERPCMD-APP-E-016`) yanıt onu `Retry-After` başlığında ve gövdedeki `retryAfterSeconds` alanında da taşır.
269
+ - Kod Devoplus git deposuna push edilir; `one-ci validate` (manifest + giriş dosyası + README.md/AGENTS.md + araç
270
+ beyanı varsa AGENT_GUIDE.md + `one.data.json` varsa üretilmiş dosyaların güncelliği (`one data generate --check`) +
271
+ arayüz denetimi) ve taramalardan geçmeyen sürüm yüklenmez. Geliştirici makinesinden doğrudan yükleme YOKTUR.
272
+ - **AI değişikliği ayrı dalda yapılır ve izlenebilir kalır:** çalışmaya `ai/<konu>` adlı yeni bir dalda başlayın;
273
+ yayın dallarına (`test`, `uat`, `main`) doğrudan commit atmayın, değişiklik birleştirme isteğiyle (merge request /
274
+ pull request) girer. AI ile yazdığınız her commit'in iletisine ayrı satırda `AI-Assisted: <araç adı>` trailer'ı
275
+ ekleyin (ör. `AI-Assisted: Claude Code`). `one-ci deploy` bu commit'leri ve geldikleri dalı sürüm defterine yazar;
276
+ platform `ONE_AI_BRANCH_POLICY=enforce` açarsa yayın dalına doğrudan yazılmış AI commit'i yayını durdurur.
277
+ - `README.md` uygulamanın amacını ve davranışını, bu dosya AI kurallarını, `AGENT_GUIDE.md` Devoplus yapay zekasının
278
+ araçları nasıl kullanacağını anlatır; davranışı değiştiren değişiklik üçünü aynı commit'te günceller (one-ci README ya da
279
+ AGENTS yoksa veya boşsa, araç beyanı olan uygulamada AGENT_GUIDE.md eksikse yayını durdurur).
280
+
281
+ ## Kaynaklar
282
+
283
+ - SDK API ve hata kodları (SDK başvurusu): `node_modules/@devoplus/one-sdk/REFERENCE.md` (bu dosyayı taşımayan eski ön
284
+ sürümlerde `README.md`)
285
+ - Platform mimarisi: `devoplus/devoplus-one` deposundaki `docs/architecture.md`; kararlar `devoplus-root/docs/devoplus-one-karar-defteri.md`
286
+ - Örnek/test konsolu: `devoplus/devoplus-one` deposundaki `apps/hello`