@vennyx/solicrm-mcp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/bin.js ADDED
@@ -0,0 +1,4097 @@
1
+ #!/usr/bin/env node
2
+ import { z } from "zod";
3
+ import { McpServer, ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4
+ import { WebStandardStreamableHTTPServerTransport } from "@modelcontextprotocol/sdk/server/webStandardStreamableHttp.js";
5
+ import { StdioServerTransport } from "@modelcontextprotocol/sdk/server/stdio.js";
6
+ //#region packages/contracts/src/fields.ts
7
+ /**
8
+ * Alan kataloğu — `field_key` → kolon/tip/yetenek eşlemesi.
9
+ *
10
+ * NEDEN VAR: kaydedilmiş görünümler, filtreler, sıralama, CSV içe/dışa aktarma
11
+ * ve MCP/SDK tip üretimi HEPSİ bu katalogdan okur. Hiçbir yerde kullanıcıdan
12
+ * gelen bir değer doğrudan kolon adına dönüşmez — `resolveField()` bilinmeyen
13
+ * anahtar için `undefined` döner ve çağıran 400 verir. Bu, SQL enjeksiyonu ve
14
+ * bilgi sızdırmanın sınırıdır.
15
+ *
16
+ * İLERİYE DÖNÜK: özel alanlar (custom fields) eklendiğinde katalog runtime'da
17
+ * tenant'ın tanımlarıyla genişletilir; tüketici kod değişmez (bkz. spec §6.7).
18
+ */
19
+ const CRM_RESOURCES = [
20
+ "contacts",
21
+ "companies",
22
+ "deals",
23
+ "tasks"
24
+ ];
25
+ const FIELD_CATALOG = {
26
+ contacts: {
27
+ first_name: {
28
+ column: "first_name",
29
+ type: "text",
30
+ filterable: true,
31
+ sortable: true
32
+ },
33
+ last_name: {
34
+ column: "last_name",
35
+ type: "text",
36
+ filterable: true,
37
+ sortable: true
38
+ },
39
+ email: {
40
+ column: "email",
41
+ type: "text",
42
+ filterable: true,
43
+ sortable: true
44
+ },
45
+ phone: {
46
+ column: "phone",
47
+ type: "text",
48
+ filterable: true,
49
+ sortable: false
50
+ },
51
+ job_title: {
52
+ column: "job_title",
53
+ type: "text",
54
+ filterable: true,
55
+ sortable: true
56
+ },
57
+ city: {
58
+ column: "city",
59
+ type: "text",
60
+ filterable: true,
61
+ sortable: true
62
+ },
63
+ country: {
64
+ column: "country",
65
+ type: "text",
66
+ filterable: true,
67
+ sortable: true
68
+ },
69
+ status: {
70
+ column: "status",
71
+ type: "enum",
72
+ filterable: true,
73
+ sortable: true,
74
+ values: [
75
+ "lead",
76
+ "active",
77
+ "inactive"
78
+ ]
79
+ },
80
+ company_id: {
81
+ column: "company_id",
82
+ type: "uuid",
83
+ filterable: true,
84
+ sortable: false
85
+ },
86
+ owner_user_id: {
87
+ column: "owner_user_id",
88
+ type: "uuid",
89
+ filterable: true,
90
+ sortable: false
91
+ },
92
+ created_at: {
93
+ column: "created_at",
94
+ type: "date",
95
+ filterable: true,
96
+ sortable: true
97
+ }
98
+ },
99
+ companies: {
100
+ name: {
101
+ column: "name",
102
+ type: "text",
103
+ filterable: true,
104
+ sortable: true
105
+ },
106
+ domain: {
107
+ column: "domain",
108
+ type: "text",
109
+ filterable: true,
110
+ sortable: true
111
+ },
112
+ industry: {
113
+ column: "industry",
114
+ type: "text",
115
+ filterable: true,
116
+ sortable: true
117
+ },
118
+ city: {
119
+ column: "city",
120
+ type: "text",
121
+ filterable: true,
122
+ sortable: true
123
+ },
124
+ country: {
125
+ column: "country",
126
+ type: "text",
127
+ filterable: true,
128
+ sortable: true
129
+ },
130
+ annual_revenue: {
131
+ column: "annual_revenue",
132
+ type: "money",
133
+ filterable: true,
134
+ sortable: true
135
+ },
136
+ owner_user_id: {
137
+ column: "owner_user_id",
138
+ type: "uuid",
139
+ filterable: true,
140
+ sortable: false
141
+ },
142
+ created_at: {
143
+ column: "created_at",
144
+ type: "date",
145
+ filterable: true,
146
+ sortable: true
147
+ }
148
+ },
149
+ deals: {
150
+ title: {
151
+ column: "title",
152
+ type: "text",
153
+ filterable: true,
154
+ sortable: true
155
+ },
156
+ amount: {
157
+ column: "amount",
158
+ type: "money",
159
+ filterable: true,
160
+ sortable: true
161
+ },
162
+ currency_code: {
163
+ column: "currency_code",
164
+ type: "text",
165
+ filterable: true,
166
+ sortable: false
167
+ },
168
+ stage_id: {
169
+ column: "stage_id",
170
+ type: "uuid",
171
+ filterable: true,
172
+ sortable: false
173
+ },
174
+ pipeline_id: {
175
+ column: "pipeline_id",
176
+ type: "uuid",
177
+ filterable: true,
178
+ sortable: false
179
+ },
180
+ company_id: {
181
+ column: "company_id",
182
+ type: "uuid",
183
+ filterable: true,
184
+ sortable: false
185
+ },
186
+ outcome: {
187
+ column: "outcome",
188
+ type: "enum",
189
+ filterable: true,
190
+ sortable: true,
191
+ values: [
192
+ "open",
193
+ "won",
194
+ "lost"
195
+ ]
196
+ },
197
+ expected_close_date: {
198
+ column: "expected_close_date",
199
+ type: "date",
200
+ filterable: true,
201
+ sortable: true
202
+ },
203
+ owner_user_id: {
204
+ column: "owner_user_id",
205
+ type: "uuid",
206
+ filterable: true,
207
+ sortable: false
208
+ },
209
+ created_at: {
210
+ column: "created_at",
211
+ type: "date",
212
+ filterable: true,
213
+ sortable: true
214
+ }
215
+ },
216
+ tasks: {
217
+ title: {
218
+ column: "title",
219
+ type: "text",
220
+ filterable: true,
221
+ sortable: true
222
+ },
223
+ due_at: {
224
+ column: "due_at",
225
+ type: "date",
226
+ filterable: true,
227
+ sortable: true
228
+ },
229
+ completed_at: {
230
+ column: "completed_at",
231
+ type: "date",
232
+ filterable: true,
233
+ sortable: true
234
+ },
235
+ priority: {
236
+ column: "priority",
237
+ type: "enum",
238
+ filterable: true,
239
+ sortable: true,
240
+ values: [
241
+ "low",
242
+ "normal",
243
+ "high",
244
+ "urgent"
245
+ ]
246
+ },
247
+ assignee_user_id: {
248
+ column: "assignee_user_id",
249
+ type: "uuid",
250
+ filterable: true,
251
+ sortable: false
252
+ },
253
+ created_at: {
254
+ column: "created_at",
255
+ type: "date",
256
+ filterable: true,
257
+ sortable: true
258
+ }
259
+ }
260
+ };
261
+ /**
262
+ * Bir `field_key`'i katalogdan çözer. Bilinmeyen anahtar → `undefined`
263
+ * (çağıran 400 döner). ASLA anahtarı kolon adı olarak varsaymaz.
264
+ */
265
+ function resolveField(resource, key) {
266
+ const catalog = FIELD_CATALOG[resource];
267
+ return Object.hasOwn(catalog, key) ? catalog[key] : void 0;
268
+ }
269
+ //#endregion
270
+ //#region packages/contracts/src/crm.ts
271
+ /**
272
+ * CRM domain genelinde paylaşılan zod katalogları (spec §6.2/§6.3).
273
+ *
274
+ * NOT — Task 3 (deals) genişletmesi: `targetTypeSchema` artık `'deal'`
275
+ * içeriyor (tek satırlık, geriye uyumlu genişleme; hiçbir tüketici tipi
276
+ * DARALTILMADI, yalnız genişledi — bkz. `apps/api/src/lib/polymorphic-target.ts`,
277
+ * `packages/db/drizzle/0028_deals_guard.sql`'daki `CREATE OR REPLACE FUNCTION`).
278
+ *
279
+ * NOT — spec'in tam liste vermediği katalog değerleri (bu dosyanın kararı):
280
+ * `contactSourceSchema` ve `companySizeSchema` spec §6.2'de yalnız "text+katalog"
281
+ * / kendine özgü alan olarak anılıyor, değer kümesi verilmiyor — makul bir
282
+ * başlangıç kümesi seçildi (bkz. plan "Yeni ortak altyapı kararları / D").
283
+ */
284
+ const targetTypeSchema = z.enum([
285
+ "contact",
286
+ "company",
287
+ "deal"
288
+ ], "geçerli bir hedef türü olmalı (contact, company, deal)");
289
+ /** Fırsat sonucu (spec §6.2) — DB'de `deals_outcome_check` CHECK kısıtıyla eşleşir */
290
+ const dealOutcomeSchema = z.enum([
291
+ "open",
292
+ "won",
293
+ "lost"
294
+ ], "geçerli bir sonuç olmalı (open, won, lost)");
295
+ const contactStatusSchema = z.enum([
296
+ "lead",
297
+ "active",
298
+ "inactive"
299
+ ], "geçerli bir durum olmalı (lead, active, inactive)");
300
+ /** Spec'te tam liste verilmemiş — makul bir başlangıç kümesi (bkz. dosya başı NOT) */
301
+ const contactSourceSchema = z.enum([
302
+ "website",
303
+ "referral",
304
+ "cold_outreach",
305
+ "event",
306
+ "social_media",
307
+ "other"
308
+ ], "geçerli bir kaynak olmalı");
309
+ /** Spec'te tam liste verilmemiş — makul bir başlangıç kümesi (bkz. dosya başı NOT) */
310
+ const companySizeSchema = z.enum([
311
+ "1-10",
312
+ "11-50",
313
+ "51-200",
314
+ "201-500",
315
+ "500+"
316
+ ], "geçerli bir şirket büyüklüğü olmalı");
317
+ const sortDirectionSchema = z.enum(["asc", "desc"], "yön asc veya desc olmalı");
318
+ const filterOperandSchema = z.enum([
319
+ "eq",
320
+ "neq",
321
+ "contains",
322
+ "starts_with",
323
+ "gt",
324
+ "gte",
325
+ "lt",
326
+ "lte",
327
+ "in",
328
+ "is_null",
329
+ "is_not_null"
330
+ ], "geçerli bir karşılaştırma operatörü olmalı");
331
+ /**
332
+ * Aktivite türü (spec §6.2) — `stage_change` Faz 5 Task 3'ün `/move` ucunun
333
+ * ürettiği otomatik aktiviteyle eşleşir (bkz. `apps/api/src/routes/deals.ts`).
334
+ */
335
+ const activityKindSchema = z.enum([
336
+ "call",
337
+ "meeting",
338
+ "email",
339
+ "note",
340
+ "task",
341
+ "stage_change"
342
+ ], "geçerli bir aktivite türü olmalı (call, meeting, email, note, task, stage_change)");
343
+ /** Spec'te tam liste verilmemiş — makul bir başlangıç kümesi (bkz. plan "Yeni ortak altyapı kararları / D") */
344
+ const taskStatusSchema = z.enum([
345
+ "todo",
346
+ "in_progress",
347
+ "done",
348
+ "cancelled"
349
+ ], "geçerli bir durum olmalı (todo, in_progress, done, cancelled)");
350
+ /** Spec'in verdiği tam liste (§6.2) */
351
+ const taskPrioritySchema = z.enum([
352
+ "low",
353
+ "normal",
354
+ "high",
355
+ "urgent"
356
+ ], "geçerli bir öncelik olmalı (low, normal, high, urgent)");
357
+ z.object({
358
+ targetType: targetTypeSchema,
359
+ targetId: z.uuid("geçerli bir hedef kimliği olmalı")
360
+ });
361
+ /**
362
+ * Kaydedilmiş görünüm katalogları (spec §6.5, Faz 5 Task 5).
363
+ * `savedViewResourceSchema` = `CRM_RESOURCES`'ın zod karşılığı — `fields.ts`
364
+ * DEĞİŞTİRİLMEDEN (`CRM_RESOURCES` sabiti oradan İÇE AKTARILIR, yeniden
365
+ * TANIMLANMAZ, tek gerçek kaynak korunur).
366
+ */
367
+ const savedViewResourceSchema = z.enum(CRM_RESOURCES, "geçerli bir CRM kaynağı olmalı (contacts, companies, deals, tasks)");
368
+ const savedViewLayoutSchema = z.enum(["table", "kanban"], "geçerli bir düzen olmalı (table, kanban)");
369
+ const savedViewVisibilitySchema = z.enum(["private", "tenant"], "geçerli bir görünürlük olmalı (private, tenant)");
370
+ const filterLogicalOperatorSchema = z.enum([
371
+ "and",
372
+ "or",
373
+ "not"
374
+ ], "geçerli bir mantıksal operatör olmalı (and, or, not)");
375
+ /**
376
+ * `numeric(18,2)` DB kolonuna eşlenen PARA alanları için ORTAK biçim
377
+ * doğrulaması (spec §6.1 — para asla native `number`/aritmetikle hesaplanmaz,
378
+ * yalnız string olarak taşınır, `bignumber.js` dışında ASLA işlenmez).
379
+ *
380
+ * [Fix round 2 — doğruluk reviewer'ının bulduğu, Task 3'ün `deals.amount`
381
+ * fix'iyle BİREBİR aynı sınıf hata]: `companies.annual_revenue` DB'de `text`
382
+ * kolondu ama `FIELD_CATALOG.companies.annual_revenue` `type:'money'`,
383
+ * `sortable:true`, `filterable:true` ilan ediyordu — `text` kolonda `ORDER
384
+ * BY`/`>`/`<` LEXICOGRAPHIC karşılaştırma yapar (`"100" < "20" < "9"`), bu
385
+ * SESSİZ bir doğruluk hatasıdır. Bu şema, Task 3'ün önceden yalnız
386
+ * `deals.ts` içinde TANIMLADIĞI `dealAmountSchema`'nın (aynı kısıtlar: en
387
+ * fazla 2 ondalık basamak, en fazla 18 anlamlı basamak — `numeric(18,2)`
388
+ * sınırı) buraya TAŞINMIŞ/PAYLAŞILAN hâlidir — İKİ ayrı DB kolonu (deals.amount,
389
+ * companies.annual_revenue) TEK doğrulama mantığından geçer (drift riski
390
+ * olmasın diye, DRY).
391
+ */
392
+ const numericMoneySchema = z.string().trim().max(40, "en fazla 40 karakter olabilir").regex(/^-?\d+(\.\d{1,2})?$/, "geçerli bir ondalık tutar olmalı, en fazla 2 ondalık basamak (örn. 1234.56)").refine((v) => v.replace(/[-.]/g, "").length <= 18, "en fazla 18 anlamlı basamak olabilir (numeric(18,2) sınırı)");
393
+ const pageQuerySchema = z.object({
394
+ cursor: z.string().max(2e3, "geçersiz sayfalama imleci").optional(),
395
+ limit: z.coerce.number("sayı olmalı").int("tam sayı olmalı").min(1, "en az 1 olmalı").max(100, `en fazla 100 olabilir`).optional()
396
+ });
397
+ function pagedResponseSchema(itemSchema) {
398
+ return z.object({
399
+ items: z.array(itemSchema),
400
+ nextCursor: z.string().nullable(),
401
+ hasMore: z.boolean()
402
+ });
403
+ }
404
+ //#endregion
405
+ //#region packages/contracts/src/notes.ts
406
+ /**
407
+ * Not (note) sözleşmeleri (spec §6.2/§6.3) — `tasks.ts`'in BİREBİR aynı kalıbı,
408
+ * `assigneeUserId` yerine `authorUserId`, `status`/`priority` YOK.
409
+ */
410
+ const noteBodySchema = z.record(z.string(), z.unknown());
411
+ const createNoteRequestSchema = z.object({
412
+ title: z.string().trim().max(200, "en fazla 200 karakter olabilir").optional(),
413
+ body: noteBodySchema.optional(),
414
+ bodyFormat: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
415
+ authorUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional()
416
+ });
417
+ z.object({
418
+ title: z.string().trim().max(200, "en fazla 200 karakter olabilir").nullable().optional(),
419
+ body: noteBodySchema.optional(),
420
+ bodyFormat: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
421
+ authorUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").nullable().optional()
422
+ });
423
+ const noteLinkResponseSchema = z.object({
424
+ id: z.uuid(),
425
+ targetType: targetTypeSchema,
426
+ targetId: z.uuid(),
427
+ targetCachedName: z.string(),
428
+ createdAt: z.iso.datetime()
429
+ });
430
+ const noteResponseSchema = z.object({
431
+ id: z.uuid(),
432
+ title: z.string().nullable(),
433
+ body: noteBodySchema.nullable(),
434
+ bodyFormat: z.string().nullable(),
435
+ authorUserId: z.uuid().nullable(),
436
+ createdAt: z.iso.datetime(),
437
+ updatedAt: z.iso.datetime()
438
+ });
439
+ noteResponseSchema.extend({ links: z.array(noteLinkResponseSchema) });
440
+ pagedResponseSchema(noteResponseSchema);
441
+ //#endregion
442
+ //#region packages/contracts/src/tasks.ts
443
+ /**
444
+ * Görev (task) sözleşmeleri (spec §6.2/§6.3).
445
+ *
446
+ * KRİTİK KURAL: `dueAt`/`completedAt` kullanıcının Date/Time Handling
447
+ * Guideline'ına uygun olarak `apps/api/src/routes/tasks.ts`'te luxon
448
+ * (`DateTime`) ile dönüştürülür — burada yalnız ISO 8601 biçim doğrulaması
449
+ * yapılır (zod), gerçek dönüşüm route katmanının işidir.
450
+ *
451
+ * `assigneeUserId` PATCH'te `null` gönderilerek atamanın KALDIRILMASI
452
+ * desteklenir (`nullable().optional()`) — `undefined` "değiştirme", `null`
453
+ * "kaldır" anlamına gelir (companies/contacts/deals'ın PARTIAL şemalarından
454
+ * FARKLI bir kalıp, çünkü onlarda "atamayı kaldır" ihtiyacı yoktu).
455
+ */
456
+ const taskBodySchema = z.record(z.string(), z.unknown());
457
+ const createTaskRequestSchema = z.object({
458
+ title: z.string().trim().min(1, "başlık gerekli").max(200, "en fazla 200 karakter olabilir"),
459
+ body: taskBodySchema.optional(),
460
+ bodyFormat: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
461
+ dueAt: z.iso.datetime("geçerli bir tarih olmalı").optional(),
462
+ status: taskStatusSchema.default("todo"),
463
+ priority: taskPrioritySchema.default("normal"),
464
+ assigneeUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional()
465
+ });
466
+ z.object({
467
+ title: z.string().trim().min(1, "başlık gerekli").max(200, "en fazla 200 karakter olabilir").optional(),
468
+ body: taskBodySchema.optional(),
469
+ bodyFormat: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
470
+ dueAt: z.iso.datetime("geçerli bir tarih olmalı").nullable().optional(),
471
+ status: taskStatusSchema.optional(),
472
+ priority: taskPrioritySchema.optional(),
473
+ assigneeUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").nullable().optional()
474
+ });
475
+ const taskLinkResponseSchema = z.object({
476
+ id: z.uuid(),
477
+ targetType: targetTypeSchema,
478
+ targetId: z.uuid(),
479
+ targetCachedName: z.string(),
480
+ createdAt: z.iso.datetime()
481
+ });
482
+ const taskResponseSchema = z.object({
483
+ id: z.uuid(),
484
+ title: z.string(),
485
+ body: taskBodySchema.nullable(),
486
+ bodyFormat: z.string().nullable(),
487
+ dueAt: z.iso.datetime().nullable(),
488
+ completedAt: z.iso.datetime().nullable(),
489
+ status: taskStatusSchema,
490
+ priority: taskPrioritySchema,
491
+ assigneeUserId: z.uuid().nullable(),
492
+ createdAt: z.iso.datetime(),
493
+ updatedAt: z.iso.datetime()
494
+ });
495
+ taskResponseSchema.extend({ links: z.array(taskLinkResponseSchema) });
496
+ pagedResponseSchema(taskResponseSchema);
497
+ //#endregion
498
+ //#region packages/contracts/src/activities.ts
499
+ /**
500
+ * Aktivite (activity) sözleşmeleri (spec §6.2/§6.3).
501
+ *
502
+ * KRİTİK KURAL: `updateActivityRequestSchema` `.strict()`'tir ve `kind`/
503
+ * `targetType`/`targetId`'yi hiç TANIMLAMAZ — bir aktivitenin hedefi/türü
504
+ * OLUŞTURULDUKTAN SONRA değiştirilemez (bkz. `packages/db/src/schema/activities.ts`
505
+ * dosya başı yorumu, `apps/api/src/routes/activities.ts`). `deals.ts`'teki
506
+ * `updateDealRequestSchema`'nın AYNI kalıbı (bkz. o dosyanın dosya başı yorumu).
507
+ *
508
+ * TEKNİK BORÇ KAPANIŞI (Faz 6 Task 7): `GET /v1/tenants/:id/targets/
509
+ * :targetType/:targetId/timeline` ucu Faz 5 Task 4'ün plan dışı eklediği bir
510
+ * uçtu ve YANIT ZARFI için bir sözleşme şeması export ETMEMİŞTİ — SPA
511
+ * (`apps/dashboard/src/lib/timeline-one-shot.ts`) bu yüzden kendi elle yazılmış
512
+ * bir tipe (`.array()` bileşimi) doğrulama yapıyordu. `targetTimelineResponseSchema`
513
+ * artık TEK gerçek kaynak; iki taraf da (`apps/api/src/routes/activities.ts`
514
+ * VE SPA) buradan okur. `TIMELINE_LIMIT_PER_RESOURCE` de AYNI nedenle burada:
515
+ * backend'in `activities.ts`'teki elle kopyalanmış `= 50` sabiti ile SPA'nın
516
+ * `timeline-panel.tsx`'teki `TIMELINE_ONE_SHOT_CAP = 50` sabiti birbirinden
517
+ * BAĞIMSIZ, elle senkron tutuluyordu (drift riski, bkz. Faz 6 Task 6 raporu
518
+ * "Endişeler").
519
+ */
520
+ const activityPropertiesSchema = z.record(z.string(), z.unknown());
521
+ const createActivityRequestSchema = z.object({
522
+ kind: activityKindSchema,
523
+ subject: z.string().trim().max(200, "en fazla 200 karakter olabilir").optional(),
524
+ body: z.string().trim().max(1e4, "en fazla 10000 karakter olabilir").optional(),
525
+ happensAt: z.iso.datetime("geçerli bir tarih olmalı"),
526
+ durationMinutes: z.number("sayı olmalı").int("tam sayı olmalı").min(0, "negatif olamaz").max(43200, "en fazla 43200 dakika (30 gün) olabilir").optional(),
527
+ targetType: targetTypeSchema,
528
+ targetId: z.uuid("geçerli bir hedef kimliği olmalı"),
529
+ actorUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional(),
530
+ properties: activityPropertiesSchema.optional()
531
+ });
532
+ z.object({
533
+ subject: z.string().trim().max(200, "en fazla 200 karakter olabilir").optional(),
534
+ body: z.string().trim().max(1e4, "en fazla 10000 karakter olabilir").optional(),
535
+ happensAt: z.iso.datetime("geçerli bir tarih olmalı").optional(),
536
+ durationMinutes: z.number("sayı olmalı").int("tam sayı olmalı").min(0, "negatif olamaz").max(43200, "en fazla 43200 dakika (30 gün) olabilir").optional(),
537
+ actorUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional(),
538
+ properties: activityPropertiesSchema.optional()
539
+ }).strict();
540
+ const activityResponseSchema = z.object({
541
+ id: z.uuid(),
542
+ kind: activityKindSchema,
543
+ subject: z.string().nullable(),
544
+ body: z.string().nullable(),
545
+ happensAt: z.iso.datetime(),
546
+ durationMinutes: z.number().nullable(),
547
+ targetType: targetTypeSchema,
548
+ targetId: z.uuid(),
549
+ targetCachedName: z.string(),
550
+ properties: activityPropertiesSchema,
551
+ actorUserId: z.uuid().nullable(),
552
+ createdAt: z.iso.datetime(),
553
+ updatedAt: z.iso.datetime()
554
+ });
555
+ pagedResponseSchema(activityResponseSchema);
556
+ z.object({
557
+ activities: activityResponseSchema.array(),
558
+ tasks: taskResponseSchema.array(),
559
+ notes: noteResponseSchema.array()
560
+ });
561
+ clone();
562
+ var isNumeric = /^-?(?:\d+(?:\.\d*)?|\.\d+)(?:e[+-]?\d+)?$/i;
563
+ var mathceil = Math.ceil;
564
+ var mathfloor = Math.floor;
565
+ var bignumberError = "[BigNumber Error] ";
566
+ var BASE = 0x5af3107a4000;
567
+ var LOG_BASE = 14;
568
+ var MAX_SAFE_INTEGER = 9007199254740991;
569
+ var POWS_TEN = [
570
+ 1,
571
+ 10,
572
+ 100,
573
+ 1e3,
574
+ 1e4,
575
+ 1e5,
576
+ 1e6,
577
+ 1e7,
578
+ 1e8,
579
+ 1e9,
580
+ 1e10,
581
+ 1e11,
582
+ 0xe8d4a51000,
583
+ 0x9184e72a000
584
+ ];
585
+ var SQRT_BASE = 1e7;
586
+ var MAX = 1e9;
587
+ function clone(configObject) {
588
+ var div, convertBase, basePrefix = /^(-?)0([xbo])(?=[^.])/i, isInfinityOrNaN = /^-?(Infinity|NaN)$/, whitespaceOrPlus = /^\s*\+(?!-)|^\s+|\s+$/g, P = BigNumber.prototype = {
589
+ constructor: BigNumber,
590
+ toString: null,
591
+ valueOf: null
592
+ }, ONE = new BigNumber(1), DECIMAL_PLACES = 20, ROUNDING_MODE = 4, TO_EXP_NEG = -7, TO_EXP_POS = 21, MIN_EXP = -1e7, MAX_EXP = 1e7, CRYPTO = false, STRICT = true, MODULO_MODE = 1, POW_PRECISION = 0, FORMAT = {
593
+ prefix: "",
594
+ negativeSign: "-",
595
+ positiveSign: "",
596
+ groupSeparator: ",",
597
+ groupSize: 3,
598
+ secondaryGroupSize: 0,
599
+ decimalSeparator: ".",
600
+ fractionGroupSeparator: "",
601
+ fractionGroupSize: 0,
602
+ suffix: ""
603
+ }, ALPHABET = "0123456789abcdefghijklmnopqrstuvwxyz";
604
+ function BigNumber(v, b) {
605
+ var e, i, str, t, x = this;
606
+ if (!(x instanceof BigNumber)) return new BigNumber(v, b);
607
+ t = typeof v;
608
+ if (b == null) {
609
+ if (isBigNumber(v)) {
610
+ x.s = v.s;
611
+ if (!v.c || v.e > MAX_EXP) x.c = x.e = null;
612
+ else if (v.e < MIN_EXP) x.c = [x.e = 0];
613
+ else {
614
+ x.e = v.e;
615
+ x.c = v.c.slice();
616
+ }
617
+ return;
618
+ }
619
+ if (t == "number") {
620
+ if (v * 0 != 0) {
621
+ x.s = isNaN(v) ? null : v < 0 ? -1 : 1;
622
+ x.c = x.e = null;
623
+ return;
624
+ }
625
+ x.s = 1 / v < 0 ? (v = -v, -1) : 1;
626
+ if (v === ~~v) {
627
+ for (e = 0, i = v; i >= 10; i /= 10, e++);
628
+ if (e > MAX_EXP) x.c = x.e = null;
629
+ else {
630
+ x.e = e;
631
+ x.c = [v];
632
+ }
633
+ return;
634
+ }
635
+ return parseValidString(x, String(v));
636
+ }
637
+ if (t == "bigint") {
638
+ x.s = v < 0 ? (v = -v, -1) : 1;
639
+ return parseValidString(x, String(v));
640
+ }
641
+ if (t == "string") str = v;
642
+ else {
643
+ if (STRICT) throw Error(bignumberError + "BigNumber, string, number, or BigInt expected: " + v);
644
+ str = String(v);
645
+ }
646
+ if (isNumeric.test(str)) {
647
+ x.s = str.charCodeAt(0) == 45 ? (str = str.slice(1), -1) : 1;
648
+ return parseValidString(x, str);
649
+ }
650
+ str = str.replace(whitespaceOrPlus, "");
651
+ if (isInfinityOrNaN.test(str)) {
652
+ x.s = isNaN(str) ? null : str < 0 ? -1 : 1;
653
+ x.c = x.e = null;
654
+ return;
655
+ }
656
+ str = str.replace(basePrefix, function(m, p1, p2) {
657
+ b = (p2 = p2.toLowerCase()) == "x" ? 16 : p2 == "b" ? 2 : 8;
658
+ return p1;
659
+ });
660
+ if (b) return parseBaseString(x, str, b, v);
661
+ str = str.replace(/(\d)_(?=\d)/g, "$1");
662
+ if (isNumeric.test(str)) {
663
+ x.s = str.charCodeAt(0) == 45 ? (str = str.slice(1), -1) : 1;
664
+ return parseValidString(x, str);
665
+ }
666
+ if (STRICT) throw Error(bignumberError + "Not a number: " + v);
667
+ x.s = x.c = x.e = null;
668
+ } else {
669
+ if (t != "string") {
670
+ if (STRICT) throw Error(bignumberError + "String expected: " + v);
671
+ v = String(v);
672
+ }
673
+ intCheck(b, 2, ALPHABET.length, "Base");
674
+ parseBaseString(x, v.replace(whitespaceOrPlus, ""), b, v);
675
+ }
676
+ }
677
+ BigNumber.clone = clone;
678
+ BigNumber.ROUND_UP = 0;
679
+ BigNumber.ROUND_DOWN = 1;
680
+ BigNumber.ROUND_CEIL = 2;
681
+ BigNumber.ROUND_FLOOR = 3;
682
+ BigNumber.ROUND_HALF_UP = 4;
683
+ BigNumber.ROUND_HALF_DOWN = 5;
684
+ BigNumber.ROUND_HALF_EVEN = 6;
685
+ BigNumber.ROUND_HALF_CEIL = 7;
686
+ BigNumber.ROUND_HALF_FLOOR = 8;
687
+ BigNumber.EUCLID = 9;
688
+ BigNumber.config = BigNumber.set = function(obj) {
689
+ var p, v;
690
+ if (obj != null) if (typeof obj == "object") {
691
+ if (obj.hasOwnProperty(p = "DECIMAL_PLACES")) DECIMAL_PLACES = intCheck(obj[p], 0, MAX, p);
692
+ if (obj.hasOwnProperty(p = "ROUNDING_MODE")) ROUNDING_MODE = intCheck(obj[p], 0, 8, p);
693
+ if (obj.hasOwnProperty(p = "EXPONENTIAL_AT")) {
694
+ v = obj[p];
695
+ if (isArray(v)) {
696
+ intCheck(v[0], -MAX, 0, p);
697
+ intCheck(v[1], 0, MAX, p);
698
+ TO_EXP_NEG = v[0];
699
+ TO_EXP_POS = v[1];
700
+ } else TO_EXP_NEG = -(TO_EXP_POS = intCheck(v, -MAX, MAX, p) < 0 ? -v : v);
701
+ }
702
+ if (obj.hasOwnProperty(p = "RANGE")) {
703
+ v = obj[p];
704
+ if (v) if (isArray(v)) {
705
+ intCheck(v[0], -MAX, -1, p);
706
+ intCheck(v[1], 1, MAX, p);
707
+ MIN_EXP = v[0];
708
+ MAX_EXP = v[1];
709
+ } else MIN_EXP = -(MAX_EXP = intCheck(v, -MAX, MAX, p) < 0 ? -v : v);
710
+ else throw Error(bignumberError + p + " cannot be zero: " + v);
711
+ }
712
+ if (obj.hasOwnProperty(p = "CRYPTO")) {
713
+ v = obj[p];
714
+ if (v === !!v) if (v) if (typeof crypto != "undefined" && crypto && (crypto.getRandomValues || crypto.randomBytes)) CRYPTO = v;
715
+ else {
716
+ CRYPTO = !v;
717
+ throw Error(bignumberError + "crypto unavailable");
718
+ }
719
+ else CRYPTO = v;
720
+ else throw Error(bignumberError + p + " not true or false: " + v);
721
+ }
722
+ if (obj.hasOwnProperty(p = "STRICT")) {
723
+ v = obj[p];
724
+ if (v === !!v) STRICT = v;
725
+ else throw Error(bignumberError + p + " not true or false: " + v);
726
+ }
727
+ if (obj.hasOwnProperty(p = "MODULO_MODE")) MODULO_MODE = intCheck(obj[p], 0, 9, p);
728
+ if (obj.hasOwnProperty(p = "POW_PRECISION")) POW_PRECISION = intCheck(obj[p], 0, MAX, p);
729
+ if (obj.hasOwnProperty(p = "FORMAT")) {
730
+ v = obj[p];
731
+ if (typeof v == "object") {
732
+ for (p in v) if (v.hasOwnProperty(p) && FORMAT.hasOwnProperty(p)) FORMAT[p] = v[p];
733
+ } else throw Error(bignumberError + p + " not an object: " + v);
734
+ }
735
+ if (obj.hasOwnProperty(p = "ALPHABET")) {
736
+ v = obj[p];
737
+ if (typeof v == "string" && !/^.?$|[+\-.\s]|(.).*\1/.test(v)) ALPHABET = v;
738
+ else throw Error(bignumberError + p + " invalid: " + v);
739
+ }
740
+ } else throw Error(bignumberError + "Object expected: " + obj);
741
+ return {
742
+ DECIMAL_PLACES,
743
+ ROUNDING_MODE,
744
+ EXPONENTIAL_AT: [TO_EXP_NEG, TO_EXP_POS],
745
+ RANGE: [MIN_EXP, MAX_EXP],
746
+ CRYPTO,
747
+ STRICT,
748
+ MODULO_MODE,
749
+ POW_PRECISION,
750
+ FORMAT,
751
+ ALPHABET
752
+ };
753
+ };
754
+ BigNumber.fromFormat = function(str, options) {
755
+ if (typeof str !== "string") throw Error(bignumberError + "Not a string: " + str);
756
+ if (options == null) options = FORMAT;
757
+ else if (typeof options != "object") throw Error(bignumberError + "Argument not an object: " + options);
758
+ else options = resolveFormatOptions(options);
759
+ var i, isNeg, integerPart, fractionPart, negativeSign = options.negativeSign || "-", positiveSign = options.positiveSign || "", prefix = options.prefix || "", suffix = options.suffix || "", groupSeparator = options.groupSeparator || "", decimalSeparator = options.decimalSeparator || ".", fractionGroupSeparator = options.fractionGroupSeparator || "";
760
+ if (prefix && str.indexOf(prefix) === 0) str = str.slice(prefix.length);
761
+ if (suffix && str.lastIndexOf(suffix) === str.length - suffix.length) str = str.slice(0, -suffix.length);
762
+ if (negativeSign && str.indexOf(negativeSign) === 0) {
763
+ str = str.slice(negativeSign.length);
764
+ isNeg = true;
765
+ } else if (positiveSign && str.indexOf(positiveSign) === 0) str = str.slice(positiveSign.length);
766
+ i = str.indexOf(decimalSeparator);
767
+ if (i < 0) {
768
+ if (groupSeparator) while (str.indexOf(groupSeparator) > -1) str = str.replace(groupSeparator, "");
769
+ } else {
770
+ integerPart = str.slice(0, i);
771
+ fractionPart = str.slice(i + decimalSeparator.length);
772
+ if (groupSeparator) while (integerPart.indexOf(groupSeparator) > -1) integerPart = integerPart.replace(groupSeparator, "");
773
+ if (fractionGroupSeparator) while (fractionPart.indexOf(fractionGroupSeparator) > -1) fractionPart = fractionPart.replace(fractionGroupSeparator, "");
774
+ str = integerPart + "." + fractionPart;
775
+ }
776
+ return new BigNumber(isNeg ? "-" + str : str);
777
+ };
778
+ BigNumber.isBigNumber = function(v) {
779
+ if (!isBigNumber(v)) return false;
780
+ var i, n, c = v.c, e = v.e, s = v.s;
781
+ if (!isArray(c)) return c === null && e === null && (s === null || s === 1 || s === -1);
782
+ if (s !== 1 && s !== -1 || e < -MAX || e > MAX || e !== mathfloor(e)) return false;
783
+ if (c[0] === 0) return e === 0 && c.length === 1;
784
+ i = (e + 1) % LOG_BASE;
785
+ if (i < 1) i += LOG_BASE;
786
+ if (String(c[0]).length !== i) return false;
787
+ for (i = 0; i < c.length; i++) {
788
+ n = c[i];
789
+ if (n < 0 || n >= BASE || n !== mathfloor(n)) return false;
790
+ }
791
+ return n !== 0;
792
+ };
793
+ BigNumber.maximum = BigNumber.max = function() {
794
+ return maxOrMin(arguments, -1);
795
+ };
796
+ BigNumber.minimum = BigNumber.min = function() {
797
+ return maxOrMin(arguments, 1);
798
+ };
799
+ BigNumber.random = (function() {
800
+ var pow2_53 = 9007199254740992;
801
+ var random53bitInt = Math.random() * pow2_53 & 2097151 ? function() {
802
+ return mathfloor(Math.random() * pow2_53);
803
+ } : function() {
804
+ return (Math.random() * 1073741824 | 0) * 8388608 + (Math.random() * 8388608 | 0);
805
+ };
806
+ return function(dp) {
807
+ var a, b, e, k, v, i = 0, c = [], rand = new BigNumber(ONE);
808
+ dp = dp == null ? DECIMAL_PLACES : intCheck(dp, 0, MAX);
809
+ k = mathceil(dp / LOG_BASE);
810
+ if (CRYPTO) if (crypto.getRandomValues) {
811
+ a = crypto.getRandomValues(new Uint32Array(k *= 2));
812
+ for (; i < k;) {
813
+ v = a[i] * 131072 + (a[i + 1] >>> 11);
814
+ if (v >= 9e15) {
815
+ b = crypto.getRandomValues(/* @__PURE__ */ new Uint32Array(2));
816
+ a[i] = b[0];
817
+ a[i + 1] = b[1];
818
+ } else {
819
+ c.push(v % 0x5af3107a4000);
820
+ i += 2;
821
+ }
822
+ }
823
+ i = k / 2;
824
+ } else if (crypto.randomBytes) {
825
+ a = crypto.randomBytes(k *= 7);
826
+ for (; i < k;) {
827
+ v = (a[i] & 31) * 281474976710656 + a[i + 1] * 1099511627776 + a[i + 2] * 4294967296 + a[i + 3] * 16777216 + (a[i + 4] << 16) + (a[i + 5] << 8) + a[i + 6];
828
+ if (v >= 9e15) crypto.randomBytes(7).copy(a, i);
829
+ else {
830
+ c.push(v % 0x5af3107a4000);
831
+ i += 7;
832
+ }
833
+ }
834
+ i = k / 7;
835
+ } else {
836
+ CRYPTO = false;
837
+ throw Error(bignumberError + "crypto unavailable");
838
+ }
839
+ if (!CRYPTO) for (; i < k;) {
840
+ v = random53bitInt();
841
+ if (v < 9e15) c[i++] = v % 0x5af3107a4000;
842
+ }
843
+ k = c[--i];
844
+ dp %= LOG_BASE;
845
+ if (k && dp) {
846
+ v = POWS_TEN[LOG_BASE - dp];
847
+ c[i] = mathfloor(k / v) * v;
848
+ }
849
+ for (; c[i] === 0; c.pop(), i--);
850
+ if (i < 0) c = [e = 0];
851
+ else {
852
+ for (e = -1; c[0] === 0; c.splice(0, 1), e -= LOG_BASE);
853
+ for (i = 1, v = c[0]; v >= 10; v /= 10, i++);
854
+ if (i < LOG_BASE) e -= LOG_BASE - i;
855
+ }
856
+ rand.e = e;
857
+ rand.c = c;
858
+ return rand;
859
+ };
860
+ })();
861
+ BigNumber.sum = function() {
862
+ var i = 0, sum = new BigNumber(0);
863
+ for (; i < arguments.length;) sum = sum.plus(arguments[i++]);
864
+ return sum;
865
+ };
866
+ function parseValidString(x, str) {
867
+ var e, i, len;
868
+ if ((e = str.indexOf(".")) > -1) str = str.replace(".", "");
869
+ if ((i = str.search(/e/i)) > 0) {
870
+ if (e < 0) e = i;
871
+ e += +str.slice(i + 1);
872
+ str = str.substring(0, i);
873
+ } else if (e < 0) e = str.length;
874
+ for (i = 0; str.charCodeAt(i) === 48; i++);
875
+ for (len = str.length; str.charCodeAt(--len) === 48;);
876
+ if (str = str.slice(i, ++len)) {
877
+ len -= i;
878
+ e = e - i - 1;
879
+ if (e > MAX_EXP) x.c = x.e = null;
880
+ else if (e < MIN_EXP) x.c = [x.e = 0];
881
+ else {
882
+ x.e = e;
883
+ x.c = [];
884
+ i = (e + 1) % LOG_BASE;
885
+ if (e < 0) i += LOG_BASE;
886
+ if (i < len) {
887
+ if (i) x.c.push(+str.slice(0, i));
888
+ for (len -= LOG_BASE; i < len;) x.c.push(+str.slice(i, i += LOG_BASE));
889
+ i = LOG_BASE - (str = str.slice(i)).length;
890
+ } else i -= len;
891
+ for (; i--; str += "0");
892
+ x.c.push(+str);
893
+ }
894
+ } else x.c = [x.e = 0];
895
+ }
896
+ function parseBaseString(x, str, b, v) {
897
+ var c, len, alphabet = ALPHABET.slice(0, b), i = 0, clean = "", hasDot = false, prevIsNumeral = false, caseChanged = false;
898
+ x.s = str.charCodeAt(0) === 45 ? (str = str.slice(1), -1) : 1;
899
+ for (len = str.length; i < len; i++) {
900
+ c = str.charAt(i);
901
+ if (alphabet.indexOf(c) >= 0) {
902
+ clean += c;
903
+ prevIsNumeral = true;
904
+ continue;
905
+ }
906
+ if (c == "_") {
907
+ if (prevIsNumeral && i + 1 < len) {
908
+ prevIsNumeral = false;
909
+ continue;
910
+ }
911
+ } else if (c == ".") {
912
+ if (i == 0 || !hasDot && prevIsNumeral) {
913
+ if (i + 1 == len) break;
914
+ if (i == 0) clean = "0";
915
+ clean += c;
916
+ hasDot = true;
917
+ prevIsNumeral = false;
918
+ continue;
919
+ }
920
+ } else if (!caseChanged) {
921
+ if (str == str.toUpperCase() && alphabet == alphabet.toLowerCase() && (str = str.toLowerCase()) || str == str.toLowerCase() && alphabet == alphabet.toUpperCase() && (str = str.toUpperCase())) {
922
+ i = -1;
923
+ clean = "";
924
+ caseChanged = true;
925
+ hasDot = prevIsNumeral = false;
926
+ continue;
927
+ }
928
+ }
929
+ if (STRICT) throw Error(bignumberError + "Not a base " + b + " number: " + v);
930
+ x.s = x.c = x.e = null;
931
+ return;
932
+ }
933
+ parseValidString(x, convertBase(clean, b, 10, x.s));
934
+ }
935
+ convertBase = (function() {
936
+ var decimal = "0123456789";
937
+ function toBaseOut(str, baseIn, baseOut, alphabet) {
938
+ var j, arr = [0], arrL, i = 0, len = str.length;
939
+ for (; i < len;) {
940
+ for (arrL = arr.length; arrL--; arr[arrL] *= baseIn);
941
+ arr[0] += alphabet.indexOf(str.charAt(i++));
942
+ for (j = 0; j < arr.length; j++) if (arr[j] > baseOut - 1) {
943
+ if (arr[j + 1] == null) arr[j + 1] = 0;
944
+ arr[j + 1] += arr[j] / baseOut | 0;
945
+ arr[j] %= baseOut;
946
+ }
947
+ }
948
+ return arr.reverse();
949
+ }
950
+ return function(str, baseIn, baseOut, sign, callerIsToString) {
951
+ var alphabet, d, e, k, r, x, xc, y, i = str.indexOf("."), dp = DECIMAL_PLACES, rm = ROUNDING_MODE;
952
+ if (i >= 0) {
953
+ k = POW_PRECISION;
954
+ POW_PRECISION = 0;
955
+ str = str.replace(".", "");
956
+ y = new BigNumber(baseIn);
957
+ x = y.pow(str.length - i);
958
+ POW_PRECISION = k;
959
+ y.c = toBaseOut(toFixedPoint(coeffToString(x.c), x.e, "0"), 10, baseOut, decimal);
960
+ y.e = y.c.length;
961
+ }
962
+ xc = toBaseOut(str, baseIn, baseOut, callerIsToString ? (alphabet = ALPHABET, decimal) : (alphabet = decimal, ALPHABET));
963
+ e = k = xc.length;
964
+ for (; xc[--k] == 0; xc.pop());
965
+ if (!xc[0]) return alphabet.charAt(0);
966
+ if (i < 0) --e;
967
+ else {
968
+ x.c = xc;
969
+ x.e = e;
970
+ x.s = sign;
971
+ x = div(x, y, dp, rm, baseOut);
972
+ xc = x.c;
973
+ r = x.r;
974
+ e = x.e;
975
+ }
976
+ d = e + dp + 1;
977
+ i = xc[d];
978
+ k = baseOut / 2;
979
+ r = r || d < 0 || xc[d + 1] != null;
980
+ r = rm < 4 ? (i != null || r) && (rm == 0 || rm == (x.s < 0 ? 3 : 2)) : i > k || i == k && (rm == 4 || r || rm == 6 && xc[d - 1] & 1 || rm == (x.s < 0 ? 8 : 7));
981
+ if (d < 1 || !xc[0]) str = r ? toFixedPoint(alphabet.charAt(1), -dp, alphabet.charAt(0)) : alphabet.charAt(0);
982
+ else {
983
+ if (d < xc.length) xc.length = d;
984
+ if (r) for (--baseOut; ++xc[--d] > baseOut;) {
985
+ xc[d] = 0;
986
+ if (!d) {
987
+ ++e;
988
+ xc = [1].concat(xc);
989
+ }
990
+ }
991
+ for (k = xc.length; !xc[--k];);
992
+ for (i = 0, str = ""; i <= k; str += alphabet.charAt(xc[i++]));
993
+ str = toFixedPoint(str, e, alphabet.charAt(0));
994
+ }
995
+ return str;
996
+ };
997
+ })();
998
+ div = (function() {
999
+ function multiply(x, k, base) {
1000
+ var m, temp, xlo, xhi, carry = 0, i = x.length, klo = k % SQRT_BASE, khi = k / SQRT_BASE | 0;
1001
+ for (x = x.slice(); i--;) {
1002
+ xlo = x[i] % SQRT_BASE;
1003
+ xhi = x[i] / SQRT_BASE | 0;
1004
+ m = khi * xlo + xhi * klo;
1005
+ temp = klo * xlo + m % SQRT_BASE * SQRT_BASE + carry;
1006
+ carry = (temp / base | 0) + (m / SQRT_BASE | 0) + khi * xhi;
1007
+ x[i] = temp % base;
1008
+ }
1009
+ if (carry) x = [carry].concat(x);
1010
+ return x;
1011
+ }
1012
+ function compare(a, b, aL, bL) {
1013
+ var i, cmp;
1014
+ if (aL != bL) cmp = aL > bL ? 1 : -1;
1015
+ else for (i = cmp = 0; i < aL; i++) if (a[i] != b[i]) {
1016
+ cmp = a[i] > b[i] ? 1 : -1;
1017
+ break;
1018
+ }
1019
+ return cmp;
1020
+ }
1021
+ function subtract(a, b, aL, base) {
1022
+ var i = 0;
1023
+ for (; aL--;) {
1024
+ a[aL] -= i;
1025
+ i = a[aL] < b[aL] ? 1 : 0;
1026
+ a[aL] = i * base + a[aL] - b[aL];
1027
+ }
1028
+ for (; !a[0] && a.length > 1; a.splice(0, 1));
1029
+ }
1030
+ return function(x, y, dp, rm, base) {
1031
+ var cmp, e, i, more, n, prod, prodL, q, qc, rem, remL, rem0, xi, xL, yc0, yL, yz, s = x.s == y.s ? 1 : -1, xc = x.c, yc = y.c;
1032
+ if (!xc || !xc[0] || !yc || !yc[0]) return new BigNumber(!x.s || !y.s || (xc ? yc && xc[0] == yc[0] : !yc) ? NaN : xc && xc[0] == 0 || !yc ? s * 0 : s / 0);
1033
+ q = new BigNumber(s);
1034
+ qc = q.c = [];
1035
+ e = x.e - y.e;
1036
+ s = dp + e + 1;
1037
+ if (!base) {
1038
+ base = BASE;
1039
+ e = bitFloor(x.e / LOG_BASE) - bitFloor(y.e / LOG_BASE);
1040
+ s = s / LOG_BASE | 0;
1041
+ }
1042
+ for (i = 0; yc[i] == (xc[i] || 0); i++);
1043
+ if (yc[i] > (xc[i] || 0)) e--;
1044
+ if (s < 0) {
1045
+ qc.push(1);
1046
+ more = true;
1047
+ } else {
1048
+ xL = xc.length;
1049
+ yL = yc.length;
1050
+ i = 0;
1051
+ s += 2;
1052
+ n = mathfloor(base / (yc[0] + 1));
1053
+ if (n > 1) {
1054
+ yc = multiply(yc, n, base);
1055
+ xc = multiply(xc, n, base);
1056
+ yL = yc.length;
1057
+ xL = xc.length;
1058
+ }
1059
+ xi = yL;
1060
+ rem = xc.slice(0, yL);
1061
+ remL = rem.length;
1062
+ for (; remL < yL; rem[remL++] = 0);
1063
+ yz = yc.slice();
1064
+ yz = [0].concat(yz);
1065
+ yc0 = yc[0];
1066
+ if (yc[1] >= base / 2) yc0++;
1067
+ do {
1068
+ n = 0;
1069
+ cmp = compare(yc, rem, yL, remL);
1070
+ if (cmp < 0) {
1071
+ rem0 = rem[0];
1072
+ if (yL != remL) rem0 = rem0 * base + (rem[1] || 0);
1073
+ n = mathfloor(rem0 / yc0);
1074
+ if (n > 1) {
1075
+ if (n >= base) n = base - 1;
1076
+ prod = multiply(yc, n, base);
1077
+ prodL = prod.length;
1078
+ remL = rem.length;
1079
+ while (compare(prod, rem, prodL, remL) == 1) {
1080
+ n--;
1081
+ subtract(prod, yL < prodL ? yz : yc, prodL, base);
1082
+ prodL = prod.length;
1083
+ cmp = 1;
1084
+ }
1085
+ } else {
1086
+ if (n == 0) cmp = n = 1;
1087
+ prod = yc.slice();
1088
+ prodL = prod.length;
1089
+ }
1090
+ if (prodL < remL) prod = [0].concat(prod);
1091
+ subtract(rem, prod, remL, base);
1092
+ remL = rem.length;
1093
+ if (cmp == -1) while (compare(yc, rem, yL, remL) < 1) {
1094
+ n++;
1095
+ subtract(rem, yL < remL ? yz : yc, remL, base);
1096
+ remL = rem.length;
1097
+ }
1098
+ } else if (cmp === 0) {
1099
+ n++;
1100
+ rem = [0];
1101
+ }
1102
+ qc[i++] = n;
1103
+ if (rem[0]) rem[remL++] = xc[xi] || 0;
1104
+ else {
1105
+ rem = [xc[xi]];
1106
+ remL = 1;
1107
+ }
1108
+ } while ((xi++ < xL || rem[0] != null) && s--);
1109
+ more = rem[0] != null;
1110
+ if (!qc[0]) qc.splice(0, 1);
1111
+ }
1112
+ if (base == BASE) {
1113
+ for (i = 1, s = qc[0]; s >= 10; s /= 10, i++);
1114
+ round(q, dp + (q.e = i + e * LOG_BASE - 1) + 1, rm, more);
1115
+ } else {
1116
+ q.e = e;
1117
+ q.r = +more;
1118
+ }
1119
+ return q;
1120
+ };
1121
+ })();
1122
+ function format(n, i, rm, id) {
1123
+ var c0, e, ne, len, str;
1124
+ rm = rm == null ? ROUNDING_MODE : intCheck(rm, 0, 8);
1125
+ if (!n.c) return n.toString();
1126
+ c0 = n.c[0];
1127
+ ne = n.e;
1128
+ if (i == null) {
1129
+ str = coeffToString(n.c);
1130
+ str = id == 1 || id == 2 && (ne <= TO_EXP_NEG || ne >= TO_EXP_POS) ? toExponential(str, ne) : toFixedPoint(str, ne, "0");
1131
+ } else {
1132
+ n = round(new BigNumber(n), i, rm);
1133
+ e = n.e;
1134
+ str = coeffToString(n.c);
1135
+ len = str.length;
1136
+ if (id == 1 || id == 2 && (i <= e || e <= TO_EXP_NEG)) {
1137
+ for (; len < i; str += "0", len++);
1138
+ str = toExponential(str, e);
1139
+ } else {
1140
+ i -= ne + (id === 2 && e > ne);
1141
+ str = toFixedPoint(str, e, "0");
1142
+ if (e + 1 > len) {
1143
+ if (--i > 0) for (str += "."; i--; str += "0");
1144
+ } else {
1145
+ i += e - len;
1146
+ if (i > 0) {
1147
+ if (e + 1 == len) str += ".";
1148
+ for (; i--; str += "0");
1149
+ }
1150
+ }
1151
+ }
1152
+ }
1153
+ return n.s < 0 && c0 ? "-" + str : str;
1154
+ }
1155
+ function isBigNumber(v) {
1156
+ return v instanceof BigNumber || !!v && v._isBigNumber === true;
1157
+ }
1158
+ function maxOrMin(args, n) {
1159
+ var k, y, i = 1, x = new BigNumber(args[0]);
1160
+ for (; i < args.length; i++) {
1161
+ y = new BigNumber(args[i]);
1162
+ if (!y.s || (k = compare(x, y)) === n || k === 0 && x.s === n) x = y;
1163
+ }
1164
+ return x;
1165
+ }
1166
+ function normalise(n, c, e) {
1167
+ var i = 1, j = c.length;
1168
+ for (; !c[--j]; c.pop());
1169
+ for (j = c[0]; j >= 10; j /= 10, i++);
1170
+ if ((e = i + e * LOG_BASE - 1) > MAX_EXP) n.c = n.e = null;
1171
+ else if (e < MIN_EXP) n.c = [n.e = 0];
1172
+ else {
1173
+ n.e = e;
1174
+ n.c = c;
1175
+ }
1176
+ return n;
1177
+ }
1178
+ function resolveFormatOptions(options) {
1179
+ var key, resolved = {};
1180
+ for (key in FORMAT) if (FORMAT.hasOwnProperty(key)) resolved[key] = options.hasOwnProperty(key) ? options[key] : FORMAT[key];
1181
+ return resolved;
1182
+ }
1183
+ function round(x, sd, rm, r) {
1184
+ var d, i, j, k, n, ni, rd, xc = x.c, pows10 = POWS_TEN;
1185
+ if (xc) {
1186
+ out: {
1187
+ for (d = 1, k = xc[0]; k >= 10; k /= 10, d++);
1188
+ i = sd - d;
1189
+ if (i < 0) {
1190
+ i += LOG_BASE;
1191
+ j = sd;
1192
+ n = xc[ni = 0];
1193
+ rd = mathfloor(n / pows10[d - j - 1] % 10);
1194
+ } else {
1195
+ ni = mathceil((i + 1) / LOG_BASE);
1196
+ if (ni >= xc.length) if (r) {
1197
+ for (; xc.length <= ni; xc.push(0));
1198
+ n = rd = 0;
1199
+ d = 1;
1200
+ i %= LOG_BASE;
1201
+ j = i - LOG_BASE + 1;
1202
+ } else break out;
1203
+ else {
1204
+ n = k = xc[ni];
1205
+ for (d = 1; k >= 10; k /= 10, d++);
1206
+ i %= LOG_BASE;
1207
+ j = i - LOG_BASE + d;
1208
+ rd = j < 0 ? 0 : mathfloor(n / pows10[d - j - 1] % 10);
1209
+ }
1210
+ }
1211
+ r = r || sd < 0 || xc[ni + 1] != null || (j < 0 ? n : n % pows10[d - j - 1]);
1212
+ r = rm < 4 ? (rd || r) && (rm == 0 || rm == (x.s < 0 ? 3 : 2)) : rd > 5 || rd == 5 && (rm == 4 || r || rm == 6 && (i > 0 ? j > 0 ? n / pows10[d - j] : 0 : xc[ni - 1]) % 10 & 1 || rm == (x.s < 0 ? 8 : 7));
1213
+ if (sd < 1 || !xc[0]) {
1214
+ xc.length = 0;
1215
+ if (r) {
1216
+ sd -= x.e + 1;
1217
+ xc[0] = pows10[(LOG_BASE - sd % LOG_BASE) % LOG_BASE];
1218
+ x.e = -sd || 0;
1219
+ } else xc[0] = x.e = 0;
1220
+ return x;
1221
+ }
1222
+ if (i == 0) {
1223
+ xc.length = ni;
1224
+ k = 1;
1225
+ ni--;
1226
+ } else {
1227
+ xc.length = ni + 1;
1228
+ k = pows10[LOG_BASE - i];
1229
+ xc[ni] = j > 0 ? mathfloor(n / pows10[d - j] % pows10[j]) * k : 0;
1230
+ }
1231
+ if (r) for (;;) if (ni == 0) {
1232
+ for (i = 1, j = xc[0]; j >= 10; j /= 10, i++);
1233
+ j = xc[0] += k;
1234
+ for (k = 1; j >= 10; j /= 10, k++);
1235
+ if (i != k) {
1236
+ x.e++;
1237
+ if (xc[0] == BASE) xc[0] = 1;
1238
+ }
1239
+ break;
1240
+ } else {
1241
+ xc[ni] += k;
1242
+ if (xc[ni] != BASE) break;
1243
+ xc[ni--] = 0;
1244
+ k = 1;
1245
+ }
1246
+ for (i = xc.length; xc[--i] === 0; xc.pop());
1247
+ }
1248
+ if (x.e > MAX_EXP) x.c = x.e = null;
1249
+ else if (x.e < MIN_EXP) x.c = [x.e = 0];
1250
+ }
1251
+ return x;
1252
+ }
1253
+ function valueOf(n) {
1254
+ var str, e = n.e;
1255
+ if (e === null) return n.toString();
1256
+ str = coeffToString(n.c);
1257
+ str = e <= TO_EXP_NEG || e >= TO_EXP_POS ? toExponential(str, e) : toFixedPoint(str, e, "0");
1258
+ return n.s < 0 ? "-" + str : str;
1259
+ }
1260
+ P.absoluteValue = P.abs = function() {
1261
+ var x = new BigNumber(this);
1262
+ if (x.s < 0) x.s = 1;
1263
+ return x;
1264
+ };
1265
+ P.comparedTo = function(y, b) {
1266
+ return compare(this, new BigNumber(y, b));
1267
+ };
1268
+ P.decimalPlaces = P.dp = function(dp, rm) {
1269
+ var c, n, v, x = this;
1270
+ if (dp != null) return round(new BigNumber(x), intCheck(dp, -MAX, MAX) + x.e + 1, rm == null ? ROUNDING_MODE : intCheck(rm, 0, 8));
1271
+ if (!(c = x.c)) return null;
1272
+ n = ((v = c.length - 1) - bitFloor(this.e / LOG_BASE)) * LOG_BASE;
1273
+ if (v = c[v]) for (; v % 10 == 0; v /= 10, n--);
1274
+ if (n < 0) n = 0;
1275
+ return n;
1276
+ };
1277
+ P.dividedBy = P.div = function(y, b) {
1278
+ return div(this, new BigNumber(y, b), DECIMAL_PLACES, ROUNDING_MODE);
1279
+ };
1280
+ P.dividedToIntegerBy = P.idiv = function(y, b) {
1281
+ return div(this, new BigNumber(y, b), 0, 1);
1282
+ };
1283
+ P.exponentiatedBy = P.pow = function(n, m) {
1284
+ var half, isModExp, i, k, more, nIsBig, nIsNeg, nIsOdd, y, x = this;
1285
+ n = new BigNumber(n);
1286
+ if (n.c && !n.isInteger()) throw Error(bignumberError + "Exponent not an integer: " + valueOf(n));
1287
+ if (m != null) m = new BigNumber(m);
1288
+ nIsBig = n.e > 14;
1289
+ if (!x.c || !x.c[0] || x.c[0] == 1 && !x.e && x.c.length == 1 || !n.c || !n.c[0]) {
1290
+ y = new BigNumber(Math.pow(+valueOf(x), nIsBig ? n.s * (2 - isOdd(n)) : +valueOf(n)));
1291
+ return m ? y.mod(m) : y;
1292
+ }
1293
+ nIsNeg = n.s < 0;
1294
+ if (m) {
1295
+ if (m.c ? !m.c[0] : !m.s) return new BigNumber(NaN);
1296
+ isModExp = !nIsNeg && x.isInteger() && m.isInteger();
1297
+ if (isModExp) x = x.mod(m);
1298
+ } else if (n.e > 9 && (x.e > 0 || x.e < -1 || (x.e == 0 ? x.c[0] > 1 || nIsBig && x.c[1] >= 24e7 : x.c[0] < 8e13 || nIsBig && x.c[0] <= 9999975e7))) {
1299
+ k = x.s < 0 && isOdd(n) ? -0 : 0;
1300
+ if (x.e > -1) k = 1 / k;
1301
+ return new BigNumber(nIsNeg ? 1 / k : k);
1302
+ } else if (POW_PRECISION) k = mathceil(POW_PRECISION / LOG_BASE + 2);
1303
+ if (nIsBig) {
1304
+ half = new BigNumber(.5);
1305
+ if (nIsNeg) n.s = 1;
1306
+ nIsOdd = isOdd(n);
1307
+ } else {
1308
+ i = Math.abs(+valueOf(n));
1309
+ nIsOdd = i % 2;
1310
+ }
1311
+ y = new BigNumber(ONE);
1312
+ for (;;) {
1313
+ if (nIsOdd) {
1314
+ y = y.times(x);
1315
+ if (!y.c) break;
1316
+ if (k) {
1317
+ if (y.c.length > k) y.c.length = k;
1318
+ } else if (isModExp) y = y.mod(m);
1319
+ }
1320
+ if (i) {
1321
+ i = mathfloor(i / 2);
1322
+ if (i === 0) break;
1323
+ nIsOdd = i % 2;
1324
+ } else {
1325
+ n = n.times(half);
1326
+ round(n, n.e + 1, 1);
1327
+ if (n.e > 14) nIsOdd = isOdd(n);
1328
+ else {
1329
+ i = +valueOf(n);
1330
+ if (i === 0) break;
1331
+ nIsOdd = i % 2;
1332
+ }
1333
+ }
1334
+ x = x.times(x);
1335
+ if (k) {
1336
+ if (x.c && x.c.length > k) x.c.length = k;
1337
+ } else if (isModExp) x = x.mod(m);
1338
+ }
1339
+ if (isModExp) return y;
1340
+ if (nIsNeg) y = ONE.div(y);
1341
+ return m ? y.mod(m) : k ? round(y, POW_PRECISION, ROUNDING_MODE, more) : y;
1342
+ };
1343
+ P.integerValue = function(rm) {
1344
+ var n = new BigNumber(this);
1345
+ return round(n, n.e + 1, rm == null ? ROUNDING_MODE : intCheck(rm, 0, 8));
1346
+ };
1347
+ P.isEqualTo = P.eq = function(y, b) {
1348
+ return compare(this, new BigNumber(y, b)) === 0;
1349
+ };
1350
+ P.isFinite = function() {
1351
+ return !!this.c;
1352
+ };
1353
+ P.isGreaterThan = P.gt = function(y, b) {
1354
+ return compare(this, new BigNumber(y, b)) > 0;
1355
+ };
1356
+ P.isGreaterThanOrEqualTo = P.gte = function(y, b) {
1357
+ return (b = compare(this, new BigNumber(y, b))) === 1 || b === 0;
1358
+ };
1359
+ P.isInteger = function() {
1360
+ return !!this.c && bitFloor(this.e / LOG_BASE) > this.c.length - 2;
1361
+ };
1362
+ P.isLessThan = P.lt = function(y, b) {
1363
+ return compare(this, new BigNumber(y, b)) < 0;
1364
+ };
1365
+ P.isLessThanOrEqualTo = P.lte = function(y, b) {
1366
+ return (b = compare(this, new BigNumber(y, b))) === -1 || b === 0;
1367
+ };
1368
+ P.isNaN = function() {
1369
+ return !this.s;
1370
+ };
1371
+ P.isNegative = function() {
1372
+ return this.s < 0;
1373
+ };
1374
+ P.isPositive = function() {
1375
+ return this.s > 0;
1376
+ };
1377
+ P.isZero = function() {
1378
+ return !!this.c && this.c[0] == 0;
1379
+ };
1380
+ P.minus = function(y, b) {
1381
+ var i, j, t, xLTy, x = this, a = x.s;
1382
+ y = new BigNumber(y, b);
1383
+ b = y.s;
1384
+ if (!a || !b) return new BigNumber(NaN);
1385
+ if (a != b) {
1386
+ y.s = -b;
1387
+ return x.plus(y);
1388
+ }
1389
+ var xe = x.e / LOG_BASE, ye = y.e / LOG_BASE, xc = x.c, yc = y.c;
1390
+ if (!xe || !ye) {
1391
+ if (!xc || !yc) return xc ? (y.s = -b, y) : new BigNumber(yc ? x : NaN);
1392
+ if (!xc[0] || !yc[0]) return yc[0] ? (y.s = -b, y) : new BigNumber(xc[0] ? x : ROUNDING_MODE == 3 ? -0 : 0);
1393
+ }
1394
+ xe = bitFloor(xe);
1395
+ ye = bitFloor(ye);
1396
+ xc = xc.slice();
1397
+ if (a = xe - ye) {
1398
+ if (xLTy = a < 0) {
1399
+ a = -a;
1400
+ t = xc;
1401
+ } else {
1402
+ ye = xe;
1403
+ t = yc;
1404
+ }
1405
+ t.reverse();
1406
+ for (b = a; b--; t.push(0));
1407
+ t.reverse();
1408
+ } else {
1409
+ j = (xLTy = (a = xc.length) < (b = yc.length)) ? a : b;
1410
+ for (a = b = 0; b < j; b++) if (xc[b] != yc[b]) {
1411
+ xLTy = xc[b] < yc[b];
1412
+ break;
1413
+ }
1414
+ }
1415
+ if (xLTy) {
1416
+ t = xc;
1417
+ xc = yc;
1418
+ yc = t;
1419
+ y.s = -y.s;
1420
+ }
1421
+ b = (j = yc.length) - (i = xc.length);
1422
+ if (b > 0) for (; b--; xc[i++] = 0);
1423
+ b = BASE - 1;
1424
+ for (; j > a;) {
1425
+ if (xc[--j] < yc[j]) {
1426
+ for (i = j; i && !xc[--i]; xc[i] = b);
1427
+ --xc[i];
1428
+ xc[j] += BASE;
1429
+ }
1430
+ xc[j] -= yc[j];
1431
+ }
1432
+ for (; xc[0] == 0; xc.splice(0, 1), --ye);
1433
+ if (!xc[0]) {
1434
+ y.s = ROUNDING_MODE == 3 ? -1 : 1;
1435
+ y.c = [y.e = 0];
1436
+ return y;
1437
+ }
1438
+ return normalise(y, xc, ye);
1439
+ };
1440
+ P.modulo = P.mod = function(y, b) {
1441
+ var q, s, x = this;
1442
+ y = new BigNumber(y, b);
1443
+ if (!x.c || !y.s || y.c && !y.c[0]) return new BigNumber(NaN);
1444
+ else if (!y.c || x.c && !x.c[0]) return new BigNumber(x);
1445
+ if (MODULO_MODE == 9) {
1446
+ s = y.s;
1447
+ y.s = 1;
1448
+ q = div(x, y, 0, 3);
1449
+ y.s = s;
1450
+ q.s *= s;
1451
+ } else q = div(x, y, 0, MODULO_MODE);
1452
+ y = x.minus(q.times(y));
1453
+ if (!y.c[0] && MODULO_MODE == 1) y.s = x.s;
1454
+ return y;
1455
+ };
1456
+ P.multipliedBy = P.times = function(y, b) {
1457
+ var c, e, i, j, k, m, xcL, xlo, xhi, ycL, ylo, yhi, zc, base, sqrtBase, x = this, xc = x.c, yc = (y = new BigNumber(y, b)).c;
1458
+ if (!xc || !yc || !xc[0] || !yc[0]) {
1459
+ if (!x.s || !y.s || xc && !xc[0] && !yc || yc && !yc[0] && !xc) y.c = y.e = y.s = null;
1460
+ else {
1461
+ y.s *= x.s;
1462
+ if (!xc || !yc) y.c = y.e = null;
1463
+ else {
1464
+ y.c = [0];
1465
+ y.e = 0;
1466
+ }
1467
+ }
1468
+ return y;
1469
+ }
1470
+ e = bitFloor(x.e / LOG_BASE) + bitFloor(y.e / LOG_BASE);
1471
+ y.s *= x.s;
1472
+ xcL = xc.length;
1473
+ ycL = yc.length;
1474
+ if (xcL < ycL) {
1475
+ zc = xc;
1476
+ xc = yc;
1477
+ yc = zc;
1478
+ i = xcL;
1479
+ xcL = ycL;
1480
+ ycL = i;
1481
+ }
1482
+ for (i = xcL + ycL, zc = []; i--; zc.push(0));
1483
+ base = BASE;
1484
+ sqrtBase = SQRT_BASE;
1485
+ for (i = ycL; --i >= 0;) {
1486
+ c = 0;
1487
+ ylo = yc[i] % sqrtBase;
1488
+ yhi = yc[i] / sqrtBase | 0;
1489
+ for (k = xcL, j = i + k; j > i;) {
1490
+ xlo = xc[--k] % sqrtBase;
1491
+ xhi = xc[k] / sqrtBase | 0;
1492
+ m = yhi * xlo + xhi * ylo;
1493
+ xlo = ylo * xlo + m % sqrtBase * sqrtBase + zc[j] + c;
1494
+ c = (xlo / base | 0) + (m / sqrtBase | 0) + yhi * xhi;
1495
+ zc[j--] = xlo % base;
1496
+ }
1497
+ zc[j] = c;
1498
+ }
1499
+ if (c) ++e;
1500
+ else zc.splice(0, 1);
1501
+ return normalise(y, zc, e);
1502
+ };
1503
+ P.negated = function() {
1504
+ var x = new BigNumber(this);
1505
+ x.s = -x.s || null;
1506
+ return x;
1507
+ };
1508
+ P.plus = function(y, b) {
1509
+ var t, x = this, a = x.s;
1510
+ y = new BigNumber(y, b);
1511
+ b = y.s;
1512
+ if (!a || !b) return new BigNumber(NaN);
1513
+ if (a != b) {
1514
+ y.s = -b;
1515
+ return x.minus(y);
1516
+ }
1517
+ var xe = x.e / LOG_BASE, ye = y.e / LOG_BASE, xc = x.c, yc = y.c;
1518
+ if (!xe || !ye) {
1519
+ if (!xc || !yc) return new BigNumber(a / 0);
1520
+ if (!xc[0] || !yc[0]) return yc[0] ? y : new BigNumber(xc[0] ? x : a * 0);
1521
+ }
1522
+ xe = bitFloor(xe);
1523
+ ye = bitFloor(ye);
1524
+ xc = xc.slice();
1525
+ if (a = xe - ye) {
1526
+ if (a > 0) {
1527
+ ye = xe;
1528
+ t = yc;
1529
+ } else {
1530
+ a = -a;
1531
+ t = xc;
1532
+ }
1533
+ t.reverse();
1534
+ for (; a--; t.push(0));
1535
+ t.reverse();
1536
+ }
1537
+ a = xc.length;
1538
+ b = yc.length;
1539
+ if (a - b < 0) {
1540
+ t = yc;
1541
+ yc = xc;
1542
+ xc = t;
1543
+ b = a;
1544
+ }
1545
+ for (a = 0; b;) {
1546
+ a = (xc[--b] = xc[b] + yc[b] + a) / BASE | 0;
1547
+ xc[b] = BASE === xc[b] ? 0 : xc[b] % BASE;
1548
+ }
1549
+ if (a) {
1550
+ xc = [a].concat(xc);
1551
+ ++ye;
1552
+ }
1553
+ return normalise(y, xc, ye);
1554
+ };
1555
+ P.precision = P.sd = function(sd, rm) {
1556
+ var c, n, v, x = this;
1557
+ if (sd != null && sd !== !!sd) return round(new BigNumber(x), intCheck(sd, 1, MAX), rm == null ? ROUNDING_MODE : intCheck(rm, 0, 8));
1558
+ if (!(c = x.c)) return null;
1559
+ v = c.length - 1;
1560
+ n = v * LOG_BASE + 1;
1561
+ if (v = c[v]) {
1562
+ for (; v % 10 == 0; v /= 10, n--);
1563
+ for (v = c[0]; v >= 10; v /= 10, n++);
1564
+ }
1565
+ if (sd && x.e + 1 > n) n = x.e + 1;
1566
+ return n;
1567
+ };
1568
+ P.shiftedBy = function(k) {
1569
+ return this.times("1e" + intCheck(k, -MAX_SAFE_INTEGER, MAX_SAFE_INTEGER));
1570
+ };
1571
+ P.squareRoot = P.sqrt = function() {
1572
+ var m, n, r, rep, t, x = this, c = x.c, s = x.s, e = x.e, dp = DECIMAL_PLACES + 4, half = new BigNumber("0.5");
1573
+ if (s !== 1 || !c || !c[0]) return new BigNumber(!s || s < 0 && (!c || c[0]) ? NaN : c ? x : Infinity);
1574
+ s = Math.sqrt(+valueOf(x));
1575
+ if (s == 0 || s == Infinity) {
1576
+ n = coeffToString(c);
1577
+ if ((n.length + e) % 2 == 0) n += "0";
1578
+ s = Math.sqrt(+n);
1579
+ e = bitFloor((e + 1) / 2) - (e < 0 || e % 2);
1580
+ if (s == Infinity) n = "5e" + e;
1581
+ else {
1582
+ n = s.toExponential();
1583
+ n = n.slice(0, n.indexOf("e") + 1) + e;
1584
+ }
1585
+ r = new BigNumber(n);
1586
+ } else r = new BigNumber(s + "");
1587
+ if (r.c[0]) {
1588
+ e = r.e;
1589
+ s = e + dp;
1590
+ if (s < 3) s = 0;
1591
+ for (;;) {
1592
+ t = r;
1593
+ r = half.times(t.plus(div(x, t, dp, 1)));
1594
+ if (coeffToString(t.c).slice(0, s) === (n = coeffToString(r.c)).slice(0, s)) {
1595
+ if (r.e < e) --s;
1596
+ n = n.slice(s - 3, s + 1);
1597
+ if (n == "9999" || !rep && n == "4999") {
1598
+ if (!rep) {
1599
+ round(t, t.e + DECIMAL_PLACES + 2, 0);
1600
+ if (t.times(t).eq(x)) {
1601
+ r = t;
1602
+ break;
1603
+ }
1604
+ }
1605
+ dp += 4;
1606
+ s += 4;
1607
+ rep = 1;
1608
+ } else {
1609
+ if (!+n || !+n.slice(1) && n.charAt(0) == "5") {
1610
+ round(r, r.e + DECIMAL_PLACES + 2, 1);
1611
+ m = !r.times(r).eq(x);
1612
+ }
1613
+ break;
1614
+ }
1615
+ }
1616
+ }
1617
+ }
1618
+ return round(r, r.e + DECIMAL_PLACES + 1, ROUNDING_MODE, m);
1619
+ };
1620
+ if (typeof BigInt == "function") P.toBigInt = function(rm) {
1621
+ var x = this;
1622
+ if (!x.c) return null;
1623
+ return BigInt(format(x, x.e + 1, rm));
1624
+ };
1625
+ P.toExponential = function(dp, rm) {
1626
+ return format(this, dp == null ? dp : intCheck(dp, 0, MAX) + 1, rm, 1);
1627
+ };
1628
+ P.toFixed = function(dp, rm) {
1629
+ return format(this, dp == null ? dp : intCheck(dp, -MAX, MAX) + this.e + 1, rm);
1630
+ };
1631
+ P.toFormat = function(dp, rm, options) {
1632
+ var isNeg, min, max, str, x = this;
1633
+ if (options == null) {
1634
+ options = FORMAT;
1635
+ if (dp != null) {
1636
+ if (rm != null) {
1637
+ if (typeof rm == "object") {
1638
+ options = resolveFormatOptions(rm);
1639
+ rm = null;
1640
+ }
1641
+ } else if (typeof dp == "object" && !isArray(dp)) {
1642
+ options = resolveFormatOptions(dp);
1643
+ dp = rm = null;
1644
+ }
1645
+ }
1646
+ } else if (typeof options != "object") throw Error(bignumberError + "Argument not an object: " + options);
1647
+ else options = resolveFormatOptions(options);
1648
+ if (dp != null) if (isArray(dp) && dp.length <= 2) {
1649
+ min = dp[0];
1650
+ max = dp[1];
1651
+ dp = x.dp();
1652
+ if (max != null && dp > intCheck(max, 0, MAX)) dp = max;
1653
+ if (min != null && intCheck(min, 0, MAX) !== 0) {
1654
+ if (max != null && min > max) throw Error(bignumberError + "Minimum must not exceed maximum");
1655
+ if (dp < min) dp = min;
1656
+ }
1657
+ } else intCheck(dp, -MAX, MAX);
1658
+ str = x.toFixed(dp, rm);
1659
+ isNeg = str.charCodeAt(0) === 45;
1660
+ if (isNeg) str = str.slice(1);
1661
+ if (x.c) {
1662
+ var i, arr = str.split("."), g1 = +options.groupSize, g2 = +options.secondaryGroupSize, groupSeparator = options.groupSeparator || "", intPart = arr[0], fractionPart = arr[1], len = intPart.length;
1663
+ if (g2) {
1664
+ i = g1;
1665
+ g1 = g2;
1666
+ g2 = i;
1667
+ len -= i;
1668
+ }
1669
+ if (g1 > 0 && len > 0) {
1670
+ i = len % g1 || g1;
1671
+ str = intPart.substr(0, i);
1672
+ for (; i < len; i += g1) str += groupSeparator + intPart.substr(i, g1);
1673
+ if (g2 > 0) str += groupSeparator + intPart.slice(i);
1674
+ } else str = intPart;
1675
+ if (fractionPart) {
1676
+ i = +options.fractionGroupSize;
1677
+ if (i) fractionPart = fractionPart.replace(new RegExp("\\d{" + i + "}\\B", "g"), "$&" + (options.fractionGroupSeparator || ""));
1678
+ str += (options.decimalSeparator || "") + fractionPart;
1679
+ }
1680
+ }
1681
+ return (options.prefix || "") + (isNeg ? options.negativeSign || "" : x.s > 0 ? options.positiveSign || "" : "") + str + (options.suffix || "");
1682
+ };
1683
+ P.toFraction = function(md) {
1684
+ var d, d0, d1, d2, e, exp, n, n0, n1, q, r, s, xd, xn, x = this, xc = x.c;
1685
+ if (md != null) {
1686
+ n = new BigNumber(md);
1687
+ if (!n.isInteger() && (n.c || n.s !== 1) || n.lt(ONE)) throw Error(bignumberError + "Argument " + (n.isInteger() ? "out of range: " : "not an integer: ") + valueOf(n));
1688
+ }
1689
+ if (!xc) return [new BigNumber(x.s || 0), new BigNumber(0)];
1690
+ d = new BigNumber(ONE);
1691
+ n1 = d0 = new BigNumber(ONE);
1692
+ d1 = n0 = new BigNumber(ONE);
1693
+ s = coeffToString(xc);
1694
+ e = d.e = s.length - x.e - 1;
1695
+ d.c[0] = POWS_TEN[(exp = e % LOG_BASE) < 0 ? LOG_BASE + exp : exp];
1696
+ md = !md || n.comparedTo(d) > 0 ? e > 0 ? d : n1 : n;
1697
+ exp = MAX_EXP;
1698
+ MAX_EXP = Infinity;
1699
+ n = new BigNumber(s);
1700
+ xn = n;
1701
+ xd = d;
1702
+ n0.c[0] = 0;
1703
+ for (;;) {
1704
+ q = div(n, d, 0, 1);
1705
+ d2 = d0.plus(q.times(d1));
1706
+ if (d2.comparedTo(md) == 1) break;
1707
+ d0 = d1;
1708
+ d1 = d2;
1709
+ n1 = n0.plus(q.times(d2 = n1));
1710
+ n0 = d2;
1711
+ d = n.minus(q.times(d2 = d));
1712
+ n = d2;
1713
+ }
1714
+ d2 = div(md.minus(d0), d1, 0, 1);
1715
+ n0 = n0.plus(d2.times(n1));
1716
+ d0 = d0.plus(d2.times(d1));
1717
+ r = n1.times(xd).minus(xn.times(d1)).abs().times(d0).comparedTo(n0.times(xd).minus(xn.times(d0)).abs().times(d1)) < 1 ? [n1, d1] : [n0, d0];
1718
+ r[0].s = x.s;
1719
+ MAX_EXP = exp;
1720
+ return r;
1721
+ };
1722
+ P.toNumber = function() {
1723
+ return +valueOf(this);
1724
+ };
1725
+ P.toObject = function() {
1726
+ var x = this;
1727
+ return {
1728
+ c: x.c ? x.c.slice() : null,
1729
+ e: x.e,
1730
+ s: x.s
1731
+ };
1732
+ };
1733
+ P.toPrecision = function(sd, rm) {
1734
+ return format(this, sd == null ? sd : intCheck(sd, 1, MAX), rm, 2);
1735
+ };
1736
+ P.toString = function(b) {
1737
+ var str, n = this, s = n.s, e = n.e;
1738
+ if (e === null) if (s) {
1739
+ str = "Infinity";
1740
+ if (s < 0) str = "-" + str;
1741
+ } else str = "NaN";
1742
+ else {
1743
+ if (b == null) str = e <= TO_EXP_NEG || e >= TO_EXP_POS ? toExponential(coeffToString(n.c), e) : toFixedPoint(coeffToString(n.c), e, "0");
1744
+ else {
1745
+ intCheck(b, 2, ALPHABET.length, "Base");
1746
+ str = convertBase(toFixedPoint(coeffToString(n.c), e, "0"), 10, b, s, true);
1747
+ }
1748
+ if (s < 0 && n.c[0]) str = "-" + str;
1749
+ }
1750
+ return str;
1751
+ };
1752
+ P.valueOf = P.toJSON = function() {
1753
+ return valueOf(this);
1754
+ };
1755
+ P._isBigNumber = true;
1756
+ if (configObject != null) BigNumber.set(configObject);
1757
+ return BigNumber;
1758
+ }
1759
+ function bitFloor(n) {
1760
+ var i = n | 0;
1761
+ return n > 0 || n === i ? i : i - 1;
1762
+ }
1763
+ function coeffToString(a) {
1764
+ var s, z, i = 1, j = a.length, r = a[0] + "";
1765
+ for (; i < j;) {
1766
+ s = a[i++] + "";
1767
+ z = LOG_BASE - s.length;
1768
+ for (; z--; s = "0" + s);
1769
+ r += s;
1770
+ }
1771
+ for (j = r.length; r.charCodeAt(--j) === 48;);
1772
+ return r.slice(0, j + 1 || 1);
1773
+ }
1774
+ function compare(x, y) {
1775
+ var a, b, xc = x.c, yc = y.c, i = x.s, j = y.s, k = x.e, l = y.e;
1776
+ if (!i || !j) return null;
1777
+ a = xc && !xc[0];
1778
+ b = yc && !yc[0];
1779
+ if (a || b) return a ? b ? 0 : -j : i;
1780
+ if (i != j) return i;
1781
+ a = i < 0;
1782
+ b = k == l;
1783
+ if (!xc || !yc) return b ? 0 : !xc ^ a ? 1 : -1;
1784
+ if (!b) return k > l ^ a ? 1 : -1;
1785
+ j = (k = xc.length) < (l = yc.length) ? k : l;
1786
+ for (i = 0; i < j; i++) if (xc[i] != yc[i]) return xc[i] > yc[i] ^ a ? 1 : -1;
1787
+ return k == l ? 0 : k > l ^ a ? 1 : -1;
1788
+ }
1789
+ function intCheck(n, min, max, name) {
1790
+ if (n < min || n > max || n !== mathfloor(n)) throw Error(bignumberError + (name || "Argument") + (typeof n == "number" ? n < min || n > max ? " out of range: " : " not an integer: " : " not a primitive number: ") + String(n));
1791
+ return n;
1792
+ }
1793
+ function isArray(obj) {
1794
+ return {}.toString.call(obj) == "[object Array]";
1795
+ }
1796
+ function isOdd(n) {
1797
+ var k = n.c.length - 1;
1798
+ return bitFloor(n.e / LOG_BASE) == k && n.c[k] % 2 != 0;
1799
+ }
1800
+ function toExponential(str, e) {
1801
+ return (str.length > 1 ? str.charAt(0) + "." + str.slice(1) : str) + (e < 0 ? "e" : "e+") + e;
1802
+ }
1803
+ function toFixedPoint(str, e, z) {
1804
+ var len, zs;
1805
+ if (e < 0) {
1806
+ for (zs = z + "."; ++e; zs += z);
1807
+ str = zs + str;
1808
+ } else {
1809
+ len = str.length;
1810
+ if (++e > len) {
1811
+ for (zs = z, e -= len; --e; zs += z);
1812
+ str += zs;
1813
+ } else if (e < len) str = str.slice(0, e) + "." + str.slice(e);
1814
+ }
1815
+ return str;
1816
+ }
1817
+ //#endregion
1818
+ //#region packages/contracts/src/api-keys.ts
1819
+ /**
1820
+ * API anahtarı sözleşmeleri — permission-mask modeli (spec §5.5).
1821
+ *
1822
+ * KRİTİK KURAL: `scope`, oluşturma anındaki tavandır — gerçek etkili kapsam
1823
+ * istek anında sahibinin GÜNCEL tenant rolüyle kesişiminden hesaplanır (bkz.
1824
+ * apps/api/src/lib/api-key-auth.ts). Bu şemalar yalnız gövde şeklini tanımlar,
1825
+ * yetki hesabını YAPMAZ.
1826
+ */
1827
+ const apiKeyScopeSchema = z.enum([
1828
+ "read",
1829
+ "write",
1830
+ "admin"
1831
+ ], "geçerli bir kapsam olmalı (read, write, admin)");
1832
+ z.object({
1833
+ name: z.string().trim().min(2, "ad en az 2 karakter olmalı").max(80, "ad en fazla 80 karakter olabilir"),
1834
+ scope: apiKeyScopeSchema
1835
+ });
1836
+ z.object({
1837
+ id: z.uuid(),
1838
+ name: z.string(),
1839
+ scope: apiKeyScopeSchema,
1840
+ prefix: z.string(),
1841
+ token: z.string()
1842
+ });
1843
+ const apiKeyResponseSchema = z.object({
1844
+ id: z.uuid(),
1845
+ name: z.string(),
1846
+ scope: apiKeyScopeSchema,
1847
+ prefix: z.string(),
1848
+ lastUsedAt: z.iso.datetime().nullable(),
1849
+ revokedAt: z.iso.datetime().nullable(),
1850
+ createdAt: z.iso.datetime()
1851
+ });
1852
+ z.object({ keys: z.array(apiKeyResponseSchema) });
1853
+ //#endregion
1854
+ //#region packages/contracts/src/common.ts
1855
+ /**
1856
+ * Ortak zod şemaları ve doğrulayıcılar — birden fazla domain tarafından
1857
+ * paylaşılan küçük yapı taşları. Domain'e özgü şemalar kendi dosyalarında
1858
+ * durur (ör. plans.ts, fields.ts).
1859
+ *
1860
+ * Hata mesajları Türkçe: bu şemalar doğrudan API yanıtına dönüşüyor.
1861
+ */
1862
+ /** Tenant üyeliği rolü — sıralı: viewer < member < admin < owner */
1863
+ const memberRoleSchema = z.enum([
1864
+ "owner",
1865
+ "admin",
1866
+ "member",
1867
+ "viewer"
1868
+ ], "geçerli bir rol olmalı (owner, admin, member, viewer)");
1869
+ /**
1870
+ * Tenant slug'ı. NOT: "ayrılmış adlar" (ör. `admin`, `api`, `www`) şu anda
1871
+ * route katmanında FİLTRELENMİYOR — slug bir subdomain değil path-prefix
1872
+ * olarak kullanıldığından (bkz. spec) bu bir güvenlik açığı oluşturmuyor;
1873
+ * yalnız kullanıcı deneyimi/marka çakışması riski Faz 4A kapsamı dışında
1874
+ * bırakıldı. İleride ayrılmış ad listesi eklenirse burası genişletilir.
1875
+ */
1876
+ const tenantSlugSchema = z.string().min(2, "en az 2 karakter olmalı").max(64, "en fazla 64 karakter olabilir").regex(/^[a-z][a-z0-9-]*$/, "küçük harf, rakam ve tire kullanılabilir");
1877
+ /**
1878
+ * Katı hex renk — tenant branding'inde doğrudan CSS'e yazıldığı için
1879
+ * serbest string ASLA kabul edilmez (CSS enjeksiyonu sınırı).
1880
+ */
1881
+ const hexColorSchema = z.string().regex(/^#([0-9a-fA-F]{6}|[0-9a-fA-F]{3})$/, "geçerli bir hex renk olmalı (örn. #14b8a6)");
1882
+ z.string().regex(/^\+[1-9]\d{7,14}$/, "uluslararası biçimde olmalı (örn. +905321234567)");
1883
+ /** Muhasebe/bildirim e-postası */
1884
+ const emailSchema = z.email("geçerli bir e-posta adresi olmalı").max(160, "en fazla 160 karakter olabilir");
1885
+ /**
1886
+ * TC Kimlik Numarası checksum doğrulaması (11 hane, sıfırla başlamaz).
1887
+ * 10. hane: (tek konumların toplamı×7 - çift konumların toplamı) mod 10
1888
+ * 11. hane: ilk 10 hanenin toplamı mod 10
1889
+ */
1890
+ function validateTckn(value) {
1891
+ if (!/^[1-9]\d{10}$/.test(value)) return false;
1892
+ const d = [...value].map(Number);
1893
+ let oddSum = 0;
1894
+ let evenSum = 0;
1895
+ for (let i = 0; i < 9; i += 1) {
1896
+ const digit = d[i];
1897
+ if (digit === void 0) return false;
1898
+ if (i % 2 === 0) oddSum += digit;
1899
+ else evenSum += digit;
1900
+ }
1901
+ const tenth = d[9];
1902
+ const eleventh = d[10];
1903
+ if (tenth === void 0 || eleventh === void 0) return false;
1904
+ if (((oddSum * 7 - evenSum) % 10 + 10) % 10 !== tenth) return false;
1905
+ return d.slice(0, 10).reduce((a, b) => a + b, 0) % 10 === eleventh;
1906
+ }
1907
+ /**
1908
+ * Vergi Kimlik Numarası checksum doğrulaması (10 hane).
1909
+ * Maliye Bakanlığı algoritması.
1910
+ */
1911
+ function validateVkn(value) {
1912
+ if (!/^\d{10}$/.test(value)) return false;
1913
+ const d = [...value].map(Number);
1914
+ let sum = 0;
1915
+ for (let i = 0; i < 9; i += 1) {
1916
+ const digit = d[i];
1917
+ if (digit === void 0) return false;
1918
+ const tmp = (digit + 10 - (i + 1)) % 10;
1919
+ if (tmp === 9) sum += tmp;
1920
+ else sum += tmp * 2 ** (10 - (i + 1)) % 9;
1921
+ }
1922
+ const last = d[9];
1923
+ if (last === void 0) return false;
1924
+ return (10 - sum % 10) % 10 === last;
1925
+ }
1926
+ z.object({
1927
+ /** Zitadel'den PKCE akışıyla alınan id_token */
1928
+ idToken: z.string().min(1, "idToken gerekli").max(8192, "idToken çok uzun"),
1929
+ /** id_token profil claim'i taşımıyorsa userinfo tamamlaması için (opsiyonel) */
1930
+ accessToken: z.string().min(1, "accessToken boş olamaz").max(8192, "accessToken çok uzun").optional()
1931
+ });
1932
+ z.object({
1933
+ token: z.string(),
1934
+ user: z.object({
1935
+ id: z.uuid(),
1936
+ email: z.string(),
1937
+ name: z.string()
1938
+ })
1939
+ });
1940
+ z.object({
1941
+ user: z.object({
1942
+ id: z.uuid(),
1943
+ email: z.string(),
1944
+ name: z.string()
1945
+ }),
1946
+ tenants: z.array(z.object({
1947
+ id: z.uuid(),
1948
+ name: z.string(),
1949
+ slug: z.string(),
1950
+ role: memberRoleSchema
1951
+ }))
1952
+ });
1953
+ z.object({
1954
+ name: z.string().trim().min(2, "ad en az 2 karakter olmalı").max(120, "ad en fazla 120 karakter olabilir"),
1955
+ slug: tenantSlugSchema
1956
+ });
1957
+ z.object({
1958
+ id: z.uuid(),
1959
+ name: z.string(),
1960
+ slug: z.string(),
1961
+ role: memberRoleSchema
1962
+ });
1963
+ /** `GET /v1/tenants/:id/members` yanıtındaki tek üye satırı */
1964
+ const tenantMemberSchema = z.object({
1965
+ userId: z.uuid(),
1966
+ email: z.string(),
1967
+ name: z.string(),
1968
+ role: memberRoleSchema
1969
+ });
1970
+ z.object({ members: z.array(tenantMemberSchema) });
1971
+ z.object({ role: memberRoleSchema });
1972
+ z.object({
1973
+ userId: z.uuid(),
1974
+ role: memberRoleSchema
1975
+ });
1976
+ const COUNTRY_CODE_SET = /* @__PURE__ */ new Set([
1977
+ "AD",
1978
+ "AE",
1979
+ "AF",
1980
+ "AG",
1981
+ "AI",
1982
+ "AL",
1983
+ "AM",
1984
+ "AO",
1985
+ "AQ",
1986
+ "AR",
1987
+ "AS",
1988
+ "AT",
1989
+ "AU",
1990
+ "AW",
1991
+ "AX",
1992
+ "AZ",
1993
+ "BA",
1994
+ "BB",
1995
+ "BD",
1996
+ "BE",
1997
+ "BF",
1998
+ "BG",
1999
+ "BH",
2000
+ "BI",
2001
+ "BJ",
2002
+ "BL",
2003
+ "BM",
2004
+ "BN",
2005
+ "BO",
2006
+ "BQ",
2007
+ "BR",
2008
+ "BS",
2009
+ "BT",
2010
+ "BV",
2011
+ "BW",
2012
+ "BY",
2013
+ "BZ",
2014
+ "CA",
2015
+ "CC",
2016
+ "CD",
2017
+ "CF",
2018
+ "CG",
2019
+ "CH",
2020
+ "CI",
2021
+ "CK",
2022
+ "CL",
2023
+ "CM",
2024
+ "CN",
2025
+ "CO",
2026
+ "CR",
2027
+ "CU",
2028
+ "CV",
2029
+ "CW",
2030
+ "CX",
2031
+ "CY",
2032
+ "CZ",
2033
+ "DE",
2034
+ "DJ",
2035
+ "DK",
2036
+ "DM",
2037
+ "DO",
2038
+ "DZ",
2039
+ "EC",
2040
+ "EE",
2041
+ "EG",
2042
+ "EH",
2043
+ "ER",
2044
+ "ES",
2045
+ "ET",
2046
+ "FI",
2047
+ "FJ",
2048
+ "FK",
2049
+ "FM",
2050
+ "FO",
2051
+ "FR",
2052
+ "GA",
2053
+ "GB",
2054
+ "GD",
2055
+ "GE",
2056
+ "GF",
2057
+ "GG",
2058
+ "GH",
2059
+ "GI",
2060
+ "GL",
2061
+ "GM",
2062
+ "GN",
2063
+ "GP",
2064
+ "GQ",
2065
+ "GR",
2066
+ "GS",
2067
+ "GT",
2068
+ "GU",
2069
+ "GW",
2070
+ "GY",
2071
+ "HK",
2072
+ "HM",
2073
+ "HN",
2074
+ "HR",
2075
+ "HT",
2076
+ "HU",
2077
+ "ID",
2078
+ "IE",
2079
+ "IL",
2080
+ "IM",
2081
+ "IN",
2082
+ "IO",
2083
+ "IQ",
2084
+ "IR",
2085
+ "IS",
2086
+ "IT",
2087
+ "JE",
2088
+ "JM",
2089
+ "JO",
2090
+ "JP",
2091
+ "KE",
2092
+ "KG",
2093
+ "KH",
2094
+ "KI",
2095
+ "KM",
2096
+ "KN",
2097
+ "KP",
2098
+ "KR",
2099
+ "KW",
2100
+ "KY",
2101
+ "KZ",
2102
+ "LA",
2103
+ "LB",
2104
+ "LC",
2105
+ "LI",
2106
+ "LK",
2107
+ "LR",
2108
+ "LS",
2109
+ "LT",
2110
+ "LU",
2111
+ "LV",
2112
+ "LY",
2113
+ "MA",
2114
+ "MC",
2115
+ "MD",
2116
+ "ME",
2117
+ "MF",
2118
+ "MG",
2119
+ "MH",
2120
+ "MK",
2121
+ "ML",
2122
+ "MM",
2123
+ "MN",
2124
+ "MO",
2125
+ "MP",
2126
+ "MQ",
2127
+ "MR",
2128
+ "MS",
2129
+ "MT",
2130
+ "MU",
2131
+ "MV",
2132
+ "MW",
2133
+ "MX",
2134
+ "MY",
2135
+ "MZ",
2136
+ "NA",
2137
+ "NC",
2138
+ "NE",
2139
+ "NF",
2140
+ "NG",
2141
+ "NI",
2142
+ "NL",
2143
+ "NO",
2144
+ "NP",
2145
+ "NR",
2146
+ "NU",
2147
+ "NZ",
2148
+ "OM",
2149
+ "PA",
2150
+ "PE",
2151
+ "PF",
2152
+ "PG",
2153
+ "PH",
2154
+ "PK",
2155
+ "PL",
2156
+ "PM",
2157
+ "PN",
2158
+ "PR",
2159
+ "PS",
2160
+ "PT",
2161
+ "PW",
2162
+ "PY",
2163
+ "QA",
2164
+ "RE",
2165
+ "RO",
2166
+ "RS",
2167
+ "RU",
2168
+ "RW",
2169
+ "SA",
2170
+ "SB",
2171
+ "SC",
2172
+ "SD",
2173
+ "SE",
2174
+ "SG",
2175
+ "SH",
2176
+ "SI",
2177
+ "SJ",
2178
+ "SK",
2179
+ "SL",
2180
+ "SM",
2181
+ "SN",
2182
+ "SO",
2183
+ "SR",
2184
+ "SS",
2185
+ "ST",
2186
+ "SV",
2187
+ "SX",
2188
+ "SY",
2189
+ "SZ",
2190
+ "TC",
2191
+ "TD",
2192
+ "TF",
2193
+ "TG",
2194
+ "TH",
2195
+ "TJ",
2196
+ "TK",
2197
+ "TL",
2198
+ "TM",
2199
+ "TN",
2200
+ "TO",
2201
+ "TR",
2202
+ "TT",
2203
+ "TV",
2204
+ "TW",
2205
+ "TZ",
2206
+ "UA",
2207
+ "UG",
2208
+ "UM",
2209
+ "US",
2210
+ "UY",
2211
+ "UZ",
2212
+ "VA",
2213
+ "VC",
2214
+ "VE",
2215
+ "VG",
2216
+ "VI",
2217
+ "VN",
2218
+ "VU",
2219
+ "WF",
2220
+ "WS",
2221
+ "YE",
2222
+ "YT",
2223
+ "ZA",
2224
+ "ZM",
2225
+ "ZW"
2226
+ ]);
2227
+ //#endregion
2228
+ //#region packages/contracts/src/plans.ts
2229
+ const PLAN_CODES = [
2230
+ "free",
2231
+ "pro",
2232
+ "team",
2233
+ "enterprise"
2234
+ ];
2235
+ /** Platform admin yanıtlarında (bkz. contracts/platform.ts) `tenants.planCode`'un doğrulaması */
2236
+ const planCodeSchema = z.enum(PLAN_CODES, "geçerli bir plan olmalı");
2237
+ const BILLING_PERIODS = ["monthly", "yearly"];
2238
+ //#endregion
2239
+ //#region packages/contracts/src/billing.ts
2240
+ /**
2241
+ * Faturalama sözleşmeleri — fatura profili (ülke/bireysel/kurumsal/yurtdışı),
2242
+ * checkout isteği, abonelik/fatura yanıt şekilleri (spec §8.5–§8.8).
2243
+ *
2244
+ * KRİTİK KURAL: `accountingEmail` HER ÜÇ varyantta da ZORUNLU ve ülkeden
2245
+ * BAĞIMSIZDIR (brief madde 8) — tüm muhasebe e-postaları BURAYA gider.
2246
+ */
2247
+ const countryCodeSchema = z.string().length(2, "ISO 3166-1 iki harfli ülke kodu olmalı").refine((value) => COUNTRY_CODE_SET.has(value), "geçerli bir ülke kodu olmalı");
2248
+ z.enum([
2249
+ "tr_individual",
2250
+ "tr_corporate",
2251
+ "international"
2252
+ ], "geçerli bir fatura profili türü olmalı");
2253
+ const trIndividualInputSchema = z.object({
2254
+ kind: z.literal("tr_individual"),
2255
+ country: z.literal("TR"),
2256
+ fullName: z.string().trim().min(1, "ad soyad gerekli").max(200, "en fazla 200 karakter olabilir"),
2257
+ tckn: z.string().refine(validateTckn, "geçerli bir TC Kimlik Numarası olmalı"),
2258
+ address: z.string().trim().min(1, "adres gerekli").max(500, "en fazla 500 karakter olabilir"),
2259
+ city: z.string().trim().min(1, "şehir gerekli").max(120, "en fazla 120 karakter olabilir"),
2260
+ district: z.string().trim().min(1, "ilçe gerekli").max(120, "en fazla 120 karakter olabilir"),
2261
+ accountingEmail: emailSchema
2262
+ });
2263
+ const trCorporateInputSchema = z.object({
2264
+ kind: z.literal("tr_corporate"),
2265
+ country: z.literal("TR"),
2266
+ legalName: z.string().trim().min(1, "unvan gerekli").max(250, "en fazla 250 karakter olabilir"),
2267
+ vkn: z.string().refine(validateVkn, "geçerli bir Vergi Kimlik Numarası olmalı"),
2268
+ taxOffice: z.string().trim().min(1, "vergi dairesi gerekli").max(120, "en fazla 120 karakter olabilir"),
2269
+ address: z.string().trim().min(1, "adres gerekli").max(500, "en fazla 500 karakter olabilir"),
2270
+ city: z.string().trim().min(1, "şehir gerekli").max(120, "en fazla 120 karakter olabilir"),
2271
+ district: z.string().trim().min(1, "ilçe gerekli").max(120, "en fazla 120 karakter olabilir"),
2272
+ accountingEmail: emailSchema
2273
+ });
2274
+ const internationalInputSchema = z.object({
2275
+ kind: z.literal("international"),
2276
+ country: countryCodeSchema.refine((value) => value !== "TR", "yurt dışı profilde ülke TR olamaz"),
2277
+ fullName: z.string().trim().min(1, "ad/unvan gerekli").max(250, "en fazla 250 karakter olabilir"),
2278
+ address: z.string().trim().min(1, "adres gerekli").max(500, "en fazla 500 karakter olabilir"),
2279
+ taxId: z.string().trim().max(60, "en fazla 60 karakter olabilir").optional(),
2280
+ accountingEmail: emailSchema
2281
+ });
2282
+ const billingProfileInputSchema = z.discriminatedUnion("kind", [
2283
+ trIndividualInputSchema,
2284
+ trCorporateInputSchema,
2285
+ internationalInputSchema
2286
+ ]);
2287
+ z.object({
2288
+ data: billingProfileInputSchema,
2289
+ maskedTaxId: z.string().nullable(),
2290
+ createdAt: z.iso.datetime(),
2291
+ updatedAt: z.iso.datetime()
2292
+ });
2293
+ const purchasablePlanCodeSchema = z.enum(PLAN_CODES, "geçerli bir plan olmalı").refine((value) => value === "pro" || value === "team", "yalnız pro veya team paketleri için satın alma başlatılabilir");
2294
+ z.object({
2295
+ planCode: purchasablePlanCodeSchema,
2296
+ billingPeriod: z.enum(BILLING_PERIODS, "geçerli bir dönem olmalı")
2297
+ });
2298
+ z.object({ purchaseUrl: z.url() });
2299
+ z.object({ code: z.literal("billing_profile_required") });
2300
+ const subscriptionStatusValueSchema = z.enum([
2301
+ "trialing",
2302
+ "active",
2303
+ "past_due",
2304
+ "cancelled",
2305
+ "refunded"
2306
+ ]);
2307
+ z.object({
2308
+ planCode: z.enum(PLAN_CODES),
2309
+ billingPeriod: z.enum(BILLING_PERIODS).nullable(),
2310
+ status: subscriptionStatusValueSchema.nullable(),
2311
+ currentPeriodEnd: z.iso.datetime().nullable(),
2312
+ cancelAtPeriodEnd: z.boolean()
2313
+ });
2314
+ const invoiceResponseSchema = z.object({
2315
+ id: z.uuid(),
2316
+ invoiceNumber: z.string(),
2317
+ kind: z.enum(["invoice", "refund"]),
2318
+ status: z.enum([
2319
+ "draft",
2320
+ "formalized",
2321
+ "cancelled"
2322
+ ]),
2323
+ amountGross: z.string(),
2324
+ amountNet: z.string(),
2325
+ amountVat: z.string(),
2326
+ currencyCode: z.string(),
2327
+ vatRate: z.string(),
2328
+ vatExemptionCode: z.string().nullable(),
2329
+ hasPdf: z.boolean(),
2330
+ issuedAt: z.iso.datetime()
2331
+ });
2332
+ z.object({
2333
+ items: z.array(invoiceResponseSchema),
2334
+ nextCursor: z.string().nullable(),
2335
+ hasMore: z.boolean()
2336
+ });
2337
+ z.enum(["invoice", "refund"]);
2338
+ //#endregion
2339
+ //#region packages/contracts/src/companies.ts
2340
+ /**
2341
+ * Şirket (company) sözleşmeleri (spec §6.2).
2342
+ *
2343
+ * KRİTİK KURAL: `annualRevenue` PARA alanıdır — string olarak taşınır, hiçbir
2344
+ * yerde native `number`'a çevrilip aritmetik yapılmaz (bkz. spec §6.1,
2345
+ * `bignumber.js` dışında ASLA hesaplanmaz).
2346
+ *
2347
+ * [Fix round 2 — doğruluk reviewer'ının bulduğu, Task 3'ün `deals.amount`
2348
+ * fix'iyle BİREBİR aynı sınıf hata]: DB kolonu `text`TEN `numeric(18,2)`'ye
2349
+ * taşındı (`FIELD_CATALOG.companies.annual_revenue` `sortable`/`filterable`
2350
+ * ilan ediyordu ama `text`te `ORDER BY`/`>`/`<` LEXICOGRAPHIC karşılaştırma
2351
+ * yapıyordu — bkz. `packages/db/src/schema/companies.ts` dosya başı yorumu,
2352
+ * `packages/db/drizzle/0035_companies_annual_revenue_numeric.sql`). Biçim
2353
+ * kısıtı `deals.amount` ile AYNI (`./crm.ts` `numericMoneySchema`, DRY).
2354
+ */
2355
+ const createCompanyRequestSchema = z.object({
2356
+ name: z.string().trim().min(1, "ad gerekli").max(200, "en fazla 200 karakter olabilir"),
2357
+ domain: z.string().trim().max(255, "en fazla 255 karakter olabilir").optional(),
2358
+ industry: z.string().trim().max(120, "en fazla 120 karakter olabilir").optional(),
2359
+ size: companySizeSchema.optional(),
2360
+ phone: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
2361
+ website: z.url("geçerli bir URL olmalı").max(500, "en fazla 500 karakter olabilir").optional(),
2362
+ address: z.string().trim().max(500, "en fazla 500 karakter olabilir").optional(),
2363
+ city: z.string().trim().max(120, "en fazla 120 karakter olabilir").optional(),
2364
+ country: z.string().trim().max(120, "en fazla 120 karakter olabilir").optional(),
2365
+ /** PARA — bkz. dosya başı KRİTİK KURAL, `numeric(18,2)` ile eşleşen biçim kısıtı */
2366
+ annualRevenue: numericMoneySchema.optional(),
2367
+ linkedinUrl: z.url("geçerli bir URL olmalı").max(500, "en fazla 500 karakter olabilir").optional(),
2368
+ ownerUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional()
2369
+ });
2370
+ const updateCompanyRequestSchema = createCompanyRequestSchema.partial();
2371
+ pagedResponseSchema(z.object({
2372
+ id: z.uuid(),
2373
+ name: z.string(),
2374
+ domain: z.string().nullable(),
2375
+ industry: z.string().nullable(),
2376
+ size: companySizeSchema.nullable(),
2377
+ phone: z.string().nullable(),
2378
+ website: z.string().nullable(),
2379
+ address: z.string().nullable(),
2380
+ city: z.string().nullable(),
2381
+ country: z.string().nullable(),
2382
+ annualRevenue: z.string().nullable(),
2383
+ linkedinUrl: z.string().nullable(),
2384
+ ownerUserId: z.uuid().nullable(),
2385
+ createdAt: z.iso.datetime(),
2386
+ updatedAt: z.iso.datetime()
2387
+ }));
2388
+ //#endregion
2389
+ //#region packages/contracts/src/contacts.ts
2390
+ /**
2391
+ * Kişi (contact) sözleşmeleri (spec §6.2). `companyId` nullable — bir kişi
2392
+ * şirketsiz de var olabilir (bireysel müşteri).
2393
+ */
2394
+ const createContactRequestSchema = z.object({
2395
+ firstName: z.string().trim().min(1, "ad gerekli").max(120, "en fazla 120 karakter olabilir"),
2396
+ lastName: z.string().trim().min(1, "soyad gerekli").max(120, "en fazla 120 karakter olabilir"),
2397
+ email: z.email("geçerli bir e-posta adresi olmalı").max(160).optional(),
2398
+ phone: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
2399
+ mobile: z.string().trim().max(40, "en fazla 40 karakter olabilir").optional(),
2400
+ jobTitle: z.string().trim().max(160, "en fazla 160 karakter olabilir").optional(),
2401
+ linkedinUrl: z.url("geçerli bir URL olmalı").max(500, "en fazla 500 karakter olabilir").optional(),
2402
+ avatarUrl: z.url("geçerli bir URL olmalı").max(500, "en fazla 500 karakter olabilir").optional(),
2403
+ city: z.string().trim().max(120, "en fazla 120 karakter olabilir").optional(),
2404
+ country: z.string().trim().max(120, "en fazla 120 karakter olabilir").optional(),
2405
+ ownerUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional(),
2406
+ source: contactSourceSchema.optional(),
2407
+ status: contactStatusSchema.default("lead"),
2408
+ companyId: z.uuid("geçerli bir şirket kimliği olmalı").optional()
2409
+ });
2410
+ /**
2411
+ * [Fix round 1 — Faz 10 Task 3] `status` AÇIKÇA yeniden tanımlanır; düz
2412
+ * `.partial()` YETMEZ.
2413
+ *
2414
+ * ÖLÇÜLEN KUSUR: zod 4'te `.partial()` bir alanı `.optional()` yapar ama
2415
+ * `.default()`'unu KALDIRMAZ ⇒ `createContactRequestSchema.partial()`'in
2416
+ * `status`'ü hâlâ `.default('lead')` taşıyordu ve
2417
+ * `updateContactRequestSchema.parse({ city: 'x' })` → `{ city:'x',
2418
+ * status:'lead' }` üretiyordu (canlı ölçüldü). `PATCH /v1/tenants/:id/
2419
+ * contacts/:contactId` gövdeyi `validateJson(updateContactRequestSchema)` ile
2420
+ * doğrulayıp `.set({ ...body, updatedById })` yazdığı için
2421
+ * (`apps/api/src/routes/contacts.ts:314`) `status` GÖNDERMEYEN her kısmi
2422
+ * güncelleme, `active`/`inactive` bir kişiyi SESSİZCE `lead`'e geri
2423
+ * düşürüyordu — dashboard'ın düzenleme formu `status`'ü her zaman gönderdiği
2424
+ * için belirti orada görünmüyordu; kusuru MCP'nin `update_contact` aracı
2425
+ * (yalnız verilen alanları gönderir) ortaya çıkardı.
2426
+ *
2427
+ * KARDEŞ ŞEMALARDA AYNI KUSUR YOK ama gerekçesi TESADÜF: `companies`/
2428
+ * `pipelines`'ın create şemalarında `.default()`'lu alan HİÇ YOK,
2429
+ * `updateDealRequestSchema` ise `.partial()` DEĞİL elle yazılmış bir şemadır.
2430
+ * ⇒ `.default()`'lu bir alan içeren bir create şemasından `.partial()` ile
2431
+ * update şeması TÜRETİRKEN o alanı DAİMA açıkça `.optional()` olarak ez.
2432
+ */
2433
+ const updateContactRequestSchema = createContactRequestSchema.partial().extend({ status: contactStatusSchema.optional() });
2434
+ pagedResponseSchema(z.object({
2435
+ id: z.uuid(),
2436
+ firstName: z.string(),
2437
+ lastName: z.string(),
2438
+ email: z.string().nullable(),
2439
+ phone: z.string().nullable(),
2440
+ mobile: z.string().nullable(),
2441
+ jobTitle: z.string().nullable(),
2442
+ linkedinUrl: z.string().nullable(),
2443
+ avatarUrl: z.string().nullable(),
2444
+ city: z.string().nullable(),
2445
+ country: z.string().nullable(),
2446
+ ownerUserId: z.uuid().nullable(),
2447
+ source: contactSourceSchema.nullable(),
2448
+ status: contactStatusSchema,
2449
+ companyId: z.uuid().nullable(),
2450
+ createdAt: z.iso.datetime(),
2451
+ updatedAt: z.iso.datetime()
2452
+ }));
2453
+ z.object({
2454
+ code: z.literal("contact_limit_reached"),
2455
+ limit: z.number(),
2456
+ planCode: planCodeSchema
2457
+ });
2458
+ z.string().regex(/^[A-Z]{3}$/, "geçerli bir 3 harfli para birimi kodu olmalı (örn. TRY)");
2459
+ //#endregion
2460
+ //#region packages/contracts/src/deals.ts
2461
+ /**
2462
+ * Fırsat (deal) sözleşmeleri (spec §6.2/§6.4).
2463
+ *
2464
+ * KRİTİK KURAL: `updateDealRequestSchema` `.strict()`'tir ve `stageId`/
2465
+ * `pipelineId`/`position`/`outcome`/`closedAt` alanlarını hiç TANIMLAMAZ —
2466
+ * bu alanlar yalnız `moveDealRequestSchema` (`/move` ucu) üzerinden değişir
2467
+ * (bkz. `packages/db/src/schema/deals.ts` dosya başı KRİTİK KURAL 2,
2468
+ * `apps/api/src/routes/deals.ts`). `.strict()` altında bilinmeyen bir anahtar
2469
+ * zod'un `unrecognized_keys` hatasını üretir; bu, `apps/api/src/lib/validate.ts`
2470
+ * içindeki `ZOD_DEFAULT_MESSAGE` deseniyle ZATEN eşleşiyor (`Unrecognized key`
2471
+ * ile başlar) — bu yüzden ayrı bir Türkçe mesaj GEREKMEZ, jenerik "Geçersiz
2472
+ * istek gövdesi" mesajına düşer (bu, Global Constraints #3'ün belgelediği
2473
+ * bilinçli istisna).
2474
+ *
2475
+ * FIX ROUND 1 (bkz. task-3-report.md): `amount` DB'de `numeric(18,2)`'ye
2476
+ * taşındı (önceden `text` idi — `sortable`/`filterable` ilan edilmiş bir
2477
+ * alanın metin karşılaştırmasıyla sıralanması sessiz bir doğruluk hatasıydı).
2478
+ * Biçim kısıtı (yalnız `-?\d+(\.\d{1,2})?`, en fazla 18 anlamlı basamak) ARTIK
2479
+ * `./crm.ts`'teki `numericMoneySchema`'da PAYLAŞILIYOR (Fix round 2 —
2480
+ * `companies.annual_revenue` AYNI sınıf hatayı taşıdığı için oraya taşındı,
2481
+ * bkz. o dosyanın JSDoc'u) — bilimsel gösterim (`1e10`), `NaN`/`Infinity`,
2482
+ * çift işaret (`--5`), virgüllü ondalık (`1,5`) ve 2'den fazla ondalık basamak
2483
+ * hep REDDEDİLİR. PARA YİNE string olarak taşınır, `bignumber.js` dışında
2484
+ * hesaplanmaz.
2485
+ */
2486
+ /** ISO 4217 kodu — 3 büyük harf (DB `char(3)` kolonuyla eşleşir) */
2487
+ const currencyCodeSchema = z.string().trim().regex(/^[A-Z]{3}$/, "ISO 4217 biçiminde 3 büyük harf olmalı (örn. TRY)");
2488
+ /** Fırsat tutarı — DB `numeric(18,2)` ile BİREBİR eşleşen ortak biçim kısıtı (bkz. `crm.ts` `numericMoneySchema`) */
2489
+ const dealAmountSchema = numericMoneySchema;
2490
+ const createDealRequestSchema = z.object({
2491
+ title: z.string().trim().min(1, "başlık gerekli").max(200, "en fazla 200 karakter olabilir"),
2492
+ pipelineId: z.uuid("geçerli bir pipeline kimliği olmalı"),
2493
+ stageId: z.uuid("geçerli bir aşama kimliği olmalı"),
2494
+ amount: dealAmountSchema.optional(),
2495
+ currencyCode: currencyCodeSchema.default("TRY"),
2496
+ companyId: z.uuid("geçerli bir şirket kimliği olmalı").optional(),
2497
+ primaryContactId: z.uuid("geçerli bir kişi kimliği olmalı").optional(),
2498
+ expectedCloseDate: z.iso.datetime("geçerli bir tarih olmalı").optional(),
2499
+ ownerUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional()
2500
+ });
2501
+ /**
2502
+ * Genel güncelleme — `stageId`/`pipelineId`/`position`/`outcome`/`closedAt`
2503
+ * BİLEREK YOK (bkz. dosya başı KRİTİK KURAL). `.strict()`: bu alanlardan biri
2504
+ * gönderilirse 400 (bkz. `apps/api/test/deals.test.ts`).
2505
+ */
2506
+ const updateDealRequestSchema = z.object({
2507
+ title: z.string().trim().min(1, "başlık gerekli").max(200, "en fazla 200 karakter olabilir").optional(),
2508
+ amount: dealAmountSchema.optional(),
2509
+ currencyCode: currencyCodeSchema.optional(),
2510
+ companyId: z.uuid("geçerli bir şirket kimliği olmalı").optional(),
2511
+ primaryContactId: z.uuid("geçerli bir kişi kimliği olmalı").optional(),
2512
+ expectedCloseDate: z.iso.datetime("geçerli bir tarih olmalı").optional(),
2513
+ ownerUserId: z.uuid("geçerli bir kullanıcı kimliği olmalı").optional(),
2514
+ lostReason: z.string().trim().max(500, "en fazla 500 karakter olabilir").optional()
2515
+ }).strict();
2516
+ /**
2517
+ * Kanban taşıma kontratı (spec §6.4) — pipeline'ın `reorderRequestSchema`'sıyla
2518
+ * AYNI komşu şekli (`beforeId`/`afterId`), yalnız `stageId` hedefi eklenir.
2519
+ * `pipelineId` YOK: hedef aşamanın pipeline'ı, fırsatın yeni pipeline'ı olarak
2520
+ * KABUL EDİLİR (bkz. routes/deals.ts) — istemcinin ayrıca tutarlı bir
2521
+ * `pipelineId` göndermesini gerektirmeden çapraz-pipeline taşımaya izin verir.
2522
+ */
2523
+ const moveDealRequestSchema = z.object({
2524
+ stageId: z.uuid("geçerli bir aşama kimliği olmalı"),
2525
+ beforeId: z.uuid("geçerli bir kimlik olmalı").optional(),
2526
+ afterId: z.uuid("geçerli bir kimlik olmalı").optional()
2527
+ });
2528
+ z.object({
2529
+ contactId: z.uuid("geçerli bir kişi kimliği olmalı"),
2530
+ role: z.string().trim().max(80, "en fazla 80 karakter olabilir").optional()
2531
+ });
2532
+ const dealContactResponseSchema = z.object({
2533
+ contactId: z.uuid(),
2534
+ role: z.string().nullable(),
2535
+ createdAt: z.iso.datetime()
2536
+ });
2537
+ const dealResponseSchema = z.object({
2538
+ id: z.uuid(),
2539
+ pipelineId: z.uuid(),
2540
+ stageId: z.uuid(),
2541
+ companyId: z.uuid().nullable(),
2542
+ primaryContactId: z.uuid().nullable(),
2543
+ title: z.string(),
2544
+ amount: z.string().nullable(),
2545
+ currencyCode: z.string(),
2546
+ position: z.number(),
2547
+ expectedCloseDate: z.iso.datetime().nullable(),
2548
+ closedAt: z.iso.datetime().nullable(),
2549
+ outcome: dealOutcomeSchema,
2550
+ lostReason: z.string().nullable(),
2551
+ ownerUserId: z.uuid().nullable(),
2552
+ createdAt: z.iso.datetime(),
2553
+ updatedAt: z.iso.datetime()
2554
+ });
2555
+ dealResponseSchema.extend({ contacts: z.array(dealContactResponseSchema) });
2556
+ pagedResponseSchema(dealResponseSchema);
2557
+ z.object({
2558
+ email: emailSchema,
2559
+ role: memberRoleSchema
2560
+ });
2561
+ const inviteStatusSchema = z.enum([
2562
+ "pending",
2563
+ "accepted",
2564
+ "revoked"
2565
+ ]);
2566
+ /** Tenant içi davet listesi satırı — token/hash ASLA yer almaz */
2567
+ const inviteResponseSchema = z.object({
2568
+ id: z.uuid(),
2569
+ email: z.string(),
2570
+ role: memberRoleSchema,
2571
+ status: inviteStatusSchema,
2572
+ expiresAt: z.iso.datetime(),
2573
+ createdAt: z.iso.datetime()
2574
+ });
2575
+ z.object({ invites: z.array(inviteResponseSchema) });
2576
+ z.object({
2577
+ tenantName: z.string(),
2578
+ email: z.string(),
2579
+ role: memberRoleSchema,
2580
+ status: inviteStatusSchema,
2581
+ expiresAt: z.iso.datetime()
2582
+ });
2583
+ z.object({
2584
+ tenantId: z.uuid(),
2585
+ tenantName: z.string(),
2586
+ role: memberRoleSchema
2587
+ });
2588
+ //#endregion
2589
+ //#region packages/contracts/src/legal/company.ts
2590
+ const VENNYX_COMPANY = {
2591
+ legalName: "Vennyx Yazılım Danışmanlık A.Ş.",
2592
+ taxOffice: "Uluçınar Vergi Dairesi",
2593
+ taxNumber: "9241072735",
2594
+ tradeRegistryNo: "40690",
2595
+ tradeRegistryCity: "Gebze",
2596
+ mersisNo: "0924107273500001",
2597
+ address: "Muallimköy Mah. Deniz Cad. No: 143/8 - 1.1.C1 Blok - Zemin Kat - Kapı No: Z01 - 41400 Gebze/Kocaeli",
2598
+ kepAddress: "vennyxyazilim@hs01.kep.tr",
2599
+ phone: "+90 (850) 242 8992",
2600
+ website: "https://solicrm.com",
2601
+ legalEmail: "legal@solicrm.com",
2602
+ privacyEmail: "privacy@solicrm.com"
2603
+ };
2604
+ /** Hukuki belgelerin "Veri Sorumlusu/Sağlayıcı Kimliği" bölümünde tekrar eden paragraf */
2605
+ function companyIdentityParagraph(locale) {
2606
+ const c = VENNYX_COMPANY;
2607
+ if (locale === "tr") return `${c.legalName} · ${c.taxOffice} · VN ${c.taxNumber} · ${c.tradeRegistryCity} Ticaret Sicil No ${c.tradeRegistryNo} · MERSİS ${c.mersisNo} · ${c.address}`;
2608
+ return `${c.legalName} · Tax office: ${c.taxOffice} · Tax No: ${c.taxNumber} · Trade Registry No (${c.tradeRegistryCity}): ${c.tradeRegistryNo} · MERSIS: ${c.mersisNo} · ${c.address}, Türkiye`;
2609
+ }
2610
+ /** İletişim/başvuru bölümünde tekrar eden paragraf */
2611
+ function contactParagraph(locale) {
2612
+ const c = VENNYX_COMPANY;
2613
+ if (locale === "tr") return `KEP: ${c.kepAddress} · Tel: ${c.phone} · Hukuki başvurular: ${c.legalEmail} · Gizlilik başvuruları: ${c.privacyEmail}`;
2614
+ return `Registered e-mail (KEP): ${c.kepAddress} · Phone: ${c.phone} · Legal inquiries: ${c.legalEmail} · Privacy inquiries: ${c.privacyEmail}`;
2615
+ }
2616
+ contactParagraph("tr"), contactParagraph("en");
2617
+ companyIdentityParagraph("tr"), contactParagraph("tr"), companyIdentityParagraph("en"), contactParagraph("en");
2618
+ companyIdentityParagraph("tr"), contactParagraph("tr"), companyIdentityParagraph("en"), contactParagraph("en");
2619
+ companyIdentityParagraph("tr"), contactParagraph("tr"), companyIdentityParagraph("en"), contactParagraph("en");
2620
+ companyIdentityParagraph("tr"), contactParagraph("tr"), companyIdentityParagraph("en"), contactParagraph("en");
2621
+ contactParagraph("tr"), contactParagraph("en");
2622
+ contactParagraph("tr"), contactParagraph("en");
2623
+ contactParagraph("tr"), contactParagraph("en");
2624
+ companyIdentityParagraph("tr"), contactParagraph("tr"), companyIdentityParagraph("en"), contactParagraph("en");
2625
+ //#endregion
2626
+ //#region packages/contracts/src/legal/slugs.ts
2627
+ /** Spec §10'un tam listesi — 9 belge, sıra `/legal` hub'ının kart sırasıdır */
2628
+ const LEGAL_SLUGS = [
2629
+ "terms",
2630
+ "privacy",
2631
+ "cookies",
2632
+ "kvkk",
2633
+ "distance-sales",
2634
+ "refund-policy",
2635
+ "dpa",
2636
+ "subprocessors",
2637
+ "sla"
2638
+ ];
2639
+ //#endregion
2640
+ //#region packages/contracts/src/legal/types.ts
2641
+ /**
2642
+ * `@solicrm/i18n`'in `SupportedLanguage`'ı İLE AYNI kümedir ama BAĞIMSIZ
2643
+ * tanımlanır (contracts, i18n'e bağımlı OLMAMALI — katman ihlali). Senkron
2644
+ * kalması `test/legal-locale-parity.test.ts` ile mekanik olarak zorlanır.
2645
+ */
2646
+ const LEGAL_LOCALES = ["tr", "en"];
2647
+ z.object({ documentSlug: z.enum(LEGAL_SLUGS, "geçerli bir belge olmalı") });
2648
+ const legalAcceptanceResponseSchema = z.object({
2649
+ id: z.uuid(),
2650
+ documentSlug: z.enum(LEGAL_SLUGS),
2651
+ locale: z.enum(LEGAL_LOCALES),
2652
+ effectiveDate: z.string(),
2653
+ acceptedAt: z.iso.datetime(),
2654
+ isCurrentVersion: z.boolean()
2655
+ });
2656
+ z.object({ acceptances: z.array(legalAcceptanceResponseSchema) });
2657
+ //#endregion
2658
+ //#region packages/contracts/src/pipelines.ts
2659
+ /**
2660
+ * Pipeline + aşama (stage) sözleşmeleri (spec §6.2/§6.4).
2661
+ *
2662
+ * KRİTİK KURAL: `probability` (yüzde 0-100) PARA DEĞİLDİR — native `number`
2663
+ * kabul edilir (bkz. spec §6.1/plan Global Constraints #19, DB kolonu
2664
+ * `numeric(5,2)`). `pipelineStageColorSchema`, `common.ts`'teki `hexColorSchema`'yı
2665
+ * YENİDEN KULLANIR — spec renk formatı vermiyor, yeni bir renk kataloğu
2666
+ * İCAT EDİLMEDİ.
2667
+ */
2668
+ const pipelineStageColorSchema = hexColorSchema;
2669
+ z.object({
2670
+ name: z.string().trim().min(1, "ad gerekli").max(120, "en fazla 120 karakter olabilir"),
2671
+ isDefault: z.boolean().optional()
2672
+ }).partial();
2673
+ const pipelineResponseSchema = z.object({
2674
+ id: z.uuid(),
2675
+ name: z.string(),
2676
+ isDefault: z.boolean(),
2677
+ position: z.number(),
2678
+ createdAt: z.iso.datetime(),
2679
+ updatedAt: z.iso.datetime()
2680
+ });
2681
+ z.object({ pipelines: z.array(pipelineResponseSchema) });
2682
+ /**
2683
+ * `key`, programatik eşleştirme için kullanılır (ör. `is_won`/`is_lost`
2684
+ * kanban kolonunu bulmak) — bu yüzden slug benzeri kısıtlı bir biçim taşır
2685
+ * (küçük harf, rakam, alt çizgi).
2686
+ */
2687
+ const stageKeySchema = z.string().trim().min(1, "anahtar gerekli").max(60, "en fazla 60 karakter olabilir").regex(/^[a-z][a-z0-9_]*$/, "küçük harf, rakam ve alt çizgi kullanılabilir");
2688
+ z.object({
2689
+ key: stageKeySchema,
2690
+ label: z.string().trim().min(1, "etiket gerekli").max(120, "en fazla 120 karakter olabilir"),
2691
+ color: pipelineStageColorSchema.optional(),
2692
+ probability: z.number().min(0, "en az 0 olmalı").max(100, "en fazla 100 olmalı").optional(),
2693
+ isWon: z.boolean().optional(),
2694
+ isLost: z.boolean().optional()
2695
+ }).partial();
2696
+ const stageResponseSchema = z.object({
2697
+ id: z.uuid(),
2698
+ pipelineId: z.uuid(),
2699
+ key: z.string(),
2700
+ label: z.string(),
2701
+ color: z.string().nullable(),
2702
+ position: z.number(),
2703
+ probability: z.string().nullable(),
2704
+ isWon: z.boolean(),
2705
+ isLost: z.boolean(),
2706
+ createdAt: z.iso.datetime(),
2707
+ updatedAt: z.iso.datetime()
2708
+ });
2709
+ z.object({ stages: z.array(stageResponseSchema) });
2710
+ z.object({
2711
+ beforeId: z.uuid("geçerli bir kimlik olmalı").optional(),
2712
+ afterId: z.uuid("geçerli bir kimlik olmalı").optional()
2713
+ });
2714
+ //#endregion
2715
+ //#region packages/contracts/src/platform.ts
2716
+ /**
2717
+ * Platform yönetimi sözleşmeleri (spec §5.8) — yalnız Vennyx personeli
2718
+ * (`SOLICRM_ADMIN_EMAILS` allowlist'i, bkz. `apps/api/src/lib/platform-admin.ts`)
2719
+ * tarafından tüketilir.
2720
+ */
2721
+ const platformTenantSummarySchema = z.object({
2722
+ id: z.uuid(),
2723
+ name: z.string(),
2724
+ slug: z.string(),
2725
+ planCode: planCodeSchema,
2726
+ suspendedAt: z.iso.datetime().nullable(),
2727
+ createdAt: z.iso.datetime(),
2728
+ memberCount: z.number().int().nonnegative()
2729
+ });
2730
+ z.object({ tenants: z.array(platformTenantSummarySchema) });
2731
+ z.object({
2732
+ memberCount: z.number().int().nonnegative(),
2733
+ activeApiKeyCount: z.number().int().nonnegative(),
2734
+ pendingInviteCount: z.number().int().nonnegative(),
2735
+ ssoEnabled: z.boolean()
2736
+ });
2737
+ z.object({ email: z.string() });
2738
+ /**
2739
+ * `GET /v1/admin/tenants/:id/audit-logs` satırı (Faz 9 Task 2, plan kararı "E").
2740
+ *
2741
+ * `data` NEDEN `z.unknown()`: `audit_logs.data` şemasız bir jsonb kolonudur —
2742
+ * her `writeAudit()` çağrısı kendi eylemine özgü bir şekil yazar (18 dosyada 55
2743
+ * çağrı), ortak bir yapı YOKTUR. Burada bir şekil DAYATMAK, bugün geçerli bir
2744
+ * denetim satırını yarın şema hatasıyla GÖRÜNMEZ kılardı — denetim kaydı
2745
+ * append-only olduğu için o satır bir daha DÜZELTİLEMEZ. Zod v4'te `z.unknown()`
2746
+ * anahtarın VARLIĞINI yine zorunlu kılar (ölçüldü: `{}` girdisi `success:false`),
2747
+ * yani "alan hiç gelmedi" ile "alan null" ayrımı KORUNUR.
2748
+ *
2749
+ * KRİTİK — `data` NORMALİZE EDİLMİŞ şekli tanımlar: ham `tx.execute()` yolu bu
2750
+ * kolonu jsonb çift-encode kusuru nedeniyle STRING olarak döndürebilir; route
2751
+ * katmanı tek bir `JSON.parse` ile bunu geri alır (bkz.
2752
+ * `apps/api/src/routes/platform-audit.ts`).
2753
+ */
2754
+ const adminAuditLogEntrySchema = z.object({
2755
+ id: z.uuid(),
2756
+ actorEmail: z.string().nullable(),
2757
+ action: z.string(),
2758
+ target: z.string().nullable(),
2759
+ data: z.unknown().nullable(),
2760
+ createdAt: z.iso.datetime()
2761
+ });
2762
+ z.object({
2763
+ items: z.array(adminAuditLogEntrySchema),
2764
+ nextCursor: z.string().nullable(),
2765
+ hasMore: z.boolean()
2766
+ });
2767
+ /**
2768
+ * Zehirli/çözülmemiş webhook olayı satırı — `GET /v1/admin/webhook-events`
2769
+ * (Faz 7 Task 6'nın ürettiği uç, Faz 9 Task 3 tüketicisi).
2770
+ *
2771
+ * KRİTİK — `payload` BİLİNÇLİ OLARAK YOK: `webhook_events.payload` Whop'un ham
2772
+ * gövdesidir (müşteri/ödeme bilgisi taşır, `safeJsonb` kolonu). Bu sözleşme onu
2773
+ * TAŞIMAZ ve route katmanı kolonu SELECT bile ETMEZ — operatör ekranı olayı
2774
+ * TANIMLAMAYA yeten alanlarla sınırlıdır (kaynak/tür/zaman/hata). Ham gövdeye
2775
+ * ihtiyaç duyulursa doğru yer DB erişimidir, bir HTTP yanıtı değil.
2776
+ *
2777
+ * `error` SERBEST METİNDİR ve route katmanında hassas sayı dizileri (VKN/TCKN/
2778
+ * kart benzeri) MASKELENMİŞ olarak gelir — bkz. `apps/api/src/lib/mask-sensitive.ts`.
2779
+ *
2780
+ * İKİ "çözülmemiş" sınıfı bu üç alandan TÜRETİLİR (bkz. `lib/webhook-health.ts`):
2781
+ * `error !== null` ⇒ iş kuralı hatası; `error === null && processedAt === null`
2782
+ * ⇒ hiç işlenemedi/sıkıştı.
2783
+ */
2784
+ const adminWebhookEventSchema = z.object({
2785
+ id: z.uuid(),
2786
+ source: z.string(),
2787
+ eventType: z.string(),
2788
+ receivedAt: z.iso.datetime(),
2789
+ processedAt: z.iso.datetime().nullable(),
2790
+ error: z.string().nullable()
2791
+ });
2792
+ z.object({ items: z.array(adminWebhookEventSchema) });
2793
+ z.object({ reason: z.string().trim().min(1, "kapatma gerekçesi gerekli").max(1e3, "en fazla 1000 karakter olabilir") });
2794
+ z.object({
2795
+ id: z.uuid(),
2796
+ closedAt: z.iso.datetime(),
2797
+ closedReason: z.string(),
2798
+ closedBy: z.string()
2799
+ });
2800
+ z.object({
2801
+ effective: z.boolean(),
2802
+ source: z.enum(["platform-setting", "env-fallback"]),
2803
+ envFallback: z.boolean(),
2804
+ updatedAt: z.iso.datetime().nullable(),
2805
+ updatedByEmail: z.string().nullable()
2806
+ });
2807
+ z.object({ enabled: z.boolean("etkin değeri true veya false olmalı") });
2808
+ z.object({
2809
+ paymentId: z.uuid(),
2810
+ tenantId: z.uuid(),
2811
+ tenantName: z.string(),
2812
+ whopPaymentId: z.string(),
2813
+ paymentStatus: z.string(),
2814
+ /** `numeric(18,2)` — API'de DAİMA dizge (kesinlik korunur, bkz. CLAUDE.md) */
2815
+ amountGross: z.string(),
2816
+ refundedAmount: z.string(),
2817
+ currencyCode: z.string(),
2818
+ /** `'passed' | 'out_of_band' | 'not_verifiable'` — bant dışı bir ödemeyi iade etmek ÖZEL dikkat ister */
2819
+ amountCheckStatus: z.string(),
2820
+ paidAt: z.iso.datetime().nullable(),
2821
+ invoice: z.object({
2822
+ id: z.uuid(),
2823
+ invoiceNumber: z.string(),
2824
+ status: z.string(),
2825
+ amountGross: z.string(),
2826
+ currencyCode: z.string(),
2827
+ formalizedAt: z.iso.datetime().nullable()
2828
+ }).nullable(),
2829
+ billing: z.object({
2830
+ kind: z.string(),
2831
+ country: z.string(),
2832
+ maskedTaxId: z.string().nullable()
2833
+ }).nullable(),
2834
+ openRefund: z.object({
2835
+ id: z.uuid(),
2836
+ status: z.string(),
2837
+ amountGross: z.string(),
2838
+ attemptCount: z.number().int().nonnegative(),
2839
+ lastAttemptAt: z.iso.datetime().nullable()
2840
+ }).nullable()
2841
+ });
2842
+ z.object({
2843
+ refundId: z.uuid(),
2844
+ status: z.enum([
2845
+ "pending",
2846
+ "completed",
2847
+ "awaiting_balance",
2848
+ "failed"
2849
+ ]),
2850
+ message: z.string()
2851
+ });
2852
+ const filterLeafSchema = z.object({
2853
+ fieldKey: z.string().trim().min(1, "alan adı gerekli").max(80, "en fazla 80 karakter olabilir"),
2854
+ operand: filterOperandSchema,
2855
+ value: z.unknown()
2856
+ });
2857
+ const filterGroupSchema = z.lazy(() => z.object({
2858
+ logicalOperator: filterLogicalOperatorSchema,
2859
+ filters: z.array(filterLeafSchema).max(20, "bir grupta en fazla 20 filtre olabilir"),
2860
+ groups: z.array(filterGroupSchema).max(5, "bir grupta en fazla 5 alt grup olabilir")
2861
+ }));
2862
+ /** Bir filtre grubu ağacının GERÇEK azami derinliğini ölçer (bkz. dosya başı KRİTİK KURAL) */
2863
+ function filterGroupDepth(group) {
2864
+ if (group.groups.length === 0) return 1;
2865
+ return 1 + Math.max(...group.groups.map(filterGroupDepth));
2866
+ }
2867
+ const fieldSelectionSchema = z.object({
2868
+ fieldKey: z.string().trim().min(1, "alan adı gerekli").max(80, "en fazla 80 karakter olabilir"),
2869
+ isVisible: z.boolean().default(true)
2870
+ });
2871
+ const sortSelectionSchema = z.object({
2872
+ fieldKey: z.string().trim().min(1, "alan adı gerekli").max(80, "en fazla 80 karakter olabilir"),
2873
+ direction: sortDirectionSchema
2874
+ });
2875
+ /** Derinlik aşımını yakalayan paylaşılan `superRefine` — create/update ŞEMASI ikisi de kullanır */
2876
+ function checkFilterGroupDepth(rootFilterGroup, ctx) {
2877
+ if (rootFilterGroup === void 0) return;
2878
+ if (filterGroupDepth(rootFilterGroup) > 5) ctx.addIssue({
2879
+ code: "custom",
2880
+ path: ["rootFilterGroup"],
2881
+ message: `filtre grubu en fazla 5 seviye iç içe olabilir`
2882
+ });
2883
+ }
2884
+ z.object({
2885
+ name: z.string().trim().min(1, "ad gerekli").max(120, "en fazla 120 karakter olabilir"),
2886
+ icon: z.string().trim().max(60, "en fazla 60 karakter olabilir").optional(),
2887
+ resource: savedViewResourceSchema,
2888
+ layout: savedViewLayoutSchema.default("table"),
2889
+ visibility: savedViewVisibilitySchema.default("private"),
2890
+ groupByFieldKey: z.string().trim().min(1, "alan adı gerekli").max(80, "en fazla 80 karakter olabilir").optional(),
2891
+ isDefault: z.boolean().optional(),
2892
+ fields: z.array(fieldSelectionSchema).max(60, "en fazla 60 kolon olabilir").default([]),
2893
+ sorts: z.array(sortSelectionSchema).max(5, "en fazla 5 sıralama alanı olabilir").default([]),
2894
+ rootFilterGroup: filterGroupSchema.optional()
2895
+ }).superRefine((data, ctx) => checkFilterGroupDepth(data.rootFilterGroup, ctx));
2896
+ z.object({
2897
+ name: z.string().trim().min(1, "ad gerekli").max(120, "en fazla 120 karakter olabilir").optional(),
2898
+ icon: z.string().trim().max(60, "en fazla 60 karakter olabilir").nullable().optional(),
2899
+ layout: savedViewLayoutSchema.optional(),
2900
+ visibility: savedViewVisibilitySchema.optional(),
2901
+ groupByFieldKey: z.string().trim().min(1, "alan adı gerekli").max(80, "en fazla 80 karakter olabilir").nullable().optional(),
2902
+ isDefault: z.boolean().optional(),
2903
+ fields: z.array(fieldSelectionSchema).max(60, "en fazla 60 kolon olabilir").optional(),
2904
+ sorts: z.array(sortSelectionSchema).max(5, "en fazla 5 sıralama alanı olabilir").optional(),
2905
+ rootFilterGroup: filterGroupSchema.nullable().optional()
2906
+ }).strict().superRefine((data, ctx) => {
2907
+ if (data.rootFilterGroup !== null) checkFilterGroupDepth(data.rootFilterGroup, ctx);
2908
+ });
2909
+ const savedViewFieldResponseSchema = z.object({
2910
+ fieldKey: z.string(),
2911
+ isVisible: z.boolean()
2912
+ });
2913
+ const savedViewSortResponseSchema = z.object({
2914
+ fieldKey: z.string(),
2915
+ direction: sortDirectionSchema
2916
+ });
2917
+ z.object({
2918
+ id: z.uuid(),
2919
+ name: z.string(),
2920
+ icon: z.string().nullable(),
2921
+ resource: savedViewResourceSchema,
2922
+ layout: savedViewLayoutSchema,
2923
+ /** Sidebar sırası — `pipelineResponseSchema`/`stageResponseSchema` ile TUTARLILIK için döndürülür (bu fazda bir /reorder ucu YOK, yalnız append-only, bkz. routes/saved-views.ts `nextViewPosition`) */
2924
+ position: z.number(),
2925
+ visibility: savedViewVisibilitySchema,
2926
+ createdById: z.uuid(),
2927
+ groupByFieldKey: z.string().nullable(),
2928
+ isDefault: z.boolean(),
2929
+ fields: z.array(savedViewFieldResponseSchema),
2930
+ sorts: z.array(savedViewSortResponseSchema),
2931
+ rootFilterGroup: filterGroupSchema.nullable(),
2932
+ createdAt: z.iso.datetime(),
2933
+ updatedAt: z.iso.datetime()
2934
+ });
2935
+ /**
2936
+ * Liste ucu (`GET /saved-views`) YANITI — özet şekil, `fields`/`sorts`/
2937
+ * `rootFilterGroup` İÇERMEZ (bkz. `apps/api/src/routes/saved-views.ts`
2938
+ * KRİTİK KURAL — N+1 önleme: bir tenant'ın TÜM görünümlerini listelerken
2939
+ * her biri için 4 ek sorgu (fields/sorts/groups/filters) atmak N+1 olurdu;
2940
+ * tam detay yalnız TEKİL görünüm uçlarında (`GET .../saved-views/:viewId`,
2941
+ * `POST .../apply`) döner — o uçlarda "N" zaten 1'dir).
2942
+ */
2943
+ const savedViewSummaryResponseSchema = z.object({
2944
+ id: z.uuid(),
2945
+ name: z.string(),
2946
+ icon: z.string().nullable(),
2947
+ resource: savedViewResourceSchema,
2948
+ layout: savedViewLayoutSchema,
2949
+ position: z.number(),
2950
+ visibility: savedViewVisibilitySchema,
2951
+ createdById: z.uuid(),
2952
+ isDefault: z.boolean(),
2953
+ createdAt: z.iso.datetime(),
2954
+ updatedAt: z.iso.datetime()
2955
+ });
2956
+ z.object({ views: z.array(savedViewSummaryResponseSchema) });
2957
+ const searchableResourceSchema = z.enum([
2958
+ "contacts",
2959
+ "companies",
2960
+ "deals"
2961
+ ]);
2962
+ const searchResultItemSchema = z.object({
2963
+ id: z.uuid("geçersiz kimlik"),
2964
+ resource: searchableResourceSchema,
2965
+ /** Kaynağın görünen adı — contacts: ad soyad, companies: unvan, deals: başlık */
2966
+ title: z.string(),
2967
+ /** İkincil satır (e-posta / alan adı / tutar) — kolon boşsa `null` */
2968
+ snippet: z.string().nullable()
2969
+ });
2970
+ z.object({ items: z.array(searchResultItemSchema) });
2971
+ const searchQuerySchema = z.object({
2972
+ q: z.string().trim().min(1, "arama metni boş olamaz").max(200, `en fazla 200 karakter olabilir`),
2973
+ limit: z.coerce.number("sayı olmalı").int("tam sayı olmalı").min(1, "en az 1 olmalı").max(50, `en fazla 50 olabilir`).optional(),
2974
+ /**
2975
+ * Aranacak kaynakları daraltır (virgülle ayrılmış). Verilmezse ÜÇÜ de
2976
+ * aranır. Bilinmeyen bir kaynak adı 400 üretir — sessizce yok SAYILMAZ
2977
+ * (fail-closed: "tanımadığım filtreyi görmezden gel" bir fail-open kalıbıdır).
2978
+ */
2979
+ resources: z.string().transform((raw) => raw.split(",").map((part) => part.trim())).pipe(z.array(searchableResourceSchema).min(1, "en az bir kaynak belirtilmeli")).optional()
2980
+ });
2981
+ //#endregion
2982
+ //#region packages/contracts/src/sso.ts
2983
+ /**
2984
+ * BYO-SSO tenant bağlantısı sözleşmeleri (spec §5.7).
2985
+ *
2986
+ * KRİTİK KURAL: `ssoConnectionResponseSchema` `clientSecret`/`clientSecretEncrypted`
2987
+ * ALANI TAŞIMAZ — sır listeleme/okuma yanıtlarında ASLA dönmez.
2988
+ */
2989
+ const DOMAIN_RE = /^(?!-)[a-z0-9-]{1,63}(?<!-)(\.[a-z0-9-]{1,63})+$/;
2990
+ /**
2991
+ * Genel (kişisel) e-posta sağlayıcı alan adları — bunlar SSO `emailDomain`
2992
+ * olarak KABUL EDİLMEZ (spec'in "gmail.com gibi genel bir alan adını
2993
+ * doğrulamaya çalışırsa?" tehdidi — bir tenant bu alan adını doğrulayabilseydi
2994
+ * TÜM Gmail kullanıcılarını kendi tenant'ına çekebilirdi). TAM/kapsayıcı bir
2995
+ * liste DEĞİLDİR — ileride genişletilebilir bir başlangıç kümesidir (bkz.
2996
+ * plan "Belirsiz bırakılan noktalar" #4).
2997
+ *
2998
+ * Plandaki temel liste (gmail/outlook/yahoo/icloud ailesi ve büyük bölgesel
2999
+ * sağlayıcılar) OLDUĞU GİBİ alındı. Task brifinginin "eksik görürsen genişlet"
3000
+ * talimatıyla implementasyon anında GENUINE olarak eklenenler (regex her biri
3001
+ * AYRI bir alan adı olduğu için kök alan adı listede olsa da eşleşmez):
3002
+ * Microsoft'un bölgesel tüketici alan adları (`outlook.co.uk`, `hotmail.co.uk`,
3003
+ * `live.co.uk`, `hotmail.fr`, `live.fr`, `hotmail.de`, `live.de` — `outlook.com`/
3004
+ * `hotmail.com`/`live.com` zaten listede ama bunlar ayrı TLD'ler) ve büyük ISP
3005
+ * webmail'leri (kurumsal alan adı OLMASI beklenmeyen, geniş bireysel kullanıcı
3006
+ * tabanına sahip): `comcast.net`/`att.net`/`verizon.net`/`sbcglobal.net` (ABD),
3007
+ * `btinternet.com` (İngiltere), `free.fr` (Fransa).
3008
+ */
3009
+ const PUBLIC_EMAIL_DOMAINS = /* @__PURE__ */ new Set([
3010
+ "gmail.com",
3011
+ "googlemail.com",
3012
+ "outlook.com",
3013
+ "hotmail.com",
3014
+ "live.com",
3015
+ "msn.com",
3016
+ "yahoo.com",
3017
+ "yahoo.co.uk",
3018
+ "yahoo.fr",
3019
+ "ymail.com",
3020
+ "icloud.com",
3021
+ "me.com",
3022
+ "mac.com",
3023
+ "aol.com",
3024
+ "protonmail.com",
3025
+ "proton.me",
3026
+ "pm.me",
3027
+ "gmx.com",
3028
+ "gmx.de",
3029
+ "gmx.net",
3030
+ "mail.com",
3031
+ "yandex.com",
3032
+ "yandex.ru",
3033
+ "zoho.com",
3034
+ "fastmail.com",
3035
+ "hey.com",
3036
+ "hushmail.com",
3037
+ "tutanota.com",
3038
+ "tuta.io",
3039
+ "inbox.com",
3040
+ "mail.ru",
3041
+ "qq.com",
3042
+ "163.com",
3043
+ "126.com",
3044
+ "naver.com",
3045
+ "daum.net",
3046
+ "web.de",
3047
+ "t-online.de",
3048
+ "orange.fr",
3049
+ "laposte.net",
3050
+ "libero.it",
3051
+ "virgilio.it",
3052
+ "seznam.cz",
3053
+ "wp.pl",
3054
+ "onet.pl",
3055
+ "rocketmail.com",
3056
+ "outlook.co.uk",
3057
+ "hotmail.co.uk",
3058
+ "live.co.uk",
3059
+ "hotmail.fr",
3060
+ "live.fr",
3061
+ "hotmail.de",
3062
+ "live.de",
3063
+ "comcast.net",
3064
+ "att.net",
3065
+ "verizon.net",
3066
+ "sbcglobal.net",
3067
+ "btinternet.com",
3068
+ "free.fr"
3069
+ ]);
3070
+ /**
3071
+ * [Fix round 1 — Minor 1, code review bulgusu]: `PUBLIC_EMAIL_DOMAINS.has(value)`
3072
+ * yalnız TAM eşleşme kontrol ediyordu — `mail.gmail.com` gibi bir ALT alan adı
3073
+ * engeli atlıyordu (canlı doğrulandı: `ACCEPTED` dönüyordu). Pratikte risk
3074
+ * düşük (saldırgan gerçek sağlayıcının alt alan DNS'ini kontrol edemez, TXT
3075
+ * sahiplik kanıtı hâlâ gerekir) ama kafa karıştırıcı/gereksiz bir açık —
3076
+ * engellenen bir kök alan adının HER alt alan adı da (`x.gmail.com`,
3077
+ * `a.b.gmail.com` vb.) reddedilir. `value` zaten `.toLowerCase()`'dan SONRA
3078
+ * buraya gelir (zincirleme sırası), bu yüzden büyük/küçük harf normalize
3079
+ * edilmiş durumda.
3080
+ */
3081
+ function isPublicEmailProviderDomain(domain) {
3082
+ if (PUBLIC_EMAIL_DOMAINS.has(domain)) return true;
3083
+ for (const blocked of PUBLIC_EMAIL_DOMAINS) if (domain.endsWith(`.${blocked}`)) return true;
3084
+ return false;
3085
+ }
3086
+ const ssoEmailDomainSchema = z.string().trim().toLowerCase().max(255, "en fazla 255 karakter olabilir").regex(DOMAIN_RE, "geçerli bir alan adı olmalı (örn. ornek.com)").refine((value) => !isPublicEmailProviderDomain(value), "genel bir e-posta sağlayıcı alan adı SSO için kullanılamaz");
3087
+ /**
3088
+ * Format kontrolü — prod'da yalnız https zorunluluğu ROUTE katmanında ayrıca
3089
+ * uygulanır (bkz. sso-admin.ts, `NODE_ENV==='production'` kontrolü).
3090
+ *
3091
+ * [Fix round 1 — Minor 2, code review bulgusu]: bu şema ÖNCEDEN
3092
+ * `http://127.0.0.1:PORT`'u da (localhost ile AYNI) dev istisnasına
3093
+ * sokuyordu, ama `lib/ssrf-guard.ts`'in `assertPublicHttpsUrl`'i yalnız
3094
+ * `localhost` HOST ADINI dev-bypass eder — çıplak `127.0.0.1` IP'sini HER
3095
+ * ZAMAN reddeder (bkz. o dosyanın KRİTİK KURAL 1 yorumu: kritik güvenlik
3096
+ * kuralı "özel IP aralıklarına (…, 127/8, …) gitmemeli" der, istisna YALNIZ
3097
+ * `localhost` için tanımlıdır). Sonuç: `http://127.0.0.1:9000` issuer'ıyla
3098
+ * bağlantı OLUŞTURULABİLİYORDU (bu şema kabul ediyordu) ama `/test` ucu HER
3099
+ * ZAMAN başarısız oluyordu (guard reddediyordu) — güvenlik açığı değil ama
3100
+ * kafa karıştırıcı bir tutarsızlık.
3101
+ *
3102
+ * ÇÖZÜM YÖNÜ — şema DARALTILDI (guard GENİŞLETİLMEDİ): guard'ın davranışı
3103
+ * kritik güvenlik kuralının kelimesi kelimesine yazdığı, implementasyon
3104
+ * anında canlı test edilip reviewer tarafından bağımsız doğrulanmış bir
3105
+ * karardı (bkz. Task 1 raporu) — bunu GEVŞETMEK yerine şemayı guard'a
3106
+ * UYDURMAK, sistemin her yerinde AYNI (daha sıkı) kuralı korur. Artık yalnız
3107
+ * `http://localhost` (port'lu/portsuz) dev istisnası kabul edilir; çıplak
3108
+ * `127.0.0.1` (ve `::1`) issuer olarak asla kabul edilmez — bu ikisi zaten
3109
+ * `ssrf-guard.ts`'te de reddediliyordu.
3110
+ */
3111
+ const ssoIssuerSchema = z.url("geçerli bir URL olmalı").max(500, "en fazla 500 karakter olabilir").refine((value) => value.startsWith("https://") || /^http:\/\/localhost(:\d+)?(\/|$)/.test(value), "issuer https olmalı (yalnız geliştirmede localhost istisnası var)");
3112
+ z.object({
3113
+ issuer: ssoIssuerSchema,
3114
+ clientId: z.string().trim().min(1, "clientId gerekli").max(255, "en fazla 255 karakter olabilir"),
3115
+ clientSecret: z.string().trim().min(1, "clientSecret gerekli").max(2e3, "en fazla 2000 karakter olabilir"),
3116
+ emailDomain: ssoEmailDomainSchema
3117
+ });
3118
+ z.object({
3119
+ id: z.uuid(),
3120
+ issuer: z.string(),
3121
+ clientId: z.string(),
3122
+ emailDomain: z.string(),
3123
+ domainVerificationToken: z.string(),
3124
+ domainVerified: z.boolean(),
3125
+ enabled: z.boolean(),
3126
+ createdAt: z.iso.datetime(),
3127
+ updatedAt: z.iso.datetime()
3128
+ });
3129
+ z.object({
3130
+ ok: z.boolean(),
3131
+ reason: z.string().optional()
3132
+ });
3133
+ z.object({
3134
+ code: z.literal("integration_requires_plan"),
3135
+ requiredPlans: z.array(z.enum(["team", "enterprise"]))
3136
+ });
3137
+ z.object({
3138
+ ssoAvailable: z.boolean(),
3139
+ tenantId: z.uuid().optional(),
3140
+ tenantName: z.string().optional()
3141
+ });
3142
+ z.object({ code: z.string().min(1, "code gerekli").max(200, "en fazla 200 karakter olabilir") });
3143
+ //#endregion
3144
+ //#region packages/mcp/src/config.ts
3145
+ /**
3146
+ * `npx @vennyx/solicrm-mcp`'nin ortam değişkeninden yapılandırma okuması.
3147
+ *
3148
+ * KRİTİK KURAL 1 — ZORUNLU DEĞER YOKSA AÇIK HATA, VARSAYILANA DÜŞÜLMEZ.
3149
+ * `SOLICRM_API_KEY` ve `SOLICRM_TENANT_ID` ikisi de ZORUNLUDUR ve eksikse
3150
+ * süreç net bir Türkçe mesajla ölür. Sessiz bir varsayılan (`''`, `'default'`)
3151
+ * ilk araç çağrısında anlaşılmaz bir 401/404'e dönüşürdü — bu projenin beş
3152
+ * ayrı yerden çıkmış kusur sınıfı (bkz. kök `CLAUDE.md` "Sessiz kusurlar").
3153
+ *
3154
+ * KRİTİK KURAL 2 — `SOLICRM_TENANT_ID` NEDEN GEREKLİ. SoliCRM'in URL şeması
3155
+ * `/v1/tenants/:tenantId/...`'dir (bkz. `@vennyx/solicrm`'in
3156
+ * `TenantScopedResource`) ve API anahtarının hangi tenant'a bağlı olduğunu
3157
+ * SÖYLEYEN bir uç YOK — anahtarı çözen tek yer sunucunun kendi
3158
+ * `app_resolve_api_key`'idir ve o HTTP'ye açık değildir. Yani istemci URL'i
3159
+ * kuramaz. İleride "anahtarımın tenant'ı ne" ucu eklenirse bu env
3160
+ * GEREKSİZLEŞİR (barındırılan `POST /mcp` ucu zaten tenant'ı anahtardan
3161
+ * çözdüğü için ondan hiçbir env istemez).
3162
+ *
3163
+ * KRİTİK KURAL 3 — `SOLICRM_SCOPE` BİR GÜVENLİK KAPISI DEĞİLDİR, UX
3164
+ * FİLTRESİDİR. Bu değer `tools/list`'in hangi araçları göstereceğini belirler
3165
+ * (bkz. `scope.ts` KRİTİK KURAL 3a: "LLM göremediği aracı denemez"). GERÇEK
3166
+ * yetki sınırı sunucudadır ve HER istekte yeniden hesaplanır
3167
+ * (`ROLE_SCOPE_CEILING[güncel rol] ∩ anahtar kapsamı`,
3168
+ * `apps/api/src/lib/crm-access.ts`). Bu yüzden VERİLMEZSE `admin`'e düşer:
3169
+ * tüm araçlar kaydedilir, yetkisiz olanı sunucu 403'le reddeder. `read`'e
3170
+ * düşmek daha "güvenli" GÖRÜNÜR ama değildir — hiçbir şeyi kapatmaz, yalnız
3171
+ * yazma yetkisi OLAN bir anahtarın yazma araçlarını görünmez yapıp
3172
+ * kullanıcının teşhis edemeyeceği bir arıza üretir. VERİLİRSE ve geçersizse
3173
+ * (`Admin`, `full`, ...) SESSİZCE varsayılana düşülMEZ, açık hata verilir.
3174
+ */
3175
+ /** Bkz. KRİTİK KURAL 3 — kapsam verilmediğinde sunucuyu yetkili taraf sayarız */
3176
+ const DEFAULT_MCP_SCOPE = "admin";
3177
+ function readTrimmed(env, name) {
3178
+ const raw = env[name];
3179
+ if (raw === void 0) return void 0;
3180
+ const trimmed = raw.trim();
3181
+ return trimmed.length === 0 ? void 0 : trimmed;
3182
+ }
3183
+ function loadConfigFromEnv(env) {
3184
+ const apiKey = readTrimmed(env, "SOLICRM_API_KEY");
3185
+ if (apiKey === void 0) throw new Error("SOLICRM_API_KEY ortam değişkeni zorunludur (dashboard → API anahtarları bölümünden oluşturun).");
3186
+ const tenantId = readTrimmed(env, "SOLICRM_TENANT_ID");
3187
+ if (tenantId === void 0) throw new Error("SOLICRM_TENANT_ID ortam değişkeni zorunludur (dashboard → kiracı ayarlarındaki kiracı kimliği).");
3188
+ const rawScope = readTrimmed(env, "SOLICRM_SCOPE");
3189
+ let scope = DEFAULT_MCP_SCOPE;
3190
+ if (rawScope !== void 0) {
3191
+ const parsed = apiKeyScopeSchema.safeParse(rawScope);
3192
+ if (!parsed.success) throw new Error(`SOLICRM_SCOPE geçersiz: "${rawScope}". Geçerli değerler: read, write, admin (boş bırakılırsa ${DEFAULT_MCP_SCOPE}).`);
3193
+ scope = parsed.data;
3194
+ }
3195
+ return {
3196
+ apiKey,
3197
+ tenantId,
3198
+ baseUrl: readTrimmed(env, "SOLICRM_BASE_URL"),
3199
+ scope
3200
+ };
3201
+ }
3202
+ //#endregion
3203
+ //#region packages/mcp/src/prompts.ts
3204
+ function registerSummarizePipelinePrompt(server) {
3205
+ return server.registerPrompt("summarize_pipeline", {
3206
+ title: "Pipeline özeti",
3207
+ description: "Guides an agent through summarising the health of one sales pipeline using the SoliCRM tools.",
3208
+ argsSchema: { pipelineName: z.string().describe("Name of the pipeline to summarise, as shown by list_pipelines") }
3209
+ }, ({ pipelineName }) => ({ messages: [{
3210
+ role: "user",
3211
+ content: {
3212
+ type: "text",
3213
+ text: [
3214
+ `Summarise the health of the SoliCRM pipeline named "${pipelineName}".`,
3215
+ "1. Call list_pipelines and find the pipeline whose name matches; note its id and the ordered stages.",
3216
+ "2. Call list_deals with filters { pipeline_id: \"<that id>\" } and page through with the returned nextCursor until hasMore is false.",
3217
+ "3. Group the deals by stage. For each stage report the count, the summed amount and the oldest deal.",
3218
+ "4. Amounts are decimal STRINGS (numeric(18,2)) — add them with exact decimal arithmetic, never with floating-point math, and keep the currency code with every total.",
3219
+ "5. Close with the two or three stages that look most at risk (largest value sitting in the earliest stages, or deals with the oldest expectedCloseDate)."
3220
+ ].join("\n")
3221
+ }
3222
+ }] }));
3223
+ }
3224
+ //#endregion
3225
+ //#region packages/mcp/src/errors.ts
3226
+ /**
3227
+ * MCP katmanının KENDİ hata sınıfları — sunucudan GELMEYEN, bu paketin
3228
+ * ürettiği başarısızlıklar.
3229
+ *
3230
+ * DİL İSTİSNASI (Global Constraints #8): buradaki mesajlar İngilizce'dir.
3231
+ * "Kullanıcıya dönen mesajlar Türkçe" kuralı `apps/api`'nin `jsonError()`
3232
+ * zarfı içindir; bu paket global npm tüketicilerine (Claude Code/Desktop) gider
3233
+ * ve mesajları bir LLM okur. Sunucudan gelen TÜRKÇE metin ise ÇEVRİLMEZ,
3234
+ * `toMcpToolError` onu AYNEN taşır (bkz. `result.ts`).
3235
+ */
3236
+ /** Bu paketin ürettiği her hatanın taban sınıfı — tüketici tek `catch` ile kapsar. */
3237
+ var SolicrmMcpError = class extends Error {
3238
+ constructor(message) {
3239
+ super(message);
3240
+ this.name = "SolicrmMcpError";
3241
+ Object.setPrototypeOf(this, new.target.prototype);
3242
+ }
3243
+ };
3244
+ /**
3245
+ * Çağıranın etkin kapsamı aracın gerektirdiği minimumun ALTINDA.
3246
+ *
3247
+ * KRİTİK KURAL — bu hata TEK yetki sınırı DEĞİLDİR, İKİNCİ katmandır. Gerçek
3248
+ * yetki kontrolü sunucu tarafındadır: her araç çağrısı SDK üzerinden
3249
+ * `/v1/tenants/:id/...` uçlarına gider ve orada
3250
+ * `checkApiKeyTenantAndScope` (`apps/api/src/lib/crm-access.ts`) çalışır — o
3251
+ * kontrol `effectiveScope`'u (= `ROLE_SCOPE_CEILING[güncel rol] ∩ anahtar
3252
+ * kapsamı`) HER istekte DB'den YENİDEN hesaplar. Bu paketteki `scope` ise
3253
+ * sunucu kurulurken bir kez alınmış bir ANLIK GÖRÜNTÜDÜR (bkz. `scope.ts`).
3254
+ */
3255
+ var McpScopeError = class extends SolicrmMcpError {
3256
+ toolName;
3257
+ requiredScope;
3258
+ actualScope;
3259
+ constructor(params) {
3260
+ super(`Tool "${params.toolName}" requires the "${params.requiredScope}" API key scope, but this key's effective scope is "${params.actualScope}".`);
3261
+ this.name = "McpScopeError";
3262
+ this.toolName = params.toolName;
3263
+ this.requiredScope = params.requiredScope;
3264
+ this.actualScope = params.actualScope;
3265
+ }
3266
+ };
3267
+ /**
3268
+ * Araç girdisi zod şemasını GEÇTİ ama anlamsal olarak tutarsız (ör. sıralama
3269
+ * yönü verilmiş, sıralanacak alan verilmemiş).
3270
+ *
3271
+ * NEDEN AYRI BİR HATA: bu projenin "fail-open savunma savunma değildir"
3272
+ * dersinin doğrudan uygulaması — tutarsız girdiyi SESSİZCE yok saymak
3273
+ * (`sortDirection`'ı görmezden gelip sırasız sonuç döndürmek) çağırana
3274
+ * "istediğin sıralamayı yaptım" yalanını söylerdi.
3275
+ */
3276
+ var McpInputError = class extends SolicrmMcpError {
3277
+ constructor(message) {
3278
+ super(message);
3279
+ this.name = "McpInputError";
3280
+ }
3281
+ };
3282
+ //#endregion
3283
+ //#region packages/mcp/src/scope.ts
3284
+ /**
3285
+ * Araç adı → gereken MİNİMUM coarse kapsam kataloğu, ve iki kapsam katmanının
3286
+ * paylaştığı karşılaştırma.
3287
+ *
3288
+ * KRİTİK KURAL 1 — KATALOG TOTAL, `Record<string, ApiKeyScope>` DEĞİL.
3289
+ * `TOOL_MIN_SCOPE`'un anahtarı `McpToolName` literal union'ıdır, bu yüzden
3290
+ * `TOOL_MIN_SCOPE[name]` HİÇBİR ZAMAN `undefined` olamaz (`noUncheckedIndexedAccess`
3291
+ * altında bile). Plan taslağı `Record<string, ApiKeyScope>` + `if (need !== undefined)`
3292
+ * yazıyordu; o biçimde katalogda UNUTULAN bir araç adı `undefined` verir ve kapı
3293
+ * SESSİZCE HİÇ UYGULANMAZ — projenin "fail-open savunma savunma değildir"
3294
+ * dersinin ta kendisi. Bu biçimde ise yeni bir araç adı eklerken kapsamını
3295
+ * yazmamak DERLEME HATASIDIR.
3296
+ *
3297
+ * KRİTİK KURAL 2 — BU PAKETTEKİ `scope` BİR ANLIK GÖRÜNTÜDÜR, YETKİ SINIRI
3298
+ * DEĞİLDİR. `createSolicrmMcpServer(client, scope)` çağrıldığı anda geçerli olan
3299
+ * etkin kapsam kapanışta (closure) taşınır; süreç yaşarken YENİDEN
3300
+ * HESAPLANMAZ. Gerçek yetki sınırı sunucudadır: her araç çağrısı SDK üzerinden
3301
+ * API'ye gider ve `checkApiKeyTenantAndScope` (`apps/api/src/lib/crm-access.ts`)
3302
+ * `effectiveScope`'u (= `ROLE_SCOPE_CEILING[güncel rol] ∩ anahtar kapsamı`) HER
3303
+ * istekte DB'den yeniden hesaplar — bu yüzden anahtarın sahibi demote edilince
3304
+ * ayrı bir iptal job'ı gerekmiyor. Plan Task 3 Step 4'ün "çağrı-anı kontrolü
3305
+ * gerekli ÇÜNKÜ stdio süreci uzun ömürlü ve sahibin rolü düşürülebilir"
3306
+ * gerekçesi YANLIŞTIR: çağrı-anı kontrolü de AYNI dondurulmuş `scope`'u okur,
3307
+ * demote'u göremez. Çağrı-anı kontrolünün GERÇEK değeri aşağıdaki KRİTİK
3308
+ * KURAL 3'tür.
3309
+ *
3310
+ * KRİTİK KURAL 3 — İKİ KATMAN, İKİ AYRI KUSURA KARŞI. (a) Kayıt-anı
3311
+ * `.disable()` `tools/list`'i filtreler: LLM göremediği bir aracı denemez
3312
+ * (bağlam kirliliği + gereksiz 403 turu yok). (b) Çağrı-anı `assertScope`
3313
+ * BİZİM KENDİ REGRESYONUMUZA karşıdır: `server.ts`'in disable döngüsü
3314
+ * kaldırılır/yanlış sıraya konursa ya da bir araç kayıt listesine eklenip
3315
+ * döngüye girmezse, handler yine reddeder. İkisi bağımsız testlerle sabitlenir.
3316
+ */
3317
+ /**
3318
+ * Kapsam rütbeleri — `apps/api/src/lib/api-key-auth.ts`'in `SCOPE_RANK`'ıyla
3319
+ * BİREBİR aynı. İçe aktarılMAZ çünkü bu paket npm'e yayınlanır ve `apps/api`'ye
3320
+ * (Hono, Drizzle, Postgres) bağımlı OLAMAZ. Drift riski TİP SEVİYESİNDE
3321
+ * kapatılmıştır: `Record<ApiKeyScope, number>` `@solicrm/contracts`'ın
3322
+ * `apiKeyScopeSchema` enum'una bağlıdır ⇒ contracts'a dördüncü bir kapsam
3323
+ * eklenirse bu satır DERLENMEZ.
3324
+ */
3325
+ const SCOPE_RANK = {
3326
+ read: 0,
3327
+ write: 1,
3328
+ admin: 2
3329
+ };
3330
+ apiKeyScopeSchema.options;
3331
+ const TOOL_MIN_SCOPE = {
3332
+ list_contacts: "read",
3333
+ get_contact: "read",
3334
+ create_contact: "write",
3335
+ update_contact: "write",
3336
+ list_companies: "read",
3337
+ get_company: "read",
3338
+ create_company: "write",
3339
+ update_company: "write",
3340
+ list_deals: "read",
3341
+ get_deal: "read",
3342
+ create_deal: "write",
3343
+ update_deal: "write",
3344
+ move_deal_stage: "write",
3345
+ list_pipelines: "read",
3346
+ list_activities: "read",
3347
+ create_activity: "write",
3348
+ list_tasks: "read",
3349
+ create_task: "write",
3350
+ complete_task: "write",
3351
+ create_note: "write",
3352
+ search: "read",
3353
+ list_saved_views: "read"
3354
+ };
3355
+ /** `actual`, `required` kapsamını karşılıyor mu (read < write < admin) */
3356
+ function scopeSatisfies(actual, required) {
3357
+ return SCOPE_RANK[actual] >= SCOPE_RANK[required];
3358
+ }
3359
+ /**
3360
+ * Çağrı-anı kapsam kapısı — HER araç handler'ının İLK satırı (bkz. KRİTİK
3361
+ * KURAL 3/b). Yetersizse fırlatır; `toMcpToolError` bunu `isError: true`
3362
+ * sonucuna çevirir.
3363
+ */
3364
+ function assertScope(scope, toolName) {
3365
+ const required = TOOL_MIN_SCOPE[toolName];
3366
+ if (!scopeSatisfies(scope, required)) throw new McpScopeError({
3367
+ toolName,
3368
+ requiredScope: required,
3369
+ actualScope: scope
3370
+ });
3371
+ }
3372
+ //#endregion
3373
+ //#region packages/mcp/src/resources.ts
3374
+ /** Kaynak → gereken minimum kapsam. `TOOL_MIN_SCOPE` gibi TOTAL (fail-open yok). */
3375
+ const RESOURCE_MIN_SCOPE = {
3376
+ contact: "read",
3377
+ deal: "read"
3378
+ };
3379
+ /** URI şablonu değişkeni tek değer de dizi de olabilir — ilkini alır, yoksa hata. */
3380
+ function firstVariable(value, name) {
3381
+ const first = Array.isArray(value) ? value[0] : value;
3382
+ if (first === void 0 || first.length === 0) throw new McpInputError(`Resource URI is missing the "${name}" segment.`);
3383
+ return first;
3384
+ }
3385
+ function assertResourceScope(scope, name) {
3386
+ const required = RESOURCE_MIN_SCOPE[name];
3387
+ if (!scopeSatisfies(scope, required)) throw new McpInputError(`Resource "${name}" requires the "${required}" API key scope, but this key's effective scope is "${scope}".`);
3388
+ }
3389
+ function registerCrmResources(server, client, scope) {
3390
+ return [["contact", server.registerResource("contact", new ResourceTemplate("solicrm://contacts/{id}", { list: void 0 }), {
3391
+ title: "SoliCRM kişisi",
3392
+ mimeType: "application/json"
3393
+ }, async (uri, variables) => {
3394
+ assertResourceScope(scope, "contact");
3395
+ const contact = await client.contacts.get(firstVariable(variables.id, "id"));
3396
+ return { contents: [{
3397
+ uri: uri.href,
3398
+ mimeType: "application/json",
3399
+ text: JSON.stringify(contact, null, 2)
3400
+ }] };
3401
+ })], ["deal", server.registerResource("deal", new ResourceTemplate("solicrm://deals/{id}", { list: void 0 }), {
3402
+ title: "SoliCRM fırsatı",
3403
+ mimeType: "application/json"
3404
+ }, async (uri, variables) => {
3405
+ assertResourceScope(scope, "deal");
3406
+ const deal = await client.deals.get(firstVariable(variables.id, "id"));
3407
+ return { contents: [{
3408
+ uri: uri.href,
3409
+ mimeType: "application/json",
3410
+ text: JSON.stringify(deal, null, 2)
3411
+ }] };
3412
+ })]];
3413
+ }
3414
+ //#endregion
3415
+ //#region packages/mcp/src/result.ts
3416
+ /** Beklenmeyen (bizim kusurumuz olan) hatalar için TEK jenerik metin. */
3417
+ const UNEXPECTED_ERROR_MESSAGE = "An unexpected error occurred while calling the SoliCRM API.";
3418
+ /**
3419
+ * SDK'nın hata sınıflarının `this.name` değerlerinin KAPALI listesi. Listede
3420
+ * OLMAYAN her şey jenerik metne düşer (fail-closed).
3421
+ */
3422
+ const SDK_ERROR_NAMES = [
3423
+ "SolicrmError",
3424
+ "SolicrmApiError",
3425
+ "SolicrmResponseError"
3426
+ ];
3427
+ /** Her aracın ORTAK başarı biçimi — veriyi okunabilir (girintili) JSON metne çevirir. */
3428
+ function jsonResult(data) {
3429
+ return { content: [{
3430
+ type: "text",
3431
+ text: JSON.stringify(data, null, 2)
3432
+ }] };
3433
+ }
3434
+ /** Bir hatayı MCP'nin `isError: true` sonucuna çevirir (JSON-RPC hatası DEĞİL). */
3435
+ function toMcpToolError(err) {
3436
+ return {
3437
+ isError: true,
3438
+ content: [{
3439
+ type: "text",
3440
+ text: describeError(err)
3441
+ }]
3442
+ };
3443
+ }
3444
+ /** Ham gövdeyi + status'ü koruyan API hatası okuması; hata bu şekilde değilse `undefined`. */
3445
+ function readApiError(err) {
3446
+ if (!(err instanceof Error) || err.name !== "SolicrmApiError") return void 0;
3447
+ if (!("status" in err) || typeof err.status !== "number") return void 0;
3448
+ if (!("body" in err) || !("serverMessage" in err)) return void 0;
3449
+ const serverMessage = err.serverMessage;
3450
+ return {
3451
+ status: err.status,
3452
+ body: err.body,
3453
+ serverMessage: typeof serverMessage === "string" ? serverMessage : void 0,
3454
+ message: err.message
3455
+ };
3456
+ }
3457
+ function describeError(err) {
3458
+ if (err instanceof SolicrmMcpError) return err.message;
3459
+ const apiError = readApiError(err);
3460
+ if (apiError !== void 0) return JSON.stringify({
3461
+ status: apiError.status,
3462
+ message: apiError.serverMessage ?? apiError.message,
3463
+ body: apiError.body
3464
+ }, null, 2);
3465
+ if (err instanceof Error && SDK_ERROR_NAMES.includes(err.name)) return err.message;
3466
+ return UNEXPECTED_ERROR_MESSAGE;
3467
+ }
3468
+ //#endregion
3469
+ //#region packages/mcp/src/tools/define.ts
3470
+ /**
3471
+ * Bir araç handler'ını MCP callback'ine çevirir: kapsam kapısı → handler →
3472
+ * `jsonResult`; her fırlatma `isError: true` sonucuna dönüşür (JSON-RPC
3473
+ * hatasına DEĞİL — LLM hatayı okuyup düzeltebilsin).
3474
+ */
3475
+ function guarded(scope, name, handler) {
3476
+ return async (args) => {
3477
+ try {
3478
+ assertScope(scope, name);
3479
+ return jsonResult(await handler(args));
3480
+ } catch (err) {
3481
+ return toMcpToolError(err);
3482
+ }
3483
+ };
3484
+ }
3485
+ /** Girdi almayan araçlar için (`list_pipelines`) — aynı üç garanti, argümansız. */
3486
+ function guardedNoArgs(scope, name, handler) {
3487
+ return async () => {
3488
+ try {
3489
+ assertScope(scope, name);
3490
+ return jsonResult(await handler());
3491
+ } catch (err) {
3492
+ return toMcpToolError(err);
3493
+ }
3494
+ };
3495
+ }
3496
+ //#endregion
3497
+ //#region packages/mcp/src/tools/ids.ts
3498
+ /**
3499
+ * Araç girdilerindeki kimlik (id) alanlarının TEK tanımı. `@solicrm/contracts`
3500
+ * kaynak gövdelerinde `z.uuid('geçerli bir <şey> kimliği olmalı')` kalıbını
3501
+ * kullanıyor; yol parametreleri için bir sözleşme şeması YOK (uçlar onları yol
3502
+ * segmentinden okur), bu yüzden aynı kalıp burada tek yerde kurulur — 8 araç
3503
+ * dosyasında tekrarlanmaz.
3504
+ */
3505
+ /** `label` Türkçe okunur bir isimdir ("kişi", "şirket", "fırsat"...) */
3506
+ function idField(label) {
3507
+ return z.uuid(`geçerli bir ${label} kimliği olmalı`);
3508
+ }
3509
+ //#endregion
3510
+ //#region packages/mcp/src/tools/activities.ts
3511
+ const listActivitiesInputSchema = pageQuerySchema.extend({
3512
+ targetType: targetTypeSchema.optional(),
3513
+ targetId: idField("hedef").optional()
3514
+ });
3515
+ function toActivityListQuery(args) {
3516
+ if (args.targetType !== void 0 !== (args.targetId !== void 0)) throw new McpInputError("targetType and targetId must be provided together. Send both to filter by a target, or neither to list every activity.");
3517
+ const query = {
3518
+ cursor: args.cursor,
3519
+ limit: args.limit
3520
+ };
3521
+ if (args.targetType !== void 0 && args.targetId !== void 0) query.target = {
3522
+ type: args.targetType,
3523
+ id: args.targetId
3524
+ };
3525
+ return query;
3526
+ }
3527
+ function registerActivityTools(server, client, scope) {
3528
+ return [["list_activities", server.registerTool("list_activities", {
3529
+ title: "Aktiviteleri listele",
3530
+ description: "Lists activities, cursor-paginated. Pass targetType AND targetId together to see one record’s timeline.",
3531
+ inputSchema: listActivitiesInputSchema
3532
+ }, guarded(scope, "list_activities", (args) => client.activities.list(toActivityListQuery(args))))], ["create_activity", server.registerTool("create_activity", {
3533
+ title: "Aktivite oluştur",
3534
+ description: "Logs an activity (call, meeting, email, note...) against a contact, company or deal. `happensAt` is an ISO 8601 UTC timestamp.",
3535
+ inputSchema: createActivityRequestSchema
3536
+ }, guarded(scope, "create_activity", (args) => client.activities.create(args)))]];
3537
+ }
3538
+ //#endregion
3539
+ //#region packages/mcp/src/list-input.ts
3540
+ /**
3541
+ * CRM liste araçlarının (`list_contacts`/`list_companies`/`list_deals`/
3542
+ * `list_tasks`) PAYLAŞTIĞI girdi şeması ve SDK sorgusuna çevirisi.
3543
+ *
3544
+ * KRİTİK KURAL 1 — ALAN KATALOĞU KOPYALANMAZ. Geçerli `sortField`/`filters`
3545
+ * anahtarları `@solicrm/contracts`'ın `FIELD_CATALOG`'undan ÇALIŞMA ZAMANINDA
3546
+ * okunur (araç açıklaması bile oradan üretilir, bkz. `describeCrmListFields`);
3547
+ * bu dosyada tek bir alan adı literali YOKTUR. Doğrulamanın kendisi de burada
3548
+ * TEKRARLANMAZ — SDK'nın `buildCrmListQuery`'si `resolveField` + `sortable`/
3549
+ * `filterable` bayrak kontrolünü zaten yapıyor ve bilinmeyen/uygun olmayan
3550
+ * alanda `SolicrmError` fırlatıyor (fail-closed).
3551
+ *
3552
+ * KRİTİK KURAL 2 — SAYFA SINIRLARI DA KOPYALANMAZ. `cursor`/`limit`
3553
+ * `pageQuerySchema`'dan `.extend()` ile MİRAS ALINIR ⇒ `PAGE_MAX_LIMIT`
3554
+ * değişirse araç şeması da değişir. `z.coerce.number()`'ın JSON Schema
3555
+ * karşılığının `{"type":"integer","minimum":1,"maximum":100}` olduğu canlı
3556
+ * ölçüldü — MCP tarafında sorun ÜRETMİYOR.
3557
+ *
3558
+ * KRİTİK KURAL 3 — `sortDirection` VERİLİP `sortField` VERİLMEZSE SESSİZCE
3559
+ * YOK SAYILMAZ, HATA VERİLİR (`McpInputError`). "Tanımadığım/eksik girdiyi
3560
+ * görmezden gel" bir fail-open kalıbıdır: çağıran sıralama istediğini sanır,
3561
+ * sırasız sonuç alır.
3562
+ *
3563
+ * KRİTİK KURAL 4 — ALAN ADLARI KANONİK KATALOĞA KARŞI DOĞRULANIR, DESENE
3564
+ * KARŞI DEĞİL. İlk uygulama `propertyNames` deseni (`^[a-z][a-z0-9_]*$`)
3565
+ * kullanıyor ve JSDoc'u "`__proto__`/`constructor` şema seviyesinde
3566
+ * reddedilir" diye İDDİA ediyordu. **Ölçüm bu iddiayı ÇÜRÜTTÜ:** `constructor`
3567
+ * deseni SAĞLIYOR (tamamı küçük harf) ⇒ belgelenen koruma YOKTU. Bu, bu
3568
+ * deponun "belgelenen koruma hiç çağrılmamış olabilir" kusur sınıfının ta
3569
+ * kendisi. Düzeltme: anahtarlar `resolveField(resource, key)` ile doğrulanır;
3570
+ * o fonksiyon `Object.hasOwn` allowlist'i olduğu için prototip zincirinden
3571
+ * gelen hiçbir ad kolon adına dönüşemez. Bu yüzden şema artık bir FABRİKADIR
3572
+ * — kaynağı bilmeden doğrulama yapılamaz.
3573
+ *
3574
+ * `__proto__` İSTİSNASI, DÜRÜST HÂLİ (ölçüldü): `zod@4.4.3`'ün `z.record`'u
3575
+ * `JSON.parse` ile gelen own `__proto__` anahtarını **doğrulamadan SESSİZCE
3576
+ * ATAR** — `safeParse` `success: true` döner ama çıktı `{}`'dir, yani anahtar
3577
+ * `refine`'a HİÇ ULAŞMAZ. Sonuç güvenlik açısından güvenlidir (anahtar
3578
+ * `buildCrmListQuery`'ye ve dolayısıyla SQL'e hiç geçmez) ama bir REDDETME
3579
+ * DEĞİL, bir DÜŞÜRMEDİR; `test/list-input.test.ts` bu ayrımı olduğu gibi
3580
+ * sabitler. Diğer prototip adları (`constructor`, `hasOwnProperty`,
3581
+ * `toString`, `valueOf`, `prototype`) GERÇEKTEN reddedilir.
3582
+ *
3583
+ * BAYRAK KONTROLÜ BURADA TEKRARLANMAZ: `sortable`/`filterable` denetimi
3584
+ * SDK'nın `buildCrmListQuery`'sinde yaşar (TEK sahip). Buradaki kontrol yalnız
3585
+ * "bu kaynakta böyle bir alan VAR MI" sorusunu yanıtlar.
3586
+ */
3587
+ /**
3588
+ * Bir CRM kaynağı için liste aracı girdi şeması. Alan adları o kaynağın
3589
+ * `FIELD_CATALOG` girdisine karşı doğrulanır (bkz. KRİTİK KURAL 4).
3590
+ */
3591
+ function buildCrmListInputSchema(resource) {
3592
+ const knownField = (key) => resolveField(resource, key) !== void 0;
3593
+ const unknownFieldMessage = `${resource} kaynağında böyle bir alan yok`;
3594
+ return pageQuerySchema.extend({
3595
+ sortField: z.string().trim().refine(knownField, unknownFieldMessage).optional(),
3596
+ sortDirection: sortDirectionSchema.optional(),
3597
+ filters: z.record(z.string().trim().refine(knownField, unknownFieldMessage), z.string().max(500, "en fazla 500 karakter olabilir")).optional()
3598
+ });
3599
+ }
3600
+ /**
3601
+ * Araç girdisini SDK'nın `CrmListQuery`'sine çevirir. `tenantId` DİYE BİR ALAN
3602
+ * YOKTUR ve olamaz — tenant her zaman `SolicrmClient`'ın kurulduğu andaki
3603
+ * değerdir (bkz. `server.ts` dosya başı).
3604
+ */
3605
+ function toCrmListQuery(args) {
3606
+ if (args.sortField === void 0 && args.sortDirection !== void 0) throw new McpInputError("sortDirection was given without sortField. Provide the field to sort by, or omit both.");
3607
+ const query = {
3608
+ cursor: args.cursor,
3609
+ limit: args.limit,
3610
+ filters: args.filters
3611
+ };
3612
+ if (args.sortField !== void 0) query.sort = {
3613
+ field: args.sortField,
3614
+ direction: args.sortDirection ?? "asc"
3615
+ };
3616
+ return query;
3617
+ }
3618
+ /**
3619
+ * Bir kaynağın sıralanabilir/filtrelenebilir alanlarını araç açıklamasına
3620
+ * yazılacak metne çevirir — LLM'in geçersiz alan adı denemesini azaltır.
3621
+ * Liste `FIELD_CATALOG`'dan ÜRETİLİR, elle yazılmaz.
3622
+ */
3623
+ function describeCrmListFields(resource) {
3624
+ const catalog = FIELD_CATALOG[resource];
3625
+ const sortable = [];
3626
+ const filterable = [];
3627
+ for (const [key, def] of Object.entries(catalog)) {
3628
+ if (def.sortable) sortable.push(key);
3629
+ if (def.filterable) filterable.push(key);
3630
+ }
3631
+ return `Sortable fields: ${sortable.join(", ")}. Filterable fields: ${filterable.join(", ")}.`;
3632
+ }
3633
+ //#endregion
3634
+ //#region packages/mcp/src/tools/companies.ts
3635
+ const listCompaniesInputSchema = buildCrmListInputSchema("companies");
3636
+ const getCompanyInputSchema = z.object({ companyId: idField("şirket") });
3637
+ const updateCompanyInputSchema = updateCompanyRequestSchema.extend({ companyId: idField("şirket") });
3638
+ function registerCompanyTools(server, client, scope) {
3639
+ return [
3640
+ ["list_companies", server.registerTool("list_companies", {
3641
+ title: "Şirketleri listele",
3642
+ description: `Lists companies in the tenant, cursor-paginated. ${describeCrmListFields("companies")}`,
3643
+ inputSchema: listCompaniesInputSchema
3644
+ }, guarded(scope, "list_companies", (args) => client.companies.list(toCrmListQuery(args))))],
3645
+ ["get_company", server.registerTool("get_company", {
3646
+ title: "Şirket getir",
3647
+ description: "Fetches a single company by id.",
3648
+ inputSchema: getCompanyInputSchema
3649
+ }, guarded(scope, "get_company", (args) => client.companies.get(args.companyId)))],
3650
+ ["create_company", server.registerTool("create_company", {
3651
+ title: "Şirket oluştur",
3652
+ description: "Creates a company. `annualRevenue` is a decimal STRING (e.g. \"1250000.00\"), never a JSON number — the API stores it as numeric(18,2).",
3653
+ inputSchema: createCompanyRequestSchema
3654
+ }, guarded(scope, "create_company", (args) => client.companies.create(args)))],
3655
+ ["update_company", server.registerTool("update_company", {
3656
+ title: "Şirketi güncelle",
3657
+ description: "Updates a company. Only the fields you send are changed.",
3658
+ inputSchema: updateCompanyInputSchema
3659
+ }, guarded(scope, "update_company", ({ companyId, ...body }) => client.companies.update(companyId, body)))]
3660
+ ];
3661
+ }
3662
+ //#endregion
3663
+ //#region packages/mcp/src/tools/contacts.ts
3664
+ const listContactsInputSchema = buildCrmListInputSchema("contacts");
3665
+ const getContactInputSchema = z.object({ contactId: idField("kişi") });
3666
+ const updateContactInputSchema = updateContactRequestSchema.extend({ contactId: idField("kişi") });
3667
+ function registerContactTools(server, client, scope) {
3668
+ return [
3669
+ ["list_contacts", server.registerTool("list_contacts", {
3670
+ title: "Kişileri listele",
3671
+ description: `Lists contacts in the tenant, cursor-paginated. ${describeCrmListFields("contacts")}`,
3672
+ inputSchema: listContactsInputSchema
3673
+ }, guarded(scope, "list_contacts", (args) => client.contacts.list(toCrmListQuery(args))))],
3674
+ ["get_contact", server.registerTool("get_contact", {
3675
+ title: "Kişi getir",
3676
+ description: "Fetches a single contact by id.",
3677
+ inputSchema: getContactInputSchema
3678
+ }, guarded(scope, "get_contact", (args) => client.contacts.get(args.contactId)))],
3679
+ ["create_contact", server.registerTool("create_contact", {
3680
+ title: "Kişi oluştur",
3681
+ description: "Creates a contact. Counts against the tenant plan limit — a 402 result carries the limit and plan code in its body.",
3682
+ inputSchema: createContactRequestSchema
3683
+ }, guarded(scope, "create_contact", (args) => client.contacts.create(args)))],
3684
+ ["update_contact", server.registerTool("update_contact", {
3685
+ title: "Kişiyi güncelle",
3686
+ description: "Updates a contact. Only the fields you send are changed.",
3687
+ inputSchema: updateContactInputSchema
3688
+ }, guarded(scope, "update_contact", ({ contactId, ...body }) => client.contacts.update(contactId, body)))]
3689
+ ];
3690
+ }
3691
+ //#endregion
3692
+ //#region packages/mcp/src/tools/deals.ts
3693
+ const listDealsInputSchema = buildCrmListInputSchema("deals");
3694
+ const getDealInputSchema = z.object({ dealId: idField("fırsat") });
3695
+ const updateDealInputSchema = updateDealRequestSchema.extend({ dealId: idField("fırsat") });
3696
+ const moveDealInputSchema = moveDealRequestSchema.extend({ dealId: idField("fırsat") });
3697
+ function registerDealTools(server, client, scope) {
3698
+ return [
3699
+ ["list_deals", server.registerTool("list_deals", {
3700
+ title: "Fırsatları listele",
3701
+ description: `Lists deals in the tenant, cursor-paginated. ${describeCrmListFields("deals")}`,
3702
+ inputSchema: listDealsInputSchema
3703
+ }, guarded(scope, "list_deals", (args) => client.deals.list(toCrmListQuery(args))))],
3704
+ ["get_deal", server.registerTool("get_deal", {
3705
+ title: "Fırsat getir",
3706
+ description: "Fetches a single deal by id, including its stage and linked contacts.",
3707
+ inputSchema: getDealInputSchema
3708
+ }, guarded(scope, "get_deal", (args) => client.deals.get(args.dealId)))],
3709
+ ["create_deal", server.registerTool("create_deal", {
3710
+ title: "Fırsat oluştur",
3711
+ description: "Creates a deal in a pipeline stage. `amount` is a decimal STRING (e.g. \"45000.00\"), never a JSON number. Call list_pipelines first to get valid pipelineId/stageId values.",
3712
+ inputSchema: createDealRequestSchema
3713
+ }, guarded(scope, "create_deal", (args) => client.deals.create(args)))],
3714
+ ["update_deal", server.registerTool("update_deal", {
3715
+ title: "Fırsatı güncelle",
3716
+ description: "Updates a deal. Stage/pipeline changes are rejected here by design — use move_deal_stage instead.",
3717
+ inputSchema: updateDealInputSchema
3718
+ }, guarded(scope, "update_deal", ({ dealId, ...body }) => client.deals.update(dealId, body)))],
3719
+ ["move_deal_stage", server.registerTool("move_deal_stage", {
3720
+ title: "Fırsatı aşamaya taşı",
3721
+ description: "Moves a deal to another stage (kanban). Optional beforeId/afterId place it between two neighbouring deals; omit both to append.",
3722
+ inputSchema: moveDealInputSchema
3723
+ }, guarded(scope, "move_deal_stage", ({ dealId, ...body }) => client.deals.move(dealId, body)))]
3724
+ ];
3725
+ }
3726
+ //#endregion
3727
+ //#region packages/mcp/src/tools/notes.ts
3728
+ function registerNoteTools(server, client, scope) {
3729
+ return [["create_note", server.registerTool("create_note", {
3730
+ title: "Not oluştur",
3731
+ description: "Creates a note. `body` is a free-form JSON object (rich text document). NOTE: the note is created unlinked — it will not appear on any contact/company/deal timeline until it is attached, which this MCP server does not expose.",
3732
+ inputSchema: createNoteRequestSchema
3733
+ }, guarded(scope, "create_note", (args) => client.notes.create(args)))]];
3734
+ }
3735
+ //#endregion
3736
+ //#region packages/mcp/src/tools/pipelines.ts
3737
+ function registerPipelineTools(server, client, scope) {
3738
+ return [["list_pipelines", server.registerTool("list_pipelines", {
3739
+ title: "Pipeline’ları listele",
3740
+ description: "Lists every pipeline with its ordered stages. Use this to resolve pipelineId/stageId before creating or moving a deal."
3741
+ }, guardedNoArgs(scope, "list_pipelines", () => client.pipelines.list()))]];
3742
+ }
3743
+ //#endregion
3744
+ //#region packages/mcp/src/tools/search.ts
3745
+ const searchInputSchema = z.object({
3746
+ q: searchQuerySchema.shape.q,
3747
+ limit: searchQuerySchema.shape.limit,
3748
+ resources: z.array(searchableResourceSchema).min(1, "en az bir kaynak belirtilmeli").optional()
3749
+ });
3750
+ function toSearchParams(args) {
3751
+ return {
3752
+ q: args.q,
3753
+ limit: args.limit,
3754
+ resources: args.resources
3755
+ };
3756
+ }
3757
+ function registerSearchTools(server, client, scope) {
3758
+ return [["search", server.registerTool("search", {
3759
+ title: "Çapraz arama",
3760
+ description: "Full-text search across contacts, companies and deals in one call. Returns the best matches only (single page, no cursor). Omit `resources` to search all three.",
3761
+ inputSchema: searchInputSchema
3762
+ }, guarded(scope, "search", (args) => client.search.query(toSearchParams(args))))]];
3763
+ }
3764
+ //#endregion
3765
+ //#region packages/mcp/src/tools/tasks.ts
3766
+ const listTasksInputSchema = buildCrmListInputSchema("tasks");
3767
+ const completeTaskInputSchema = z.object({ taskId: idField("görev") });
3768
+ function registerTaskTools(server, client, scope) {
3769
+ return [
3770
+ ["list_tasks", server.registerTool("list_tasks", {
3771
+ title: "Görevleri listele",
3772
+ description: `Lists tasks in the tenant, cursor-paginated. ${describeCrmListFields("tasks")}`,
3773
+ inputSchema: listTasksInputSchema
3774
+ }, guarded(scope, "list_tasks", (args) => client.tasks.list(toCrmListQuery(args))))],
3775
+ ["create_task", server.registerTool("create_task", {
3776
+ title: "Görev oluştur",
3777
+ description: "Creates a task. NOTE: the task is created unlinked — it will not appear on any contact/company/deal timeline until it is attached, which this MCP server does not expose. `dueAt` is an ISO 8601 UTC timestamp.",
3778
+ inputSchema: createTaskRequestSchema
3779
+ }, guarded(scope, "create_task", (args) => client.tasks.create(args)))],
3780
+ ["complete_task", server.registerTool("complete_task", {
3781
+ title: "Görevi tamamla",
3782
+ description: "Marks a task as completed and returns the updated task.",
3783
+ inputSchema: completeTaskInputSchema
3784
+ }, guarded(scope, "complete_task", (args) => client.tasks.complete(args.taskId)))]
3785
+ ];
3786
+ }
3787
+ //#endregion
3788
+ //#region packages/mcp/src/tools/views.ts
3789
+ const listSavedViewsInputSchema = z.object({ resource: savedViewResourceSchema.optional() });
3790
+ function registerSavedViewTools(server, client, scope) {
3791
+ return [["list_saved_views", server.registerTool("list_saved_views", {
3792
+ title: "Kaydedilmiş görünümleri listele",
3793
+ description: "Lists saved views (the tenant’s stored filter/sort presets) with their fields, sorts and filters. Optionally narrow to one CRM resource.",
3794
+ inputSchema: listSavedViewsInputSchema
3795
+ }, guarded(scope, "list_saved_views", (args) => client.views.list(args)))]];
3796
+ }
3797
+ //#endregion
3798
+ //#region packages/mcp/src/tools/index.ts
3799
+ function registerAllTools(server, client, scope) {
3800
+ return [
3801
+ ...registerContactTools(server, client, scope),
3802
+ ...registerCompanyTools(server, client, scope),
3803
+ ...registerDealTools(server, client, scope),
3804
+ ...registerPipelineTools(server, client, scope),
3805
+ ...registerActivityTools(server, client, scope),
3806
+ ...registerTaskTools(server, client, scope),
3807
+ ...registerNoteTools(server, client, scope),
3808
+ ...registerSearchTools(server, client, scope),
3809
+ ...registerSavedViewTools(server, client, scope)
3810
+ ];
3811
+ }
3812
+ //#endregion
3813
+ //#region packages/mcp/src/server.ts
3814
+ /**
3815
+ * SoliCRM ürün MCP sunucusu — 22 araç, 2 kaynak, 1 prompt. TRANSPORT-AGNOSTİK:
3816
+ * bu dosya ne HTTP ne stdio bilir; çağıran (Task 4) `server.connect(transport)`
3817
+ * ile bir taşımaya bağlar.
3818
+ *
3819
+ * KRİTİK KURAL 1 — TENANT HER ZAMAN `SolicrmClient`'IN KURULDUĞU ANDAKİ
3820
+ * DEĞERDİR, HİÇBİR ARAÇ GİRDİSİNDEN GELMEZ. Bu iddia üç bağımsız mekanizmayla
3821
+ * ayakta duruyor ve üçü de ölçülmüş durumda:
3822
+ * (a) Hiçbir araç girdi şemasında `tenantId` alanı TANIMLI DEĞİL, ve
3823
+ * `test/server.test.ts` `tools/list`'in ÜRETTİĞİ JSON Schema'ları
3824
+ * tarayarak bunu her araç için doğruluyor (gelecekte eklenen bir araç da
3825
+ * yakalanır).
3826
+ * (b) MCP SDK girdiyi zod ile ayrıştırır ve zod obje şemaları BİLİNMEYEN
3827
+ * ANAHTARLARI ATAR (canlı ölçüldü: `{firstName, lastName, tenantId:
3828
+ * 'ATTACKER-TENANT'}` → handler `{firstName, lastName, status:'lead'}`
3829
+ * görüyor) ⇒ bir LLM `tenantId` uydursa bile handler'a hiç ULAŞMAZ.
3830
+ * (c) Araç handler'ları argümanları SDK'nın `CallOptions` (`{ tenantId }`)
3831
+ * parametresine GEÇİRMEZ — `guarded()`'ın imzası öyle bir kanal
3832
+ * sunmuyor (bkz. `tools/define.ts` KRİTİK KURAL 1).
3833
+ * `[H4]` kanıtı `test/tenant-isolation.test.ts`'te: şemaya GEÇİCİ bir
3834
+ * `tenantId` alanı eklenip (yani (b) devre dışı bırakılıp) isteğin YİNE
3835
+ * client'ın tenant'ına gittiği ölçülüyor.
3836
+ *
3837
+ * KRİTİK KURAL 2 — STATELESS: her `createSolicrmMcpServer` çağrısı KENDİ
3838
+ * kapanışını kurar. Paylaşılan mutable state, modül düzeyi cache veya
3839
+ * `reset*()` gerektiren bir singleton YOKTUR ⇒ eşzamanlı isteklerde yarış
3840
+ * yapısal olarak imkânsız. Bu pakete cache EKLENMEZ; gerekirse önce
3841
+ * "ilk dolum yarışı + hata sonrası zehirlenme" sorularının ikisi de
3842
+ * yanıtlanır (bkz. kök `CLAUDE.md` "Yeni cache = iki zorunlu soru").
3843
+ *
3844
+ * KRİTİK KURAL 3 — KAPSAM İKİ KATMANLIDIR, ama İKİSİ DE SUNUCUNUN YERİNE
3845
+ * GEÇMEZ. Ayrıntı ve dürüst sınırlar için bkz. `scope.ts` KRİTİK KURAL 2/3.
3846
+ */
3847
+ /** `dist/cli.js`'nin (Task 4) `package.json` okumadan taşıdığı sürüm sabiti. */
3848
+ const SOLICRM_MCP_VERSION = "0.1.0";
3849
+ const SOLICRM_MCP_SERVER_NAME = "solicrm";
3850
+ /**
3851
+ * Verilen istemci ve etkin kapsam için TAZE bir `McpServer` kurar.
3852
+ *
3853
+ * @param client Kurulmuş `SolicrmClient` — `tenantId`'si (ve API anahtarı)
3854
+ * çağıranın kimliğinden gelir; bu fonksiyon onu SORGULAMAZ ve DEĞİŞTİRMEZ.
3855
+ * @param scope Çağıranın ŞU ANDAKİ etkin kapsamı (`ROLE_SCOPE_CEILING[rol] ∩
3856
+ * anahtar kapsamı`). Anlık görüntüdür — bkz. `scope.ts` KRİTİK KURAL 2.
3857
+ */
3858
+ function createSolicrmMcpServer(client, scope) {
3859
+ const server = new McpServer({
3860
+ name: SOLICRM_MCP_SERVER_NAME,
3861
+ version: SOLICRM_MCP_VERSION
3862
+ });
3863
+ const toolHandles = registerAllTools(server, client, scope);
3864
+ const resourceHandles = registerCrmResources(server, client, scope);
3865
+ registerSummarizePipelinePrompt(server);
3866
+ for (const [name, handle] of toolHandles) if (!scopeSatisfies(scope, TOOL_MIN_SCOPE[name])) handle.disable();
3867
+ for (const [name, handle] of resourceHandles) if (!scopeSatisfies(scope, RESOURCE_MIN_SCOPE[name])) handle.disable();
3868
+ return server;
3869
+ }
3870
+ const DEFAULT_HTTP_PORT = 3737;
3871
+ /** `--http` sunucusunun MCP yolu */
3872
+ const MCP_HTTP_PATH = "/mcp";
3873
+ const BROWSER_ORIGIN_REJECTED_MESSAGE = "Tarayıcıdan gelen istekler reddedilir (Origin başlığı taşıyor).";
3874
+ const METHOD_NOT_ALLOWED_MESSAGE = "Bu uçta yalnız POST desteklenir (stateless MCP).";
3875
+ const NOT_FOUND_MESSAGE = `Yalnız POST ${MCP_HTTP_PATH} desteklenir.`;
3876
+ function jsonResponse(status, body, headers = {}) {
3877
+ return new Response(JSON.stringify(body), {
3878
+ status,
3879
+ headers: {
3880
+ "content-type": "application/json",
3881
+ ...headers
3882
+ }
3883
+ });
3884
+ }
3885
+ /** Bkz. KRİTİK KURAL 1(b) — saf, tek satırlık, ayrı test edilebilir karar */
3886
+ function hasBrowserOrigin(request) {
3887
+ const origin = request.headers.get("origin");
3888
+ return origin !== null && origin.trim().length > 0;
3889
+ }
3890
+ /**
3891
+ * `Request → Response`. Port açmaz, soket kurmaz ⇒ testte doğrudan çağrılır.
3892
+ * Protokol hataları JSON-RPC zarfıyla transport'tan gelir; BU katmanın kendi
3893
+ * redleri (405/404/403) `{error}` zarfını kullanır — hosted uçtaki AYNI ayrım
3894
+ * (bkz. `apps/api/src/routes/mcp.ts` KRİTİK KURAL 5).
3895
+ */
3896
+ function createMcpHttpHandler(deps) {
3897
+ return async (request) => {
3898
+ if (hasBrowserOrigin(request)) return jsonResponse(403, { error: BROWSER_ORIGIN_REJECTED_MESSAGE });
3899
+ const { pathname } = new URL(request.url);
3900
+ if (pathname !== "/mcp") return jsonResponse(404, { error: NOT_FOUND_MESSAGE });
3901
+ if (request.method !== "POST") return jsonResponse(405, { error: METHOD_NOT_ALLOWED_MESSAGE }, { Allow: "POST" });
3902
+ const server = deps.createServer();
3903
+ const transport = new WebStandardStreamableHTTPServerTransport({ enableJsonResponse: true });
3904
+ await server.connect(transport);
3905
+ return await transport.handleRequest(request);
3906
+ };
3907
+ }
3908
+ /**
3909
+ * `node:http` yapıştırıcısı — Node ve Bun'da AYNI şekilde çalışır (`Bun.serve`
3910
+ * kullanılmadı: paket npm'e yayınlanıyor ve `npx` altında Node ile koşuyor).
3911
+ */
3912
+ async function startMcpHttpServer(options) {
3913
+ const { createServer } = await import("node:http");
3914
+ const host = options.host ?? "127.0.0.1";
3915
+ const port = options.port ?? 3737;
3916
+ const server = createServer((req, res) => {
3917
+ (async () => {
3918
+ try {
3919
+ const chunks = [];
3920
+ for await (const chunk of req) chunks.push(typeof chunk === "string" ? new TextEncoder().encode(chunk) : chunk);
3921
+ const method = req.method ?? "GET";
3922
+ const headers = new Headers();
3923
+ for (const [key, value] of Object.entries(req.headers)) if (typeof value === "string") headers.set(key, value);
3924
+ else if (Array.isArray(value)) for (const item of value) headers.append(key, item);
3925
+ const hasBody = method !== "GET" && method !== "HEAD" && chunks.length > 0;
3926
+ const request = new Request(`http://${host}:${port}${req.url ?? "/"}`, {
3927
+ method,
3928
+ headers,
3929
+ ...hasBody ? { body: Buffer.concat(chunks) } : {}
3930
+ });
3931
+ for (const [key, value] of headers) request.headers.set(key, value);
3932
+ const response = await options.handler(request);
3933
+ const outHeaders = {};
3934
+ response.headers.forEach((value, key) => {
3935
+ outHeaders[key] = value;
3936
+ });
3937
+ res.writeHead(response.status, outHeaders);
3938
+ res.end(response.body === null ? void 0 : Buffer.from(await response.arrayBuffer()));
3939
+ } catch (error) {
3940
+ console.error("solicrm-mcp http isteği başarısız:", error);
3941
+ if (!res.headersSent) res.writeHead(500, { "content-type": "application/json" });
3942
+ res.end(JSON.stringify({ error: "Sunucu hatası" }));
3943
+ }
3944
+ })();
3945
+ });
3946
+ await new Promise((resolve, reject) => {
3947
+ server.once("error", reject);
3948
+ server.listen(port, host, () => {
3949
+ server.removeListener("error", reject);
3950
+ resolve();
3951
+ });
3952
+ });
3953
+ const address = server.address();
3954
+ return {
3955
+ host,
3956
+ port: typeof address === "object" && address !== null ? address.port : port,
3957
+ close: () => new Promise((resolve, reject) => {
3958
+ server.close((err) => err === void 0 || err === null ? resolve() : reject(err));
3959
+ })
3960
+ };
3961
+ }
3962
+ //#endregion
3963
+ //#region packages/mcp/src/transports/stdio.ts
3964
+ async function connectStdioTransport(server) {
3965
+ const transport = new StdioServerTransport();
3966
+ await server.connect(transport);
3967
+ return transport;
3968
+ }
3969
+ //#endregion
3970
+ //#region packages/mcp/src/cli.ts
3971
+ const PORT_PATTERN = /^[0-9]+$/;
3972
+ const USAGE = `solicrm-mcp — SoliCRM MCP sunucusu (v${SOLICRM_MCP_VERSION})
3973
+
3974
+ Kullanım:
3975
+ solicrm-mcp stdio taşımasıyla başlar (varsayılan)
3976
+ solicrm-mcp --http [port] bağımsız Streamable HTTP sunucusu (varsayılan port ${DEFAULT_HTTP_PORT})
3977
+ solicrm-mcp --help bu metni yazar
3978
+ solicrm-mcp --version sürümü yazar
3979
+
3980
+ Ortam değişkenleri:
3981
+ SOLICRM_API_KEY (zorunlu) scrm_ ile başlayan tenant API anahtarı
3982
+ SOLICRM_TENANT_ID (zorunlu) kiracı kimliği (UUID)
3983
+ SOLICRM_BASE_URL (opsiyonel) API tabanı; verilmezse https://api.solicrm.com
3984
+ SOLICRM_SCOPE (opsiyonel) read|write|admin — yalnız tools/list filtresi,
3985
+ gerçek yetki sunucuda her istekte yeniden hesaplanır`;
3986
+ /** Bkz. KRİTİK KURAL 3 */
3987
+ function parsePort(raw) {
3988
+ if (!PORT_PATTERN.test(raw)) throw new Error(`--http için geçersiz port: "${raw}". 1–65535 arası bir tam sayı bekleniyor.`);
3989
+ const port = Number.parseInt(raw, 10);
3990
+ if (port < 1 || port > 65535) throw new Error(`--http için geçersiz port: "${raw}". 1–65535 arası bir tam sayı bekleniyor.`);
3991
+ return port;
3992
+ }
3993
+ function parseCliArgs(argv) {
3994
+ let transport = "stdio";
3995
+ let port;
3996
+ let help = false;
3997
+ let version = false;
3998
+ for (let i = 0; i < argv.length; i += 1) {
3999
+ const arg = argv[i];
4000
+ if (arg === void 0) continue;
4001
+ if (arg === "--help" || arg === "-h") {
4002
+ help = true;
4003
+ continue;
4004
+ }
4005
+ if (arg === "--version" || arg === "-v") {
4006
+ version = true;
4007
+ continue;
4008
+ }
4009
+ if (arg === "--http") {
4010
+ transport = "http";
4011
+ const next = argv[i + 1];
4012
+ if (next !== void 0 && !next.startsWith("-")) {
4013
+ port = parsePort(next);
4014
+ i += 1;
4015
+ }
4016
+ continue;
4017
+ }
4018
+ if (arg.startsWith("--http=")) {
4019
+ transport = "http";
4020
+ port = parsePort(arg.slice(7));
4021
+ continue;
4022
+ }
4023
+ throw new Error(`Bilinmeyen argüman: "${arg}". Kullanım için --help.`);
4024
+ }
4025
+ return {
4026
+ transport,
4027
+ port,
4028
+ help,
4029
+ version
4030
+ };
4031
+ }
4032
+ /**
4033
+ * `@vennyx/solicrm`'i ÇALIŞMA ZAMANINDA import eder. `await import(...)`
4034
+ * BİLİNÇLİ: modül düzeyinde statik bir import, `--help`/`--version` gibi
4035
+ * SDK'ya hiç ihtiyaç duymayan yolları da SDK'nın yüklenmesine bağlardı.
4036
+ */
4037
+ async function createRealClient(config) {
4038
+ const { SolicrmClient } = await import("@vennyx/solicrm");
4039
+ return new SolicrmClient({
4040
+ apiKey: config.apiKey,
4041
+ tenantId: config.tenantId,
4042
+ baseUrl: config.baseUrl
4043
+ });
4044
+ }
4045
+ async function runCli(argv, deps = {}) {
4046
+ const log = deps.log ?? ((message) => console.error(message));
4047
+ const args = parseCliArgs(argv);
4048
+ if (args.help) {
4049
+ log(USAGE);
4050
+ return;
4051
+ }
4052
+ if (args.version) {
4053
+ log(SOLICRM_MCP_VERSION);
4054
+ return;
4055
+ }
4056
+ const config = (deps.loadConfig ?? loadConfigFromEnv)(deps.env ?? process.env);
4057
+ const createClient = deps.createClient;
4058
+ const client = createClient === void 0 ? await createRealClient(config) : createClient(config);
4059
+ const createServer = deps.createServer ?? createSolicrmMcpServer;
4060
+ if (args.transport === "http") {
4061
+ const running = await (deps.startHttp ?? (async ({ handler, port }) => await startMcpHttpServer({
4062
+ handler,
4063
+ port
4064
+ })))({
4065
+ handler: createMcpHttpHandler({ createServer: () => createServer(client, config.scope) }),
4066
+ port: args.port
4067
+ });
4068
+ log(`solicrm-mcp HTTP dinliyor: http://${running.host}:${running.port}${MCP_HTTP_PATH}`);
4069
+ return;
4070
+ }
4071
+ await (deps.connectStdio ?? connectStdioTransport)(createServer(client, config.scope));
4072
+ log("solicrm-mcp stdio taşımasında hazır.");
4073
+ }
4074
+ //#endregion
4075
+ //#region packages/mcp/src/bin.ts
4076
+ /**
4077
+ * `solicrm-mcp` çalıştırılabilir girişi — `package.json`'ın `bin` alanı buraya
4078
+ * (`dist/bin.js`) işaret eder.
4079
+ *
4080
+ * KRİTİK KURAL — BURADA `import.meta.main` GİBİ BİR GUARD YOKTUR ve olmamalı.
4081
+ * Bu dosya YALNIZ giriş noktası olarak çalışır (hiçbir modül onu import
4082
+ * etmez), dolayısıyla guard'ın koruyacağı bir durum yok. Ayrıca
4083
+ * `import.meta.main` Bun'da her dosya için tanımlıdır ama Node'da yalnız çok
4084
+ * yeni sürümlerde vardır ⇒ `npx` altında (Node) kalıcı `undefined` olur ve
4085
+ * guard'lı bir sürüm SESSİZCE HİÇ ÇALIŞMAZDI. Tüm mantık `cli.ts`'te ve orada
4086
+ * test ediliyor; bu dosya bilinçli olarak dallanmasız tutuluyor.
4087
+ *
4088
+ * Hata yolu: `runCli` fırlatırsa mesaj `stderr`'a yazılır (stdout PROTOKOL
4089
+ * kanalıdır) ve çıkış kodu 1 olur — sessizce 0 dönmek, istemciye "sunucu
4090
+ * başladı" yalanını söylerdi.
4091
+ */
4092
+ runCli(process.argv.slice(2)).catch((error) => {
4093
+ console.error(error instanceof Error ? error.message : String(error));
4094
+ process.exit(1);
4095
+ });
4096
+ //#endregion
4097
+ export {};