@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/LICENSE +21 -0
- package/README.md +137 -0
- package/dist/index.d.ts +1700 -0
- package/dist/index.js +3997 -0
- package/package.json +49 -0
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 };
|