@supsis/supsis-js 1.1.3 → 1.1.4

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.
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Supsis JS SDK v1.1.1
2
+ * Supsis JS SDK v1.1.3
3
3
  * Supsis API SDK - Browser ve Node.js için omnichannel messaging, chatbot, automation, task, ticket ve voice agent entegrasyonu
4
4
  *
5
5
  * @license MIT
@@ -2308,7 +2308,16 @@ class Table extends BaseModule {
2308
2308
  * @returns {Promise<TableResponse[]>}
2309
2309
  */
2310
2310
  async list() {
2311
- return await this.http.get('/api/tables');
2311
+ const response = await this.http.get('/api/tables');
2312
+ // API returns {total, tables} format
2313
+ if (response && response.tables) {
2314
+ return response.tables;
2315
+ }
2316
+ // Fallback for array response
2317
+ if (Array.isArray(response)) {
2318
+ return response;
2319
+ }
2320
+ return [];
2312
2321
  }
2313
2322
 
2314
2323
  /**
@@ -2398,16 +2407,69 @@ class Table extends BaseModule {
2398
2407
 
2399
2408
  /**
2400
2409
  * Tablo alanı oluşturur
2410
+ *
2401
2411
  * @param {string} tableId - Tablo ID'si
2402
- * @param {Object} data - Alan verileri
2403
- * @param {string} data.name - Alan adı
2404
- * @param {string} data.type - Alan tipi: 'text', 'number', 'date', 'select', 'multiselect', 'checkbox', 'url', 'email', 'phone', 'user', 'contact', 'relation', 'formula', 'rollup', 'lookup', 'attachment', 'rating', 'currency', 'percent', 'duration', 'barcode'
2405
- * @param {Object} [data.options] - Alan seçenekleri
2412
+ * @param {FieldCreateData} data - Alan verileri
2406
2413
  * @returns {Promise<FieldResponse>}
2414
+ *
2415
+ * @example
2416
+ * // Text alanı oluştur
2417
+ * await supsis.table.createField(tableId, {
2418
+ * name: 'Ad Soyad',
2419
+ * apiName: 'fullName',
2420
+ * fieldType: 'text'
2421
+ * });
2422
+ *
2423
+ * @example
2424
+ * // Dropdown alanı oluştur (static)
2425
+ * await supsis.table.createField(tableId, {
2426
+ * name: 'Durum',
2427
+ * apiName: 'status',
2428
+ * fieldType: 'dropdown',
2429
+ * options: {
2430
+ * dropdown: {
2431
+ * mode: 'static',
2432
+ * multiple: false,
2433
+ * options: [
2434
+ * { value: 'active', color: 'rgb(222, 243, 233)' },
2435
+ * { value: 'inactive', color: 'rgb(250, 223, 217)' }
2436
+ * ]
2437
+ * }
2438
+ * }
2439
+ * });
2440
+ *
2441
+ * @example
2442
+ * // Linked Record alanı oluştur
2443
+ * await supsis.table.createField(tableId, {
2444
+ * name: 'Müşteri',
2445
+ * apiName: 'customer',
2446
+ * fieldType: 'linked-record',
2447
+ * options: {
2448
+ * linkedRecord: {
2449
+ * table: 'customersTableId',
2450
+ * labelField: 'nameFieldId'
2451
+ * }
2452
+ * }
2453
+ * });
2454
+ *
2455
+ * @example
2456
+ * // AI alanı oluştur
2457
+ * await supsis.table.createField(tableId, {
2458
+ * name: 'Özet',
2459
+ * apiName: 'summary',
2460
+ * fieldType: 'ai',
2461
+ * options: {
2462
+ * ai: {
2463
+ * prompt: 'Bu kaydı özetle',
2464
+ * runAt: 'on-every-change',
2465
+ * whichFieldsToProcess: 'all'
2466
+ * }
2467
+ * }
2468
+ * });
2407
2469
  */
2408
2470
  async createField(tableId, data) {
2409
2471
  this._validateRequired({ tableId: tableId }, ['tableId']);
2410
- this._validateRequired(data, ['name', 'type']);
2472
+ this._validateRequired(data, ['name', 'fieldType']);
2411
2473
  return await this.http.put('/api/tables/' + tableId + '/fields', data);
2412
2474
  }
2413
2475
 
@@ -2714,11 +2776,207 @@ class Table extends BaseModule {
2714
2776
  * @typedef {Object} FieldResponse
2715
2777
  * @property {string} _id - Alan ID
2716
2778
  * @property {string} name - Alan adı
2717
- * @property {string} slug - URL-friendly isim
2718
- * @property {string} type - Alan tipi
2719
- * @property {Object} [options] - Alan seçenekleri
2779
+ * @property {string} apiName - API ismi (slug)
2780
+ * @property {FieldType} fieldType - Alan tipi
2781
+ * @property {FieldValueType} [valueType] - Değer tipi
2782
+ * @property {string} [icon] - Material icon adı (örn: 'text_fields', 'looks_one')
2783
+ * @property {'left'|'center'|'right'} [alignment] - Hizalama
2720
2784
  * @property {number} [position] - Sıra
2721
- * @property {boolean} [required] - Zorunlu mu
2785
+ * @property {*} [defaultValue] - Varsayılan değer
2786
+ * @property {boolean} [isIdentifierField] - Tanımlayıcı alan mı
2787
+ * @property {FieldOptions} [options] - Alan seçenekleri (tip'e göre değişir)
2788
+ * @property {Date} createdAt - Oluşturulma tarihi
2789
+ * @property {Date} updatedAt - Güncellenme tarihi
2790
+ */
2791
+
2792
+ // ==================== FIELD TYPES ====================
2793
+
2794
+ /**
2795
+ * Desteklenen alan tipleri
2796
+ * @typedef {'text'|'long-text'|'number'|'currency'|'datetime'|'dropdown'|'checkbox'|'url'|'email'|'phone'|'json'|'file'|'image'|'video'|'audio'|'color'|'automation-button'|'contact'|'ticket'|'task'|'event'|'form-submission'|'user'|'conversation'|'linked-record'|'ai'|'record_id'|'record_created_at'|'record_updated_at'|'record_created_by'|'record_updated_by'|'formula'|'lookup'|'auto-increment'} FieldType
2797
+ */
2798
+
2799
+ /**
2800
+ * Değer tipleri
2801
+ * @typedef {'string'|'number'|'date'|'boolean'|'array'|'json'|'asset'|'objectId'|'mixed'} FieldValueType
2802
+ */
2803
+
2804
+ /**
2805
+ * Alan oluşturma verisi
2806
+ * @typedef {Object} FieldCreateData
2807
+ * @property {string} name - Alan adı (görünen isim)
2808
+ * @property {string} [apiName] - API ismi (slug, otomatik oluşturulur)
2809
+ * @property {FieldType} fieldType - Alan tipi
2810
+ * @property {string} [icon] - Material icon adı
2811
+ * @property {'left'|'center'|'right'} [alignment='left'] - Hizalama
2812
+ * @property {*} [defaultValue] - Varsayılan değer
2813
+ * @property {FieldOptions} [options] - Alan seçenekleri (tip'e göre)
2814
+ */
2815
+
2816
+ // ==================== FIELD OPTIONS BY TYPE ====================
2817
+
2818
+ /**
2819
+ * Alan seçenekleri (fieldType'a göre ilgili alt nesne kullanılır)
2820
+ * @typedef {Object} FieldOptions
2821
+ * @property {DatetimeFieldOptions} [datetime] - datetime alanı için seçenekler
2822
+ * @property {NumberFieldOptions} [number] - number alanı için seçenekler
2823
+ * @property {CurrencyFieldOptions} [currency] - currency alanı için seçenekler
2824
+ * @property {DropdownFieldOptions} [dropdown] - dropdown alanı için seçenekler
2825
+ * @property {LinkedRecordFieldOptions} [linkedRecord] - linked-record alanı için seçenekler
2826
+ * @property {AIFieldOptions} [ai] - ai alanı için seçenekler
2827
+ * @property {FormulaFieldOptions} [formula] - formula alanı için seçenekler
2828
+ * @property {LookupFieldOptions} [lookup] - lookup alanı için seçenekler
2829
+ * @property {AutoIncrementFieldOptions} [autoIncrement] - auto-increment alanı için seçenekler
2830
+ * @property {ButtonFieldOptions} [button] - automation-button alanı için seçenekler
2831
+ */
2832
+
2833
+ /**
2834
+ * Datetime alan seçenekleri
2835
+ * @typedef {Object} DatetimeFieldOptions
2836
+ * @property {string} [timezone='UTC'] - Zaman dilimi (örn: 'Europe/Istanbul', 'America/New_York')
2837
+ * @property {string} [format='YYYY-MM-DD'] - Tarih formatı
2838
+ * - 'MM/DD/YYYY' - 12/31/2023
2839
+ * - 'DD/MM/YYYY' - 31/12/2023
2840
+ * - 'YYYY-MM-DD' - 2023-12-31
2841
+ * - 'MMM DD, YYYY' - Dec 31, 2023
2842
+ * - 'MMMM DD, YYYY' - December 31, 2023
2843
+ * @property {boolean} [includeTime=true] - Saat dahil mi
2844
+ * @property {string} [timeFormat='HH:mm'] - Saat formatı
2845
+ * - 'HH:mm' - 23:59
2846
+ * - 'hh:mm A' - 11:59 PM
2847
+ * - 'HH:mm:ss' - 23:59:59
2848
+ * @property {boolean} [defaultNow=false] - Varsayılan olarak şu anki zaman mı
2849
+ */
2850
+
2851
+ /**
2852
+ * Number alan seçenekleri
2853
+ * @typedef {Object} NumberFieldOptions
2854
+ * @property {string} [decimalFormat='0'] - Ondalık formatı
2855
+ * - '0' - Tam sayı
2856
+ * - '0.0' - 1 ondalık
2857
+ * - '0.00' - 2 ondalık
2858
+ * - '0.000' - 3 ondalık
2859
+ */
2860
+
2861
+ /**
2862
+ * Currency alan seçenekleri
2863
+ * @typedef {Object} CurrencyFieldOptions
2864
+ * @property {string} [symbol='$'] - Para birimi sembolü
2865
+ * - '$' - USD
2866
+ * - '€' - EUR
2867
+ * - '£' - GBP
2868
+ * - '₺' - TRY
2869
+ * - '¥' - JPY/CNY
2870
+ * @property {string} [decimalFormat='00'] - Ondalık formatı
2871
+ */
2872
+
2873
+ /**
2874
+ * Dropdown alan seçenekleri
2875
+ * @typedef {Object} DropdownFieldOptions
2876
+ * @property {'static'|'dynamic'} [mode='static'] - Seçenek modu
2877
+ * - 'static': Sabit seçenekler (options dizisinden)
2878
+ * - 'dynamic': Başka tablodan dinamik seçenekler
2879
+ * @property {boolean} [multiple=false] - Çoklu seçim
2880
+ * @property {DropdownOption[]} [options] - Static mod için seçenekler
2881
+ * @property {string} [remoteTable] - Dynamic mod için kaynak tablo ID
2882
+ * @property {string} [remoteTableField] - Dynamic mod için kaynak alan ID
2883
+ */
2884
+
2885
+ /**
2886
+ * Dropdown seçenek
2887
+ * @typedef {Object} DropdownOption
2888
+ * @property {string} value - Seçenek değeri
2889
+ * @property {string} [color='rgb(210, 216, 243)'] - Arka plan rengi (RGB)
2890
+ * - 'rgb(232, 231, 228)' - Gray
2891
+ * - 'rgb(210, 216, 243)' - Blue
2892
+ * - 'rgb(222, 243, 233)' - Green
2893
+ * - 'rgb(255, 244, 188)' - Yellow
2894
+ * - 'rgb(250, 223, 217)' - Red
2895
+ * - 'rgb(250, 217, 239)' - Pink
2896
+ * - 'rgb(235, 217, 250)' - Purple
2897
+ */
2898
+
2899
+ /**
2900
+ * Linked Record alan seçenekleri
2901
+ * @typedef {Object} LinkedRecordFieldOptions
2902
+ * @property {string} table - Referans edilecek tablo ID
2903
+ * @property {string} [labelField] - Gösterilecek alan ID (varsayılan: ilk text alanı)
2904
+ */
2905
+
2906
+ /**
2907
+ * AI alan seçenekleri
2908
+ * @typedef {Object} AIFieldOptions
2909
+ * @property {string} prompt - AI için prompt metni
2910
+ * @property {'only-on-create'|'on-every-change'|'manual'} [runAt='only-on-create'] - Çalışma zamanı
2911
+ * - 'only-on-create': Sadece kayıt oluşturulduğunda
2912
+ * - 'on-every-change': Her değişiklikte
2913
+ * - 'manual': Manuel tetikleme (button ile)
2914
+ * @property {'all'|'custom'} [whichFieldsToProcess='all'] - İşlenecek alanlar
2915
+ * @property {string[]} [fieldsToProcess] - 'custom' seçiliyse işlenecek alan ID'leri
2916
+ */
2917
+
2918
+ /**
2919
+ * Formula alan seçenekleri
2920
+ * @typedef {Object} FormulaFieldOptions
2921
+ * @property {string} expression - Formül ifadesi
2922
+ * Örnek: "{field1} + {field2}", "IF({status}='active', 'Yes', 'No')"
2923
+ */
2924
+
2925
+ /**
2926
+ * Lookup alan seçenekleri
2927
+ * @typedef {Object} LookupFieldOptions
2928
+ * @property {string} linkedRecordField - Kaynak linked-record alan ID
2929
+ * @property {string} targetField - Hedef tablodaki gösterilecek alan ID
2930
+ */
2931
+
2932
+ /**
2933
+ * Auto Increment alan seçenekleri
2934
+ * @typedef {Object} AutoIncrementFieldOptions
2935
+ * @property {'number'|'string'} [format='number'] - Çıktı formatı
2936
+ * - 'number': Sadece sayı (1, 2, 3, ...)
2937
+ * - 'string': Prefix/suffix ile (INV-001, INV-002, ...)
2938
+ * @property {string} [prefix] - Ön ek (string format için, örn: 'INV-')
2939
+ * @property {string} [suffix] - Son ek (string format için, örn: '-2024')
2940
+ * @property {number} [startValue=1] - Başlangıç değeri
2941
+ * @property {number} [increment=1] - Artış miktarı
2942
+ */
2943
+
2944
+ /**
2945
+ * Automation Button alan seçenekleri
2946
+ * @typedef {Object} ButtonFieldOptions
2947
+ * @property {string} [defaultLabel] - Button etiketi
2948
+ * @property {boolean} [disableWhenClicked=false] - Tıklandığında devre dışı bırak
2949
+ * @property {string} [automation] - Tetiklenecek otomasyon ID
2950
+ */
2951
+
2952
+ // ==================== META FIELD TYPES ====================
2953
+
2954
+ /**
2955
+ * Meta alan tipleri (sistem tarafından otomatik yönetilir, readonly)
2956
+ *
2957
+ * - record_id: Kayıt benzersiz ID'si (string)
2958
+ * - record_created_at: Oluşturulma tarihi (date)
2959
+ * - record_updated_at: Son güncelleme tarihi (date)
2960
+ * - record_created_by: Oluşturan kullanıcı (objectId -> User)
2961
+ * - record_updated_by: Güncelleyen kullanıcı (objectId -> User)
2962
+ *
2963
+ * @typedef {'record_id'|'record_created_at'|'record_updated_at'|'record_created_by'|'record_updated_by'} MetaFieldType
2964
+ */
2965
+
2966
+ // ==================== MODULE FIELD TYPES ====================
2967
+
2968
+ /**
2969
+ * Modül alan tipleri (Supsis modüllerine referans)
2970
+ *
2971
+ * - contact: Kişi modülüne referans
2972
+ * - ticket: Destek talebi modülüne referans
2973
+ * - task: Görev modülüne referans
2974
+ * - event: Etkinlik modülüne referans
2975
+ * - user: Kullanıcı modülüne referans
2976
+ * - conversation: Sohbet/Görüşme modülüne referans
2977
+ * - form-submission: Form gönderimi modülüne referans
2978
+ *
2979
+ * @typedef {'contact'|'ticket'|'task'|'event'|'user'|'conversation'|'form-submission'} ModuleFieldType
2722
2980
  */
2723
2981
 
2724
2982
  /**
@@ -2779,31 +3037,113 @@ class Module extends BaseModule {
2779
3037
  * @returns {Promise<ModuleDefinitionResponse[]>}
2780
3038
  */
2781
3039
  async list() {
2782
- return await this.http.get('/api/module');
3040
+ const response = await this.http.get('/api/module');
3041
+ // API may return {total, modules} or direct array
3042
+ if (response && response.modules) {
3043
+ return response.modules;
3044
+ }
3045
+ // Fallback for array response
3046
+ if (Array.isArray(response)) {
3047
+ return response;
3048
+ }
3049
+ return [];
2783
3050
  }
2784
3051
 
2785
3052
  /**
2786
3053
  * Modül oluşturur
2787
- * @param {Object} data - Modül verileri
2788
- * @param {string} data.name - Modül adı (API name)
2789
- * @param {string} data.displayName - Görünen ad
2790
- * @param {Object[]} [data.fields] - Alan tanımları
3054
+ *
3055
+ * İki tip modül oluşturulabilir:
3056
+ * 1. 'module': Bir tabloya bağlı modül (CRM modülü gibi)
3057
+ * 2. 'relation': İki tablo/modül arasında ilişki tanımı
3058
+ *
3059
+ * @param {ModuleCreateData} data - Modül verileri
2791
3060
  * @returns {Promise<ModuleDefinitionResponse>}
3061
+ *
3062
+ * @example
3063
+ * // Tabloya bağlı modül oluştur
3064
+ * await supsis.module.create({
3065
+ * displayName: 'Leads',
3066
+ * description: 'Potansiyel müşteriler',
3067
+ * type: 'module',
3068
+ * icon: 'person_add',
3069
+ * module: {
3070
+ * table: 'tableId123',
3071
+ * settings: {
3072
+ * showInMainMenu: true,
3073
+ * apiEnabled: true
3074
+ * }
3075
+ * }
3076
+ * });
3077
+ *
3078
+ * @example
3079
+ * // Table-to-Table relation oluştur
3080
+ * await supsis.module.create({
3081
+ * displayName: 'Müşteri Siparişleri',
3082
+ * type: 'relation',
3083
+ * icon: 'link',
3084
+ * relation: {
3085
+ * type: 'table-to-table',
3086
+ * tableToTable: {
3087
+ * from: { table: 'customersTableId', connectionType: '_id' },
3088
+ * to: { table: 'ordersTableId', connectionType: 'field', field: 'customerField' }
3089
+ * }
3090
+ * }
3091
+ * });
3092
+ *
3093
+ * @example
3094
+ * // Standart modül -> Table relation oluştur
3095
+ * await supsis.module.create({
3096
+ * displayName: 'Kişi Satın Alımları',
3097
+ * type: 'relation',
3098
+ * icon: 'shopping_cart',
3099
+ * relation: {
3100
+ * type: 'standart-to-table',
3101
+ * standartToTable: {
3102
+ * from: { module: 'contact', connectionType: 'field', field: '_id' },
3103
+ * to: { table: 'purchasesTableId', connectionType: 'field', field: 'contactField' }
3104
+ * }
3105
+ * }
3106
+ * });
2792
3107
  */
2793
3108
  async create(data) {
2794
- this._validateRequired(data, ['name', 'displayName']);
3109
+ this._validateRequired(data, ['displayName', 'type']);
3110
+
3111
+ if (data.type === 'module') {
3112
+ this._validateRequired(data, ['module']);
3113
+ this._validateRequired(data.module, ['table']);
3114
+ } else if (data.type === 'relation') {
3115
+ this._validateRequired(data, ['relation']);
3116
+ this._validateRequired(data.relation, ['type']);
3117
+ }
3118
+
2795
3119
  return await this.http.post('/api/module', data);
2796
3120
  }
2797
3121
 
2798
3122
  /**
2799
3123
  * Modülü günceller
2800
- * @param {string} moduleName - Modül adı
2801
- * @param {Object} data - Güncellenecek veriler
3124
+ * @param {string} moduleName - Modül adı (API name)
3125
+ * @param {Object} updatingFields - Güncellenecek alanlar
3126
+ * @param {string} [updatingFields.displayName] - Görünen ad
3127
+ * @param {string} [updatingFields.description] - Açıklama
3128
+ * @param {string} [updatingFields.icon] - Material icon adı
3129
+ * @param {number} [updatingFields.position] - Sıralama pozisyonu
3130
+ * @param {ModuleSettings} [updatingFields.settings] - Modül ayarları (type='module' için)
2802
3131
  * @returns {Promise<ModuleDefinitionResponse>}
2803
3132
  */
2804
- async update(moduleName, data) {
3133
+ async update(moduleName, updatingFields) {
3134
+ this._validateRequired({ moduleName: moduleName }, ['moduleName']);
3135
+ return await this.http.post('/api/module/' + moduleName, { updatingFields: updatingFields });
3136
+ }
3137
+
3138
+ /**
3139
+ * Modül adına göre modül getirir
3140
+ * @param {string} moduleName - Modül adı (API name)
3141
+ * @returns {Promise<ModuleDefinitionResponse|null>}
3142
+ */
3143
+ async getByName(moduleName) {
2805
3144
  this._validateRequired({ moduleName: moduleName }, ['moduleName']);
2806
- return await this.http.post('/api/module/' + moduleName, data);
3145
+ const modules = await this.list();
3146
+ return modules.find(function(m) { return m.moduleName === moduleName; }) || null;
2807
3147
  }
2808
3148
 
2809
3149
  /**
@@ -2967,14 +3307,113 @@ class Module extends BaseModule {
2967
3307
  * @typedef {Object} ModuleDefinitionResponse
2968
3308
  * @property {string} _id - Modül ID
2969
3309
  * @property {string} siteId - Site ID
2970
- * @property {string} name - Modül adı (API name)
3310
+ * @property {string} moduleName - Modül adı (otomatik oluşturulur)
3311
+ * @property {string} apiName - API adı
2971
3312
  * @property {string} displayName - Görünen ad
2972
3313
  * @property {string} [description] - Açıklama
2973
- * @property {string} [icon] - İkon
2974
- * @property {ModuleFieldDefinition[]} fields - Alan tanımları
3314
+ * @property {string} [icon] - Material icon adı
3315
+ * @property {'module'|'relation'} type - Modül tipi
3316
+ * @property {ModuleConfig} [module] - type='module' için konfigürasyon
3317
+ * @property {RelationConfig} [relation] - type='relation' için konfigürasyon
3318
+ * @property {number} [position] - Sıralama pozisyonu
2975
3319
  * @property {Date} createdAt - Oluşturulma tarihi
3320
+ * @property {Date} updatedAt - Güncellenme tarihi
3321
+ */
3322
+
3323
+ // ==================== MODULE CREATE DATA ====================
3324
+
3325
+ /**
3326
+ * Modül oluşturma verisi
3327
+ * @typedef {Object} ModuleCreateData
3328
+ * @property {string} displayName - Görünen ad (zorunlu)
3329
+ * @property {string} [description] - Açıklama
3330
+ * @property {'module'|'relation'} type - Modül tipi (zorunlu)
3331
+ * @property {string} [icon] - Material icon adı (örn: 'widgets', 'list', 'person_add')
3332
+ * @property {ModuleConfig} [module] - type='module' için konfigürasyon
3333
+ * @property {RelationConfig} [relation] - type='relation' için konfigürasyon
3334
+ */
3335
+
3336
+ /**
3337
+ * Module tipi konfigürasyonu (type='module' için)
3338
+ * @typedef {Object} ModuleConfig
3339
+ * @property {string} table - Bağlı tablo ID'si (zorunlu)
3340
+ * @property {ModuleSettings} [settings] - Modül ayarları
3341
+ */
3342
+
3343
+ /**
3344
+ * Modül ayarları
3345
+ * @typedef {Object} ModuleSettings
3346
+ * @property {boolean} [showInMainMenu=true] - Ana menüde göster
3347
+ * @property {boolean} [apiEnabled=true] - API erişimi aktif
3348
+ */
3349
+
3350
+ // ==================== RELATION CONFIG ====================
3351
+
3352
+ /**
3353
+ * Relation tipi konfigürasyonu (type='relation' için)
3354
+ * @typedef {Object} RelationConfig
3355
+ * @property {'table-to-table'|'table-to-standart'|'standart-to-table'} type - İlişki tipi
3356
+ * @property {TableToTableRelation} [tableToTable] - Tablo-Tablo ilişkisi
3357
+ * @property {StandartToTableRelation} [standartToTable] - StandartModül-Tablo ilişkisi
3358
+ * @property {TableToStandartRelation} [tableToStandart] - Tablo-StandartModül ilişkisi
3359
+ */
3360
+
3361
+ /**
3362
+ * Tablo-Tablo ilişki tanımı
3363
+ *
3364
+ * Örnek: Yazarlar ve Kitaplar tabloları
3365
+ * - Yazarlar tablosunda Kitaplar listesini göstermek için:
3366
+ * from: { table: 'yazarlarId', connectionType: '_id' }
3367
+ * to: { table: 'kitaplarId', connectionType: 'field', field: 'yazarFieldId' }
3368
+ *
3369
+ * @typedef {Object} TableToTableRelation
3370
+ * @property {TableConnectionPoint} from - Kaynak tablo bağlantı noktası
3371
+ * @property {TableConnectionPoint} to - Hedef tablo bağlantı noktası
3372
+ */
3373
+
3374
+ /**
3375
+ * Tablo bağlantı noktası
3376
+ * @typedef {Object} TableConnectionPoint
3377
+ * @property {string} table - Tablo ID
3378
+ * @property {'_id'|'field'} connectionType - Bağlantı tipi
3379
+ * - '_id': Tablonun _id alanı ile bağlan
3380
+ * - 'field': Belirli bir alan ile bağlan
3381
+ * @property {string} [field] - connectionType='field' ise alan ID
3382
+ */
3383
+
3384
+ /**
3385
+ * Standart Modül-Tablo ilişki tanımı
3386
+ *
3387
+ * Örnek: Contact modülünde SatınAlımlar tablosunu göstermek için:
3388
+ * from: { module: 'contact', connectionType: 'field', field: '_id' }
3389
+ * to: { table: 'satinAlimlarId', connectionType: 'field', field: 'contactFieldId' }
3390
+ *
3391
+ * @typedef {Object} StandartToTableRelation
3392
+ * @property {StandartModuleConnectionPoint} from - Kaynak standart modül bağlantı noktası
3393
+ * @property {TableConnectionPoint} to - Hedef tablo bağlantı noktası
3394
+ */
3395
+
3396
+ /**
3397
+ * Standart modül bağlantı noktası
3398
+ * @typedef {Object} StandartModuleConnectionPoint
3399
+ * @property {'contact'|'ticket'|'task'|'calendar'|'email'} module - Standart modül adı
3400
+ * @property {'field'|'customProperties'} connectionType - Bağlantı tipi
3401
+ * - 'field': Standart alan ile bağlan (_id, name, email, phone, whatsappNumber)
3402
+ * - 'customProperties': Özel alan ile bağlan
3403
+ * @property {string} field - Bağlantı alanı
3404
+ * - contact: _id, name, email, phone, whatsappNumber
3405
+ * - ticket/task/calendar/email: _id
3406
+ */
3407
+
3408
+ /**
3409
+ * Tablo-Standart Modül ilişki tanımı
3410
+ * @typedef {Object} TableToStandartRelation
3411
+ * @property {TableConnectionPoint} from - Kaynak tablo bağlantı noktası
3412
+ * @property {StandartModuleConnectionPoint} to - Hedef standart modül bağlantı noktası
2976
3413
  */
2977
3414
 
3415
+ // ==================== RECORD TYPES ====================
3416
+
2978
3417
  /**
2979
3418
  * @typedef {Object} ModuleFieldDefinition
2980
3419
  * @property {string} name - Alan adı
@@ -4188,6 +4627,1201 @@ class SupsisModuleAPI {
4188
4627
  * @property {number} [retryCount=3] - Başarısız isteklerde retry sayısı
4189
4628
  */
4190
4629
 
4630
+ /**
4631
+ * SupsisAppBuilder
4632
+ * Template-driven application builder for Supsis platform.
4633
+ * Creates tables, fields, modules, cache entries and seed data from a template.
4634
+ *
4635
+ * @module SupsisAppBuilder
4636
+ * @example
4637
+ * const { SupsisJS, SupsisAppBuilder } = require('@supsis/supsis-js');
4638
+ *
4639
+ * const supsis = new SupsisJS({ email, password, siteId });
4640
+ * await supsis.connect();
4641
+ *
4642
+ * const appBuilder = new SupsisAppBuilder(supsis);
4643
+ * await appBuilder.build(template);
4644
+ */
4645
+
4646
+ /**
4647
+ * App Builder Validation Error
4648
+ */
4649
+ class AppBuilderValidationError extends Error {
4650
+ constructor(message, details) {
4651
+ super(message);
4652
+ this.name = 'AppBuilderValidationError';
4653
+ this.details = details || {};
4654
+ }
4655
+ }
4656
+
4657
+ /**
4658
+ * App Builder Build Error
4659
+ */
4660
+ class AppBuilderBuildError extends Error {
4661
+ constructor(message, phase, details) {
4662
+ super(message);
4663
+ this.name = 'AppBuilderBuildError';
4664
+ this.phase = phase;
4665
+ this.details = details || {};
4666
+ }
4667
+ }
4668
+
4669
+ /**
4670
+ * Meta field definitions (auto-added to every table)
4671
+ */
4672
+ const META_FIELDS = [
4673
+ { name: 'Record ID', apiName: 'record_id', fieldType: 'record_id', icon: 'tag' },
4674
+ { name: 'Created At', apiName: 'record_created_at', fieldType: 'record_created_at', icon: 'add_circle_outline' },
4675
+ { name: 'Updated At', apiName: 'record_updated_at', fieldType: 'record_updated_at', icon: 'update' },
4676
+ { name: 'Created By', apiName: 'record_created_by', fieldType: 'record_created_by', icon: 'person_add' },
4677
+ { name: 'Updated By', apiName: 'record_updated_by', fieldType: 'record_updated_by', icon: 'person_outline' }
4678
+ ];
4679
+
4680
+ /**
4681
+ * SupsisAppBuilder - Template-driven application builder
4682
+ */
4683
+ class SupsisAppBuilder {
4684
+ /**
4685
+ * @param {import('../SupsisJS.js').SupsisJS} supsisInstance - Connected SupsisJS instance
4686
+ */
4687
+ constructor(supsisInstance) {
4688
+ if (!supsisInstance) {
4689
+ throw new AppBuilderValidationError('SupsisJS instance is required');
4690
+ }
4691
+
4692
+ this._supsis = supsisInstance;
4693
+ this._verbose = false;
4694
+
4695
+ // Build state
4696
+ this._uidMap = {}; // uid -> { type, id, data }
4697
+ this._tableMap = {}; // uid -> table response
4698
+ this._fieldMap = {}; // tableUid.fieldApiName -> field response
4699
+ this._moduleMap = {}; // uid -> module response
4700
+ this._seedMap = {}; // uid -> record response
4701
+ this._pendingFields = []; // Fields waiting for linked table to be created
4702
+ }
4703
+
4704
+ /**
4705
+ * Internal logging
4706
+ * @private
4707
+ */
4708
+ _log(type, ...args) {
4709
+ if (!this._verbose) return;
4710
+
4711
+ const prefix = '[AppBuilder]';
4712
+ const timestamp = new Date().toISOString();
4713
+
4714
+ switch (type) {
4715
+ case 'info':
4716
+ console.log(`${prefix} ${timestamp} INFO:`, ...args);
4717
+ break;
4718
+ case 'warn':
4719
+ console.warn(`${prefix} ${timestamp} WARN:`, ...args);
4720
+ break;
4721
+ case 'error':
4722
+ console.error(`${prefix} ${timestamp} ERROR:`, ...args);
4723
+ break;
4724
+ case 'success':
4725
+ console.log(`${prefix} ${timestamp} ✓`, ...args);
4726
+ break;
4727
+ case 'phase':
4728
+ console.log(`${prefix} ${timestamp} ═══════════════════════════════════`);
4729
+ console.log(`${prefix} ${timestamp} PHASE:`, ...args);
4730
+ console.log(`${prefix} ${timestamp} ═══════════════════════════════════`);
4731
+ break;
4732
+ default:
4733
+ console.log(`${prefix} ${timestamp}`, ...args);
4734
+ }
4735
+ }
4736
+
4737
+ /**
4738
+ * Generate description with app builder metadata
4739
+ * @private
4740
+ */
4741
+ _generateDescription(appName, uid, customDescription) {
4742
+ const meta = `creator=supsis-app-builder;appname=${appName};uid=${uid}`;
4743
+ return customDescription ? `${customDescription}\n${meta}` : meta;
4744
+ }
4745
+
4746
+ /**
4747
+ * Parse description to extract app builder metadata
4748
+ * @private
4749
+ */
4750
+ _parseDescription(description) {
4751
+ if (!description) return null;
4752
+
4753
+ const match = description.match(/creator=supsis-app-builder;appname=([^;]+);uid=([^\s\n]+)/);
4754
+ if (match) {
4755
+ return {
4756
+ creator: 'supsis-app-builder',
4757
+ appName: match[1],
4758
+ uid: match[2]
4759
+ };
4760
+ }
4761
+ return null;
4762
+ }
4763
+
4764
+ /**
4765
+ * Validate template structure
4766
+ * @private
4767
+ */
4768
+ _validateTemplate(template) {
4769
+ const errors = [];
4770
+
4771
+ // Required fields
4772
+ if (!template.name) errors.push('Template name is required');
4773
+ if (!template.version) errors.push('Template version is required');
4774
+
4775
+ // Validate tables
4776
+ if (template.tables) {
4777
+ const tableUids = new Set();
4778
+
4779
+ template.tables.forEach((table, idx) => {
4780
+ if (!table.name) errors.push(`Table[${idx}]: name is required`);
4781
+ if (!table.uid) errors.push(`Table[${idx}]: uid is required`);
4782
+
4783
+ if (table.uid) {
4784
+ if (tableUids.has(table.uid)) {
4785
+ errors.push(`Table[${idx}]: duplicate uid '${table.uid}'`);
4786
+ }
4787
+ tableUids.add(table.uid);
4788
+ }
4789
+
4790
+ // Validate fields
4791
+ if (table.fields) {
4792
+ const fieldApiNames = new Set();
4793
+
4794
+ table.fields.forEach((field, fidx) => {
4795
+ if (!field.name) errors.push(`Table[${idx}].Field[${fidx}]: name is required`);
4796
+ if (!field.fieldType) errors.push(`Table[${idx}].Field[${fidx}]: fieldType is required`);
4797
+
4798
+ if (field.apiName) {
4799
+ if (fieldApiNames.has(field.apiName)) {
4800
+ errors.push(`Table[${idx}].Field[${fidx}]: duplicate apiName '${field.apiName}'`);
4801
+ }
4802
+ fieldApiNames.add(field.apiName);
4803
+ }
4804
+ });
4805
+ }
4806
+ });
4807
+ }
4808
+
4809
+ // Validate modules
4810
+ if (template.modules) {
4811
+ template.modules.forEach((mod, idx) => {
4812
+ if (!mod.name) errors.push(`Module[${idx}]: name is required`);
4813
+ if (!mod.table) errors.push(`Module[${idx}]: table (uid) is required`);
4814
+ });
4815
+ }
4816
+
4817
+ // Validate cache keys
4818
+ if (template.cache) {
4819
+ Object.keys(template.cache).forEach(key => {
4820
+ if (!key.startsWith(template.name)) {
4821
+ errors.push(`Cache key '${key}' must start with app name '${template.name}'`);
4822
+ }
4823
+ });
4824
+ }
4825
+
4826
+ // Validate seed data
4827
+ if (template.seed) {
4828
+ template.seed.forEach((item, idx) => {
4829
+ if (!item.module) errors.push(`Seed[${idx}]: module is required`);
4830
+ if (!item.data) errors.push(`Seed[${idx}]: data is required`);
4831
+ });
4832
+ }
4833
+
4834
+ if (errors.length > 0) {
4835
+ throw new AppBuilderValidationError('Template validation failed', { errors });
4836
+ }
4837
+ }
4838
+
4839
+ /**
4840
+ * Resolve UID to actual ID
4841
+ * @private
4842
+ */
4843
+ _resolveUid(uid, type) {
4844
+ if (!uid) return null;
4845
+
4846
+ // Check if it's already a real ID (24 char hex string)
4847
+ if (/^[a-f0-9]{24}$/i.test(uid)) {
4848
+ return uid;
4849
+ }
4850
+
4851
+ // Look up in maps
4852
+ if (type === 'table' && this._tableMap[uid]) {
4853
+ return this._tableMap[uid]._id;
4854
+ }
4855
+
4856
+ if (type === 'field') {
4857
+ // uid format: tableUid.fieldApiName
4858
+ const parts = uid.split('.');
4859
+ if (parts.length >= 2) {
4860
+ const tableUid = parts.slice(0, -1).join('.');
4861
+ const fieldApiName = parts[parts.length - 1];
4862
+ const key = `${tableUid}.${fieldApiName}`;
4863
+ if (this._fieldMap[key]) {
4864
+ return this._fieldMap[key]._id;
4865
+ }
4866
+ }
4867
+ }
4868
+
4869
+ if (type === 'module' && this._moduleMap[uid]) {
4870
+ return this._moduleMap[uid]._id;
4871
+ }
4872
+
4873
+ if (type === 'seed' && this._seedMap[uid]) {
4874
+ return this._seedMap[uid]._id;
4875
+ }
4876
+
4877
+ return null;
4878
+ }
4879
+
4880
+ /**
4881
+ * Find existing table by description metadata
4882
+ * @private
4883
+ */
4884
+ async _findExistingTable(appName, uid) {
4885
+ try {
4886
+ const tables = await this._supsis.table.list();
4887
+
4888
+ for (const table of tables) {
4889
+ const meta = this._parseDescription(table.description);
4890
+ if (meta && meta.appName === appName && meta.uid === uid) {
4891
+ return table;
4892
+ }
4893
+ }
4894
+ } catch (e) {
4895
+ this._log('warn', 'Failed to list tables:', e.message);
4896
+ }
4897
+ return null;
4898
+ }
4899
+
4900
+ /**
4901
+ * Find existing module by description metadata
4902
+ * @private
4903
+ */
4904
+ async _findExistingModule(appName, uid) {
4905
+ try {
4906
+ const modules = await this._supsis.module.list();
4907
+
4908
+ for (const mod of modules) {
4909
+ const meta = this._parseDescription(mod.description);
4910
+ if (meta && meta.appName === appName && meta.uid === uid) {
4911
+ return mod;
4912
+ }
4913
+ }
4914
+ } catch (e) {
4915
+ this._log('warn', 'Failed to list modules:', e.message);
4916
+ }
4917
+ return null;
4918
+ }
4919
+
4920
+ /**
4921
+ * Infer data type for cache value
4922
+ * @private
4923
+ */
4924
+ _inferCacheDataType(value) {
4925
+ if (value === null || value === undefined) return 'mixed';
4926
+ if (typeof value === 'string') return 'string';
4927
+ if (typeof value === 'number') return 'number';
4928
+ if (typeof value === 'boolean') return 'boolean';
4929
+ if (value instanceof Date) return 'date';
4930
+ if (Array.isArray(value)) return 'array';
4931
+ if (typeof value === 'object') return 'json';
4932
+ return 'mixed';
4933
+ }
4934
+
4935
+ /**
4936
+ * Build tables (Phase 1 - create tables without linked-record fields)
4937
+ * @private
4938
+ */
4939
+ async _buildTables(template, options) {
4940
+ this._log('phase', 'Building Tables');
4941
+
4942
+ const tables = template.tables || [];
4943
+
4944
+ for (const tableDef of tables) {
4945
+ const uid = tableDef.uid;
4946
+ const appName = template.name;
4947
+
4948
+ // Check if table already exists
4949
+ let existingTable = await this._findExistingTable(appName, uid);
4950
+
4951
+ if (existingTable) {
4952
+ if (options.skipExisting) {
4953
+ this._log('info', `Table '${tableDef.name}' (${uid}) already exists, skipping...`);
4954
+ this._tableMap[uid] = existingTable;
4955
+ continue;
4956
+ }
4957
+
4958
+ if (options.updateExisting) {
4959
+ this._log('info', `Table '${tableDef.name}' (${uid}) exists, updating...`);
4960
+ existingTable = await this._supsis.table.update(existingTable._id, {
4961
+ name: tableDef.name,
4962
+ icon: tableDef.icon,
4963
+ description: this._generateDescription(appName, uid, tableDef.description),
4964
+ category: {
4965
+ name: appName,
4966
+ icon: tableDef.categoryIcon || 'apps'
4967
+ }
4968
+ });
4969
+ this._tableMap[uid] = existingTable;
4970
+ continue;
4971
+ }
4972
+ }
4973
+
4974
+ // Create new table
4975
+ if (options.dryRun) {
4976
+ this._log('info', `[DRY-RUN] Would create table: ${tableDef.name} (${uid})`);
4977
+ this._tableMap[uid] = { _id: `dry-run-${uid}`, name: tableDef.name, uid };
4978
+ } else {
4979
+ this._log('info', `Creating table: ${tableDef.name} (${uid})`);
4980
+
4981
+ const tableData = {
4982
+ name: tableDef.name,
4983
+ description: this._generateDescription(appName, uid, tableDef.description),
4984
+ icon: tableDef.icon || 'table_chart',
4985
+ category: {
4986
+ name: appName,
4987
+ icon: tableDef.categoryIcon || 'apps'
4988
+ }
4989
+ };
4990
+
4991
+ const created = await this._supsis.table.create(tableData);
4992
+ this._tableMap[uid] = created;
4993
+ this._log('success', `Table created: ${tableDef.name} -> ${created._id}`);
4994
+ }
4995
+ }
4996
+ }
4997
+
4998
+ /**
4999
+ * Build fields (Phase 2 - create fields, queue linked-records for Phase 3)
5000
+ * @private
5001
+ */
5002
+ async _buildFields(template, options) {
5003
+ this._log('phase', 'Building Fields (Phase 1 - Non-linked fields)');
5004
+
5005
+ const tables = template.tables || [];
5006
+
5007
+ for (const tableDef of tables) {
5008
+ const tableUid = tableDef.uid;
5009
+ const table = this._tableMap[tableUid];
5010
+
5011
+ if (!table) {
5012
+ this._log('warn', `Table not found for uid: ${tableUid}`);
5013
+ continue;
5014
+ }
5015
+
5016
+ const tableId = table._id;
5017
+ const fields = tableDef.fields || [];
5018
+
5019
+ // Get existing fields
5020
+ let existingFields = [];
5021
+ if (!options.dryRun) {
5022
+ try {
5023
+ existingFields = await this._supsis.table.listFields(tableId);
5024
+ } catch (e) {
5025
+ this._log('warn', `Failed to list fields for table ${tableId}:`, e.message);
5026
+ }
5027
+ }
5028
+
5029
+ // First, add meta fields if not present
5030
+ for (const metaField of META_FIELDS) {
5031
+ const existing = existingFields.find(f => f.fieldType === metaField.fieldType);
5032
+ if (!existing) {
5033
+ if (options.dryRun) {
5034
+ this._log('info', `[DRY-RUN] Would create meta field: ${metaField.name} in ${tableDef.name}`);
5035
+ } else {
5036
+ try {
5037
+ this._log('info', `Creating meta field: ${metaField.name} in ${tableDef.name}`);
5038
+ await this._supsis.table.createField(tableId, metaField);
5039
+ } catch (e) {
5040
+ this._log('warn', `Failed to create meta field ${metaField.name}:`, e.message);
5041
+ }
5042
+ }
5043
+ }
5044
+ }
5045
+
5046
+ // Process user-defined fields
5047
+ for (const fieldDef of fields) {
5048
+ const fieldKey = `${tableUid}.${fieldDef.apiName || fieldDef.name}`;
5049
+
5050
+ // Check if field needs linked table that doesn't exist yet
5051
+ if (fieldDef.fieldType === 'linked-record') {
5052
+ const linkedTableUid = (fieldDef.options && fieldDef.options.linkedRecord)
5053
+ ? fieldDef.options.linkedRecord.table
5054
+ : null;
5055
+
5056
+ // Check if it's already a real ID or needs to be resolved
5057
+ const isRealId = linkedTableUid && /^[a-f0-9]{24}$/i.test(linkedTableUid);
5058
+ const linkedTable = linkedTableUid && !isRealId ? this._tableMap[linkedTableUid] : null;
5059
+
5060
+ // If not a real ID and table not yet created, queue for later
5061
+ if (!isRealId && !linkedTable && linkedTableUid) {
5062
+ // Queue for Phase 3
5063
+ this._pendingFields.push({
5064
+ tableUid: tableUid,
5065
+ tableId: tableId,
5066
+ tableName: tableDef.name,
5067
+ fieldDef: fieldDef,
5068
+ fieldKey: fieldKey
5069
+ });
5070
+ this._log('info', `Queued linked-record field '${fieldDef.name}' (waiting for ${linkedTableUid})`);
5071
+ continue;
5072
+ }
5073
+ }
5074
+
5075
+ await this._createOrUpdateField(tableId, tableDef.name, tableUid, fieldDef, existingFields, options);
5076
+ }
5077
+ }
5078
+ }
5079
+
5080
+ /**
5081
+ * Build pending linked-record fields (Phase 3)
5082
+ * @private
5083
+ */
5084
+ async _buildPendingFields(template, options) {
5085
+ if (this._pendingFields.length === 0) return;
5086
+
5087
+ this._log('phase', 'Building Fields (Phase 2 - Linked-record fields)');
5088
+
5089
+ for (const pending of this._pendingFields) {
5090
+ const { tableId, tableName, tableUid, fieldDef, fieldKey } = pending;
5091
+
5092
+ // Get existing fields
5093
+ let existingFields = [];
5094
+ if (!options.dryRun) {
5095
+ try {
5096
+ existingFields = await this._supsis.table.listFields(tableId);
5097
+ } catch (e) {
5098
+ // ignore
5099
+ }
5100
+ }
5101
+
5102
+ await this._createOrUpdateField(tableId, tableName, tableUid, fieldDef, existingFields, options);
5103
+ }
5104
+
5105
+ this._pendingFields = [];
5106
+ }
5107
+
5108
+ /**
5109
+ * Create or update a single field
5110
+ * @private
5111
+ */
5112
+ async _createOrUpdateField(tableId, tableName, tableUid, fieldDef, existingFields, options) {
5113
+ const apiName = fieldDef.apiName || this._toApiName(fieldDef.name);
5114
+ const fieldKey = `${tableUid}.${apiName}`;
5115
+
5116
+ // Check if field exists
5117
+ const existing = existingFields.find(f =>
5118
+ f.apiName === apiName || f.name === fieldDef.name
5119
+ );
5120
+
5121
+ // Resolve linked-record table/field IDs
5122
+ let resolvedOptions = fieldDef.options;
5123
+ if (fieldDef.fieldType === 'linked-record' && fieldDef.options && fieldDef.options.linkedRecord) {
5124
+ const linkedTableUid = fieldDef.options.linkedRecord.table;
5125
+ const labelFieldName = fieldDef.options.linkedRecord.labelField;
5126
+
5127
+ // Check if table is already a real ID (24 char hex string)
5128
+ const isTableRealId = /^[a-f0-9]{24}$/i.test(linkedTableUid);
5129
+ let resolvedTableId = linkedTableUid;
5130
+
5131
+ if (!isTableRealId) {
5132
+ // Try to resolve UID to real ID
5133
+ const linkedTable = this._tableMap[linkedTableUid];
5134
+
5135
+ if (!linkedTable) {
5136
+ this._log('error', `Cannot resolve linked table UID: ${linkedTableUid}`);
5137
+ this._log('error', `Available tables in map: ${Object.keys(this._tableMap).join(', ')}`);
5138
+ throw new AppBuilderBuildError(
5139
+ `Cannot create field '${fieldDef.name}': linked table '${linkedTableUid}' not found`,
5140
+ 'fields',
5141
+ { field: fieldDef.name, linkedTableUid: linkedTableUid, availableTables: Object.keys(this._tableMap) }
5142
+ );
5143
+ }
5144
+
5145
+ resolvedTableId = linkedTable._id;
5146
+ this._log('info', `Resolved linked table UID '${linkedTableUid}' -> ${resolvedTableId}`);
5147
+ }
5148
+
5149
+ // Resolve labelField to field ID
5150
+ let resolvedLabelFieldId = labelFieldName;
5151
+ if (labelFieldName && !/^[a-f0-9]{24}$/i.test(labelFieldName)) {
5152
+ // labelField is apiName, need to resolve to field ID
5153
+ try {
5154
+ const linkedTableFields = await this._supsis.table.listFields(resolvedTableId);
5155
+ const labelField = linkedTableFields.find(function(f) {
5156
+ return f.apiName === labelFieldName || f.slug === labelFieldName || f.name === labelFieldName;
5157
+ });
5158
+
5159
+ if (labelField) {
5160
+ resolvedLabelFieldId = labelField._id;
5161
+ this._log('info', `Resolved labelField '${labelFieldName}' -> ${resolvedLabelFieldId}`);
5162
+ } else {
5163
+ this._log('warn', `LabelField '${labelFieldName}' not found in table ${resolvedTableId}, using apiName`);
5164
+ }
5165
+ } catch (e) {
5166
+ this._log('warn', `Failed to resolve labelField '${labelFieldName}': ${e.message}`);
5167
+ }
5168
+ }
5169
+
5170
+ resolvedOptions = Object.assign({}, fieldDef.options, {
5171
+ linkedRecord: {
5172
+ table: resolvedTableId,
5173
+ labelField: resolvedLabelFieldId
5174
+ }
5175
+ });
5176
+ }
5177
+
5178
+ const fieldData = {
5179
+ name: fieldDef.name,
5180
+ apiName: apiName,
5181
+ fieldType: fieldDef.fieldType,
5182
+ icon: fieldDef.icon,
5183
+ alignment: fieldDef.alignment || 'left',
5184
+ defaultValue: fieldDef.defaultValue,
5185
+ options: resolvedOptions
5186
+ };
5187
+
5188
+ if (existing) {
5189
+ if (options.skipExisting) {
5190
+ this._log('info', `Field '${fieldDef.name}' in ${tableName} already exists, skipping...`);
5191
+ this._fieldMap[fieldKey] = existing;
5192
+ return;
5193
+ }
5194
+
5195
+ if (options.updateExisting) {
5196
+ if (options.dryRun) {
5197
+ this._log('info', `[DRY-RUN] Would update field: ${fieldDef.name} in ${tableName}`);
5198
+ } else {
5199
+ this._log('info', `Updating field: ${fieldDef.name} in ${tableName}`);
5200
+
5201
+ // Only update allowed fields (not fieldType)
5202
+ const updateData = {
5203
+ name: fieldDef.name,
5204
+ icon: fieldDef.icon,
5205
+ alignment: fieldDef.alignment,
5206
+ defaultValue: fieldDef.defaultValue,
5207
+ options: resolvedOptions
5208
+ };
5209
+
5210
+ const updated = await this._supsis.table.updateField(tableId, existing._id, updateData);
5211
+ this._fieldMap[fieldKey] = updated;
5212
+ this._log('success', `Field updated: ${fieldDef.name}`);
5213
+ }
5214
+ return;
5215
+ }
5216
+ }
5217
+
5218
+ // Create new field
5219
+ if (options.dryRun) {
5220
+ this._log('info', `[DRY-RUN] Would create field: ${fieldDef.name} in ${tableName}`);
5221
+ this._fieldMap[fieldKey] = { _id: `dry-run-${fieldKey}`, ...fieldData };
5222
+ } else {
5223
+ this._log('info', `Creating field: ${fieldDef.name} (${fieldDef.fieldType}) in ${tableName}`);
5224
+
5225
+ try {
5226
+ const created = await this._supsis.table.createField(tableId, fieldData);
5227
+ this._fieldMap[fieldKey] = created;
5228
+ this._log('success', `Field created: ${fieldDef.name} -> ${created._id}`);
5229
+ } catch (e) {
5230
+ this._log('error', `Failed to create field ${fieldDef.name}:`, e.message);
5231
+ throw new AppBuilderBuildError(
5232
+ `Failed to create field '${fieldDef.name}' in table '${tableName}'`,
5233
+ 'fields',
5234
+ { field: fieldDef, error: e.message }
5235
+ );
5236
+ }
5237
+ }
5238
+ }
5239
+
5240
+ /**
5241
+ * Convert name to API name (slug)
5242
+ * @private
5243
+ */
5244
+ _toApiName(name) {
5245
+ return name
5246
+ .toLowerCase()
5247
+ .replace(/ğ/g, 'g')
5248
+ .replace(/ü/g, 'u')
5249
+ .replace(/ş/g, 's')
5250
+ .replace(/ı/g, 'i')
5251
+ .replace(/ö/g, 'o')
5252
+ .replace(/ç/g, 'c')
5253
+ .replace(/[^a-z0-9]/g, '_')
5254
+ .replace(/_+/g, '_')
5255
+ .replace(/^_|_$/g, '');
5256
+ }
5257
+
5258
+ /**
5259
+ * Build modules
5260
+ * @private
5261
+ */
5262
+ async _buildModules(template, options) {
5263
+ this._log('phase', 'Building Modules');
5264
+
5265
+ const modules = template.modules || [];
5266
+ const appName = template.name;
5267
+
5268
+ for (const moduleDef of modules) {
5269
+ const uid = moduleDef.uid || `module.${moduleDef.apiName || this._toApiName(moduleDef.name)}`;
5270
+
5271
+ // Resolve table UID to ID
5272
+ const tableUid = moduleDef.table;
5273
+ const table = this._tableMap[tableUid];
5274
+
5275
+ if (!table) {
5276
+ this._log('error', `Table not found for module '${moduleDef.name}': ${tableUid}`);
5277
+ continue;
5278
+ }
5279
+
5280
+ // Check if module already exists
5281
+ const existingModule = await this._findExistingModule(appName, uid);
5282
+
5283
+ if (existingModule) {
5284
+ if (options.skipExisting) {
5285
+ this._log('info', `Module '${moduleDef.name}' already exists, skipping...`);
5286
+ this._moduleMap[uid] = existingModule;
5287
+ continue;
5288
+ }
5289
+
5290
+ if (options.updateExisting) {
5291
+ if (options.dryRun) {
5292
+ this._log('info', `[DRY-RUN] Would update module: ${moduleDef.name}`);
5293
+ } else {
5294
+ this._log('info', `Updating module: ${moduleDef.name}`);
5295
+ const updated = await this._supsis.module.update(existingModule.moduleName, {
5296
+ displayName: moduleDef.name,
5297
+ description: this._generateDescription(appName, uid, moduleDef.description),
5298
+ icon: moduleDef.icon,
5299
+ settings: moduleDef.settings
5300
+ });
5301
+ this._moduleMap[uid] = updated;
5302
+ }
5303
+ continue;
5304
+ }
5305
+ }
5306
+
5307
+ // Create new module
5308
+ if (options.dryRun) {
5309
+ this._log('info', `[DRY-RUN] Would create module: ${moduleDef.name}`);
5310
+ this._moduleMap[uid] = { _id: `dry-run-${uid}`, displayName: moduleDef.name };
5311
+ } else {
5312
+ this._log('info', `Creating module: ${moduleDef.name} -> table ${table._id}`);
5313
+
5314
+ const moduleData = {
5315
+ displayName: moduleDef.name,
5316
+ description: this._generateDescription(appName, uid, moduleDef.description),
5317
+ type: 'module',
5318
+ icon: moduleDef.icon || 'widgets',
5319
+ module: {
5320
+ table: table._id,
5321
+ settings: moduleDef.settings || {
5322
+ showInMainMenu: true,
5323
+ apiEnabled: true
5324
+ }
5325
+ }
5326
+ };
5327
+
5328
+ try {
5329
+ const created = await this._supsis.module.create(moduleData);
5330
+ this._moduleMap[uid] = created;
5331
+ this._log('success', `Module created: ${moduleDef.name} -> ${created._id}`);
5332
+ } catch (e) {
5333
+ this._log('error', `Failed to create module ${moduleDef.name}:`, e.message);
5334
+ throw new AppBuilderBuildError(
5335
+ `Failed to create module '${moduleDef.name}'`,
5336
+ 'modules',
5337
+ { module: moduleDef, error: e.message }
5338
+ );
5339
+ }
5340
+ }
5341
+ }
5342
+ }
5343
+
5344
+ /**
5345
+ * Build cache entries
5346
+ * @private
5347
+ */
5348
+ async _buildCache(template, options) {
5349
+ this._log('phase', 'Building Cache Entries');
5350
+
5351
+ const cache = template.cache || {};
5352
+ const appName = template.name;
5353
+
5354
+ for (const [key, value] of Object.entries(cache)) {
5355
+ // Validate key starts with app name
5356
+ if (!key.startsWith(appName)) {
5357
+ this._log('warn', `Skipping cache key '${key}' - must start with app name '${appName}'`);
5358
+ continue;
5359
+ }
5360
+
5361
+ if (options.dryRun) {
5362
+ this._log('info', `[DRY-RUN] Would write cache: ${key}`);
5363
+ } else {
5364
+ this._log('info', `Writing cache: ${key}`);
5365
+
5366
+ try {
5367
+ await this._supsis.cache.write(key, value, {
5368
+ type: 'public',
5369
+ dataType: this._inferCacheDataType(value)
5370
+ // No expiresAt = never expires
5371
+ });
5372
+ this._log('success', `Cache written: ${key}`);
5373
+ } catch (e) {
5374
+ this._log('warn', `Failed to write cache ${key}:`, e.message);
5375
+ }
5376
+ }
5377
+ }
5378
+ }
5379
+
5380
+ /**
5381
+ * Build seed data
5382
+ * @private
5383
+ */
5384
+ async _buildSeed(template, options) {
5385
+ this._log('phase', 'Building Seed Data');
5386
+
5387
+ const seed = template.seed || [];
5388
+
5389
+ for (const seedItem of seed) {
5390
+ const moduleName = seedItem.module;
5391
+ const uid = seedItem.uid;
5392
+
5393
+ // Find module
5394
+ let targetModule = null;
5395
+ for (const [mUid, mod] of Object.entries(this._moduleMap)) {
5396
+ if (mod.moduleName === moduleName || mod.apiName === moduleName) {
5397
+ targetModule = mod;
5398
+ break;
5399
+ }
5400
+ }
5401
+
5402
+ if (!targetModule) {
5403
+ // Try direct module name
5404
+ try {
5405
+ targetModule = await this._supsis.module.getByName(moduleName);
5406
+ } catch (e) {
5407
+ this._log('warn', `Module not found for seed: ${moduleName}`);
5408
+ continue;
5409
+ }
5410
+ }
5411
+
5412
+ // Resolve any UID references in data
5413
+ const resolvedData = this._resolveSeedDataUids(seedItem.data);
5414
+
5415
+ if (options.dryRun) {
5416
+ this._log('info', `[DRY-RUN] Would create seed record in ${moduleName}:`, resolvedData);
5417
+ if (uid) {
5418
+ this._seedMap[uid] = { _id: `dry-run-${uid}`, ...resolvedData };
5419
+ }
5420
+ } else {
5421
+ this._log('info', `Creating seed record in ${moduleName}`);
5422
+
5423
+ try {
5424
+ const created = await this._supsis.module.createRecord(targetModule.moduleName || moduleName, resolvedData);
5425
+
5426
+ if (uid) {
5427
+ this._seedMap[uid] = created;
5428
+ }
5429
+
5430
+ this._log('success', `Seed record created: ${created._id}`);
5431
+ } catch (e) {
5432
+ this._log('error', `Failed to create seed record in ${moduleName}:`, e.message);
5433
+ }
5434
+ }
5435
+ }
5436
+ }
5437
+
5438
+ /**
5439
+ * Resolve UID references in seed data
5440
+ * @private
5441
+ */
5442
+ _resolveSeedDataUids(data) {
5443
+ if (!data || typeof data !== 'object') return data;
5444
+
5445
+ const resolved = {};
5446
+
5447
+ for (const [key, value] of Object.entries(data)) {
5448
+ if (typeof value === 'string' && this._seedMap[value]) {
5449
+ // This is a UID reference to a previous seed record
5450
+ resolved[key] = this._seedMap[value]._id;
5451
+ } else if (typeof value === 'object' && value !== null) {
5452
+ resolved[key] = this._resolveSeedDataUids(value);
5453
+ } else {
5454
+ resolved[key] = value;
5455
+ }
5456
+ }
5457
+
5458
+ return resolved;
5459
+ }
5460
+
5461
+ /**
5462
+ * Store build manifest in cache
5463
+ * @private
5464
+ */
5465
+ async _storeBuildManifest(template, options) {
5466
+ this._log('phase', 'Storing Build Manifest');
5467
+
5468
+ const manifestKey = `${template.name}_manifest`;
5469
+
5470
+ const manifest = {
5471
+ name: template.name,
5472
+ version: template.version,
5473
+ description: template.description,
5474
+ builtAt: new Date().toISOString(),
5475
+ tables: Object.entries(this._tableMap).map(([uid, table]) => ({
5476
+ uid,
5477
+ id: table._id,
5478
+ name: table.name
5479
+ })),
5480
+ modules: Object.entries(this._moduleMap).map(([uid, mod]) => ({
5481
+ uid,
5482
+ id: mod._id,
5483
+ name: mod.displayName || mod.moduleName
5484
+ })),
5485
+ cacheKeys: Object.keys(template.cache || {}),
5486
+ template: template
5487
+ };
5488
+
5489
+ if (options.dryRun) {
5490
+ this._log('info', `[DRY-RUN] Would store manifest: ${manifestKey}`);
5491
+ } else {
5492
+ try {
5493
+ await this._supsis.cache.write(manifestKey, manifest, {
5494
+ type: 'public',
5495
+ dataType: 'json'
5496
+ });
5497
+ this._log('success', `Manifest stored: ${manifestKey}`);
5498
+ } catch (e) {
5499
+ this._log('warn', `Failed to store manifest:`, e.message);
5500
+ }
5501
+ }
5502
+
5503
+ return manifest;
5504
+ }
5505
+
5506
+ /**
5507
+ * Build application from template
5508
+ *
5509
+ * @param {AppTemplate} template - Application template
5510
+ * @param {BuildOptions} [options] - Build options
5511
+ * @returns {Promise<BuildResult>} Build result
5512
+ *
5513
+ * @example
5514
+ * const result = await appBuilder.build({
5515
+ * name: 'team-note-app',
5516
+ * version: '1.0.0',
5517
+ * tables: [
5518
+ * {
5519
+ * name: 'Notes',
5520
+ * uid: 'table.notes',
5521
+ * fields: [
5522
+ * { name: 'Title', fieldType: 'text' },
5523
+ * { name: 'Content', fieldType: 'long-text' }
5524
+ * ]
5525
+ * }
5526
+ * ],
5527
+ * modules: [
5528
+ * { name: 'Notes', table: 'table.notes' }
5529
+ * ]
5530
+ * });
5531
+ */
5532
+ async build(template, options) {
5533
+ options = Object.assign({
5534
+ dryRun: false,
5535
+ verbose: true,
5536
+ skipExisting: true,
5537
+ updateExisting: true
5538
+ }, options);
5539
+
5540
+ this._verbose = options.verbose;
5541
+
5542
+ // Reset state
5543
+ this._uidMap = {};
5544
+ this._tableMap = {};
5545
+ this._fieldMap = {};
5546
+ this._moduleMap = {};
5547
+ this._seedMap = {};
5548
+ this._pendingFields = [];
5549
+
5550
+ this._log('phase', `Building Application: ${template.name} v${template.version}`);
5551
+
5552
+ // Validate template
5553
+ this._validateTemplate(template);
5554
+ this._log('success', 'Template validation passed');
5555
+
5556
+ const startTime = Date.now();
5557
+
5558
+ try {
5559
+ // Phase 1: Create tables
5560
+ await this._buildTables(template, options);
5561
+
5562
+ // Phase 2: Create fields (non-linked)
5563
+ await this._buildFields(template, options);
5564
+
5565
+ // Phase 3: Create pending linked-record fields
5566
+ await this._buildPendingFields(template, options);
5567
+
5568
+ // Phase 4: Create modules
5569
+ await this._buildModules(template, options);
5570
+
5571
+ // Phase 5: Write cache entries
5572
+ await this._buildCache(template, options);
5573
+
5574
+ // Phase 6: Insert seed data
5575
+ await this._buildSeed(template, options);
5576
+
5577
+ // Phase 7: Store build manifest
5578
+ const manifest = await this._storeBuildManifest(template, options);
5579
+
5580
+ const duration = Date.now() - startTime;
5581
+
5582
+ this._log('phase', `Build Complete in ${duration}ms`);
5583
+
5584
+ return {
5585
+ success: true,
5586
+ dryRun: options.dryRun,
5587
+ duration,
5588
+ manifest,
5589
+ tables: this._tableMap,
5590
+ fields: this._fieldMap,
5591
+ modules: this._moduleMap,
5592
+ seeds: this._seedMap
5593
+ };
5594
+
5595
+ } catch (error) {
5596
+ this._log('error', 'Build failed:', error.message);
5597
+ throw error;
5598
+ }
5599
+ }
5600
+
5601
+ /**
5602
+ * Check if application has been built
5603
+ *
5604
+ * @param {string} appName - Application name
5605
+ * @param {string} [version] - Optional version to check
5606
+ * @returns {Promise<boolean|Object>} True/manifest if exists, false otherwise
5607
+ */
5608
+ async exists(appName, version) {
5609
+ const manifestKey = `${appName}_manifest`;
5610
+
5611
+ try {
5612
+ const manifest = await this._supsis.cache.read(manifestKey);
5613
+
5614
+ if (!manifest) return false;
5615
+
5616
+ if (version && manifest.version !== version) {
5617
+ return false;
5618
+ }
5619
+
5620
+ return manifest;
5621
+ } catch (e) {
5622
+ return false;
5623
+ }
5624
+ }
5625
+
5626
+ /**
5627
+ * Delete application by name
5628
+ *
5629
+ * @param {string} appName - Application name
5630
+ * @returns {Promise<DeleteResult>} Delete result
5631
+ */
5632
+ async deleteWithAppName(appName) {
5633
+ this._log('phase', `Deleting Application: ${appName}`);
5634
+
5635
+ const manifest = await this.exists(appName);
5636
+
5637
+ if (!manifest) {
5638
+ throw new AppBuilderValidationError(`Application '${appName}' not found`);
5639
+ }
5640
+
5641
+ return await this._deleteWithManifest(manifest);
5642
+ }
5643
+
5644
+ /**
5645
+ * Delete application by template
5646
+ *
5647
+ * @param {AppTemplate} template - Application template
5648
+ * @returns {Promise<DeleteResult>} Delete result
5649
+ */
5650
+ async deleteWithTemplate(template) {
5651
+ this._log('phase', `Deleting Application from Template: ${template.name}`);
5652
+
5653
+ // Build a manifest-like structure from template
5654
+ const manifest = {
5655
+ name: template.name,
5656
+ tables: (template.tables || []).map(t => ({ uid: t.uid })),
5657
+ modules: (template.modules || []).map(m => ({
5658
+ uid: m.uid || `module.${m.apiName || this._toApiName(m.name)}`
5659
+ })),
5660
+ cacheKeys: Object.keys(template.cache || {})
5661
+ };
5662
+
5663
+ return await this._deleteWithManifest(manifest);
5664
+ }
5665
+
5666
+ /**
5667
+ * Delete application using manifest
5668
+ * @private
5669
+ */
5670
+ async _deleteWithManifest(manifest) {
5671
+ const result = {
5672
+ deletedTables: [],
5673
+ deletedModules: [],
5674
+ deletedCacheKeys: [],
5675
+ errors: []
5676
+ };
5677
+
5678
+ const appName = manifest.name;
5679
+
5680
+ // Delete modules first
5681
+ for (const mod of (manifest.modules || [])) {
5682
+ try {
5683
+ const existing = await this._findExistingModule(appName, mod.uid);
5684
+ if (existing) {
5685
+ await this._supsis.module.delete(existing._id);
5686
+ result.deletedModules.push(mod.uid);
5687
+ this._log('success', `Deleted module: ${mod.uid}`);
5688
+ }
5689
+ } catch (e) {
5690
+ result.errors.push({ type: 'module', uid: mod.uid, error: e.message });
5691
+ this._log('error', `Failed to delete module ${mod.uid}:`, e.message);
5692
+ }
5693
+ }
5694
+
5695
+ // Delete tables
5696
+ for (const table of (manifest.tables || [])) {
5697
+ try {
5698
+ const existing = await this._findExistingTable(appName, table.uid);
5699
+ if (existing) {
5700
+ await this._supsis.table.delete(existing._id);
5701
+ result.deletedTables.push(table.uid);
5702
+ this._log('success', `Deleted table: ${table.uid}`);
5703
+ }
5704
+ } catch (e) {
5705
+ result.errors.push({ type: 'table', uid: table.uid, error: e.message });
5706
+ this._log('error', `Failed to delete table ${table.uid}:`, e.message);
5707
+ }
5708
+ }
5709
+
5710
+ // Delete cache entries
5711
+ for (const key of (manifest.cacheKeys || [])) {
5712
+ try {
5713
+ await this._supsis.cache.deleteByPattern(key);
5714
+ result.deletedCacheKeys.push(key);
5715
+ this._log('success', `Deleted cache: ${key}`);
5716
+ } catch (e) {
5717
+ result.errors.push({ type: 'cache', key, error: e.message });
5718
+ this._log('warn', `Failed to delete cache ${key}:`, e.message);
5719
+ }
5720
+ }
5721
+
5722
+ // Delete manifest
5723
+ try {
5724
+ await this._supsis.cache.deleteByPattern(`${appName}_manifest`);
5725
+ this._log('success', `Deleted manifest: ${appName}_manifest`);
5726
+ } catch (e) {
5727
+ this._log('warn', `Failed to delete manifest:`, e.message);
5728
+ }
5729
+
5730
+ this._log('phase', 'Delete Complete');
5731
+
5732
+ return result;
5733
+ }
5734
+ }
5735
+
5736
+ // ==================== TYPE DEFINITIONS ====================
5737
+
5738
+ /**
5739
+ * Application template
5740
+ * @typedef {Object} AppTemplate
5741
+ * @property {string} name - Application name (used for cache keys prefix)
5742
+ * @property {string} version - Semantic version (e.g., '1.0.0')
5743
+ * @property {string} [description] - Application description
5744
+ * @property {TableTemplate[]} [tables] - Table definitions
5745
+ * @property {ModuleTemplate[]} [modules] - Module definitions
5746
+ * @property {Object.<string, *>} [cache] - Cache key-value pairs (keys must start with app name)
5747
+ * @property {SeedItem[]} [seed] - Seed data items
5748
+ */
5749
+
5750
+ /**
5751
+ * Table template
5752
+ * @typedef {Object} TableTemplate
5753
+ * @property {string} name - Table name
5754
+ * @property {string} uid - Unique identifier (e.g., 'table.notes')
5755
+ * @property {string} [description] - Table description
5756
+ * @property {string} [icon] - Material icon name
5757
+ * @property {FieldTemplate[]} [fields] - Field definitions
5758
+ */
5759
+
5760
+ /**
5761
+ * Field template
5762
+ * @typedef {Object} FieldTemplate
5763
+ * @property {string} name - Field name
5764
+ * @property {string} [apiName] - API name (auto-generated if not provided)
5765
+ * @property {string} fieldType - Field type (see FieldType)
5766
+ * @property {string} [icon] - Material icon name
5767
+ * @property {'left'|'center'|'right'} [alignment] - Text alignment
5768
+ * @property {*} [defaultValue] - Default value
5769
+ * @property {Object} [options] - Field options (type-specific)
5770
+ */
5771
+
5772
+ /**
5773
+ * Module template
5774
+ * @typedef {Object} ModuleTemplate
5775
+ * @property {string} name - Module display name
5776
+ * @property {string} [apiName] - API name (auto-generated if not provided)
5777
+ * @property {string} [uid] - Unique identifier
5778
+ * @property {string} table - Table UID reference
5779
+ * @property {string} [description] - Module description
5780
+ * @property {string} [icon] - Material icon name
5781
+ * @property {Object} [settings] - Module settings
5782
+ * @property {boolean} [settings.showInMainMenu=true] - Show in main menu
5783
+ * @property {boolean} [settings.apiEnabled=true] - Enable API access
5784
+ */
5785
+
5786
+ /**
5787
+ * Seed data item
5788
+ * @typedef {Object} SeedItem
5789
+ * @property {string} module - Module name (apiName)
5790
+ * @property {Object} data - Record data
5791
+ * @property {string} [uid] - UID for referencing in other seed items
5792
+ */
5793
+
5794
+ /**
5795
+ * Build options
5796
+ * @typedef {Object} BuildOptions
5797
+ * @property {boolean} [dryRun=false] - Preview changes without executing
5798
+ * @property {boolean} [verbose=true] - Enable detailed logging
5799
+ * @property {boolean} [skipExisting=true] - Skip existing resources
5800
+ * @property {boolean} [updateExisting=true] - Update existing resources
5801
+ */
5802
+
5803
+ /**
5804
+ * Build result
5805
+ * @typedef {Object} BuildResult
5806
+ * @property {boolean} success - Build success status
5807
+ * @property {boolean} dryRun - Was this a dry run
5808
+ * @property {number} duration - Build duration in ms
5809
+ * @property {Object} manifest - Build manifest
5810
+ * @property {Object} tables - Created/updated tables
5811
+ * @property {Object} fields - Created/updated fields
5812
+ * @property {Object} modules - Created/updated modules
5813
+ * @property {Object} seeds - Created seed records
5814
+ */
5815
+
5816
+ /**
5817
+ * Delete result
5818
+ * @typedef {Object} DeleteResult
5819
+ * @property {string[]} deletedTables - Deleted table UIDs
5820
+ * @property {string[]} deletedModules - Deleted module UIDs
5821
+ * @property {string[]} deletedCacheKeys - Deleted cache keys
5822
+ * @property {Object[]} errors - Errors encountered
5823
+ */
5824
+
4191
5825
  /**
4192
5826
  * Supsis JS SDK
4193
5827
  *
@@ -4217,6 +5851,8 @@ class SupsisModuleAPI {
4217
5851
  */
4218
5852
 
4219
5853
  exports.AICall = AICall;
5854
+ exports.AppBuilderBuildError = AppBuilderBuildError;
5855
+ exports.AppBuilderValidationError = AppBuilderValidationError;
4220
5856
  exports.Asset = Asset;
4221
5857
  exports.BaseModule = BaseModule;
4222
5858
  exports.Cache = Cache;
@@ -4230,6 +5866,7 @@ exports.Module = Module;
4230
5866
  exports.SDK_VERSION = SDK_VERSION;
4231
5867
  exports.Site = Site;
4232
5868
  exports.SupsisAPIError = SupsisAPIError;
5869
+ exports.SupsisAppBuilder = SupsisAppBuilder;
4233
5870
  exports.SupsisJS = SupsisJS;
4234
5871
  exports.SupsisModuleAPI = SupsisModuleAPI;
4235
5872
  exports.Table = Table;