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