@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.
- package/dist/supsis.cjs.js +1661 -24
- package/dist/supsis.esm.js +1659 -25
- package/dist/supsis.js +1661 -24
- package/dist/supsis.min.js +3 -3
- package/dist/supsis.min.js.map +1 -1
- package/package.json +1 -1
- package/types/index.d.ts +301 -0
package/dist/supsis.cjs.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Supsis JS SDK v1.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
|
-
|
|
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 {
|
|
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', '
|
|
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}
|
|
2718
|
-
* @property {
|
|
2719
|
-
* @property {
|
|
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 {
|
|
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
|
-
|
|
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
|
-
*
|
|
2788
|
-
*
|
|
2789
|
-
*
|
|
2790
|
-
*
|
|
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, ['
|
|
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}
|
|
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,
|
|
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
|
-
|
|
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}
|
|
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] -
|
|
2974
|
-
* @property {
|
|
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;
|