backend-plus 2.0.0-rc.9 → 2.0.2

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.
@@ -97,7 +97,13 @@ class AppBackend{
97
97
  /** @type {{path:string}[]} */
98
98
  this.appStack=[];
99
99
  /** @type {{message:string; fun:()=>void}[]} */
100
- this.shootDownCallbackList=[];
100
+ this.shutdownCallbackList=[];
101
+ this.sessionStores={
102
+ file: SessionFileStore,
103
+ memory: memorystore,
104
+ memoryDevel: BindMemoryPerodicallySaved(this),
105
+ "memory-saved": BindMemoryPerodicallySaved(this),
106
+ }
101
107
  if(!this.rootPath){
102
108
  console.log('ATENCIÓN hay que poner be.rootPath antes de llamar a super()');
103
109
  this.rootPath=Path.resolve(__dirname,'..');
@@ -119,11 +125,14 @@ class AppBackend{
119
125
  this.tableStructures = {};
120
126
  this.configStaticConfig();
121
127
  /** @type {null|()=>Promise<void>} */
122
- this.shootDownBackend = null;
128
+ this.shutdownBackend = null;
123
129
  /** @type {bp.Server} */
124
130
  // @ts-ignore
125
131
  this.server = null;
126
132
  }
133
+ esJavascript(type){
134
+ return ['js','mjs'].includes(type)
135
+ }
127
136
  clearCaches(){
128
137
  this.caches={
129
138
  procedures:{}
@@ -358,6 +367,8 @@ AppBackend.prototype.i18n.messages.es={
358
367
  }
359
368
  };
360
369
 
370
+ function BindMemoryPerodicallySaved(be){
371
+ return (
361
372
  /**
362
373
  * @param {Express.Session} session
363
374
  */
@@ -406,7 +417,7 @@ function MemoryPerodicallySaved(session){
406
417
  console.log('dumping sessions every',dumpEverySeconds,'seconds');
407
418
  var errorsToShow=4;
408
419
  // TODO: hangs the server in devel mode
409
- setInterval(function(){
420
+ var interval = setInterval(function(){
410
421
  try{
411
422
  fs.writeFile(fileName,json4all.stringify(store.store.dump()));
412
423
  }catch(err){
@@ -417,18 +428,15 @@ function MemoryPerodicallySaved(session){
417
428
  }
418
429
  }
419
430
  },dumpEverySeconds*1000);
431
+ be.shutdownCallbackListAdd({
432
+ message:'session saver',
433
+ fun:()=>clearInterval(interval)
434
+ })
420
435
  });
421
436
  }
422
437
  }
423
438
  return MemoryDevelConstructor;
424
- }
425
-
426
- var sessionStores={
427
- file: SessionFileStore,
428
- memory: memorystore,
429
- memoryDevel: MemoryPerodicallySaved,
430
- "memory-saved": MemoryPerodicallySaved,
431
- }
439
+ })}
432
440
 
433
441
  /**
434
442
  * @param {string} text
@@ -456,7 +464,13 @@ AppBackend.prototype.setStaticConfig = function setStaticConfig(defConfigYamlStr
456
464
  }
457
465
 
458
466
  AppBackend.prototype.configList = function configList(){
459
- var list=[this.staticConfig];
467
+ var list=[
468
+ {
469
+ package:{
470
+ version: packagejson.version
471
+ }
472
+ },
473
+ this.staticConfig];
460
474
  if(!this.staticConfig["client-setup"]?.title && fs.existsSync(this.rootPath+'/def-config.yaml')){
461
475
  console.log('DEPRECATED!!!!!!')
462
476
  console.error('ERROR el def-config hay que ponerlo dentro de staticConfig');
@@ -504,6 +518,11 @@ AppBackend.prototype.canChangePass = async function canChangePass(reqOrContext,
504
518
  return be.isAdmin(reqOrContext);
505
519
  }
506
520
 
521
+ AppBackend.prototype.shutdownCallbackListAdd = function(messageFun){
522
+ this.shutdownCallbackList.push(messageFun);
523
+ }
524
+
525
+
507
526
  AppBackend.prototype._Browsers = {
508
527
  Edge: {short:'Ed' , minVer:14 , polly:true},
509
528
  Konqueror: {short:'Kq' , minVer:null, polly:true},
@@ -520,7 +539,7 @@ var imgExts = ['jpg', 'png', 'jpeg', 'ico', 'gif', 'svg']
520
539
 
521
540
  AppBackend.prototype.exts = {
522
541
  img: imgExts,
523
- normal: ['', 'js', 'map', 'html', 'css', ...imgExts, 'appcache', 'manifest', 'json', 'webmanifest', 'zip', 'pdf', ...fontExts, 'xlsx', 'csv']
542
+ normal: ['', 'js', 'map', 'html', 'css', ...imgExts, 'appcache', 'manifest', 'json', 'webmanifest', 'zip', 'pdf', ...fontExts, 'xlsx', 'csv', 'mjs']
524
543
  };
525
544
 
526
545
  /**
@@ -626,32 +645,33 @@ AppBackend.prototype.start = function start(opts){
626
645
  // @ts-ignore : only for testing */
627
646
  this.getMainApp = function getMainApp(){ return mainApp; };
628
647
  }
629
- this.shootDownBackend = function shootDownBackend(){
648
+ this.shutdownBackend = async function shutdownBackend(){
630
649
  console.log('shooting down:');
631
- return new Promise(function(resolve,reject){
632
- console.log('*','express server');
633
- be.server.close(/** @param {Error} err */function(err){
634
- if(err){
635
- console.log('*', err)
636
- reject(err);
637
- }else{
638
- resolve();
639
- }
640
- });
641
- }).then(async function(){
642
- while(be.shootDownCallbackList.length){
643
- var nextCallback=be.shootDownCallbackList.pop();
644
- if(nextCallback!=null){
645
- console.log('*',nextCallback.message);
646
- await nextCallback.fun();
647
- }
648
- }
649
- }).then(function(){
650
- console.log('pg pool balance',pg.poolBalanceControl());
651
- // @ts-ignore al hacer shootDown ya no hay mainApp.
652
- mainApp = null;
653
- logWhy && logWhy();
654
- });
650
+ var waitFor = [
651
+ new Promise(function(resolve,reject){
652
+ console.log('*','express server');
653
+ be.server.close(/** @param {Error} err */function(err){
654
+ if(err){
655
+ console.log('*', err)
656
+ reject(err);
657
+ }else{
658
+ console.log('*','express server done!')
659
+ resolve();
660
+ }
661
+ });
662
+ }),
663
+ ...(be.shutdownCallbackList.map(x => (async function(){
664
+ console.log('shut:', x.message)
665
+ await x.fun()
666
+ console.log('done:', x.message)
667
+ })()))
668
+ ];
669
+ console.log('*', 'waiting for all')
670
+ await Promise.all(waitFor);
671
+ console.log('*', 'all done')
672
+ mainApp = null;
673
+ console.log('* logWhy',logWhy)
674
+ logWhy && logWhy();
655
675
  };
656
676
  return Promise.resolve().then(function(){
657
677
  var configList=be.configList();
@@ -682,10 +702,10 @@ AppBackend.prototype.start = function start(opts){
682
702
  }
683
703
  var sessionStoreName=be.config.server["session-store"];
684
704
  if(sessionStoreName){
685
- if(config.devel && sessionStores[sessionStoreName+'Devel']){
705
+ if(config.devel && be.sessionStores[sessionStoreName+'Devel']){
686
706
  sessionStoreName+='Devel';
687
707
  }
688
- var storeModule = sessionStores[sessionStoreName];
708
+ var storeModule = be.sessionStores[sessionStoreName];
689
709
  be.config.login.plus.store.module = storeModule;
690
710
  }
691
711
  be.config.install.dump.db.owner=coalesce(be.config.db.owner,be.config.install.dump.db.owner,be.config.db.user);
@@ -963,12 +983,18 @@ AppBackend.prototype.start = function start(opts){
963
983
  loginPlusOpts.noLoggedUrlPath=be.config.login.unloggedLandPage;
964
984
  }
965
985
  mainApp.loginPlusManager.init(mainApp,changing(be.config.login.plus,loginPlusOpts));
966
- be.shootDownCallbackList.push({
986
+ be.shutdownCallbackListAdd({
967
987
  message:'login-plus manager',
968
988
  fun:function(){
969
989
  mainApp.loginPlusManager.closeManager();
970
990
  }
971
991
  });
992
+ be.shutdownCallbackListAdd({
993
+ message:'pg',
994
+ fun:function(){
995
+ pg.shutdown();
996
+ }
997
+ });
972
998
  mainApp.loginPlusManager.setValidatorStrategy(
973
999
  function(req, username, password, done) {
974
1000
  var client;
@@ -1211,13 +1237,14 @@ AppBackend.prototype.checkDatabaseStructure = async function checkDatabaseStruct
1211
1237
  }
1212
1238
  if(be.config.login?.forget){
1213
1239
  try{
1214
- await client.query(`select tokentype, info, due from tokens limit 1`).fetchOneRowIfExists();
1240
+ await client.query(`select tokentype, info, due from his.tokens limit 1`).fetchOneRowIfExists();
1215
1241
  }catch(err){
1216
1242
  var mensaje = `
1217
1243
  --------quizas falten los campos en la tabla tokens:
1218
1244
  alter table tokens add column tokentype text;
1219
1245
  alter table tokens add column info jsonb;
1220
1246
  alter table tokens add column due timestamp;
1247
+ --------quizas falte moverla al esquema his.
1221
1248
  `;
1222
1249
  err.message += mensaje;
1223
1250
  console.log(mensaje)
@@ -1528,17 +1555,17 @@ AppBackend.prototype.addProcedureServices = function addProcedureServices(forUnl
1528
1555
  });
1529
1556
  }else{
1530
1557
  if(progressInfo.lengthComputable){
1531
- if(new Date()-(context.lastMessageSended||0) > 500 || progressInfo.force){
1532
- context.lastMessageSended=new Date();
1558
+ if (!context.lastMessageSended) {
1559
+ context.lastMessageSended = {}
1560
+ }
1561
+ if(new Date()-(context.lastMessageSended[progressInfo.idGroup]||0) > 500 || progressInfo.force){
1562
+ context.lastMessageSended[progressInfo.idGroup]=new Date();
1533
1563
  res.write(JSON.stringify({progress:progressInfo})+"\n");
1534
1564
  if(progressInfo.total){
1535
1565
  var rate100=Math.floor(progressInfo.loaded*100/progressInfo.total);
1536
1566
  var rate1000=Math.floor(progressInfo.loaded*1000/progressInfo.total);
1537
- if(rate100<1 && progressInfo.loaded>0){
1538
- progress2send={message:'('+(rate1000||'½')+'‰)', ephemeral:true};
1539
- }else{
1540
- progress2send={message:rate100+'%', ephemeral:true};
1541
- }
1567
+ var message = rate100<1 && progressInfo.loaded>0 ? '('+(rate1000||'½')+'‰)' : rate100+'%';
1568
+ progress2send={message, ephemeral:true, idGroup:progressInfo.idGroup};
1542
1569
  }
1543
1570
  }else{
1544
1571
  progress2send=null;
@@ -1741,14 +1768,26 @@ function require_resolve(moduleName){
1741
1768
  return resolved;
1742
1769
  }
1743
1770
 
1744
- function resolve_module_dir(moduleName,path){
1771
+ function resolve_module_dir(moduleName,path,fileToCheck){
1745
1772
  var baseDir;
1746
1773
  if(packagejson.name==moduleName){
1747
1774
  baseDir=Path.join(process.cwd(), packagejson.main);
1748
1775
  }else{
1749
1776
  baseDir=require_resolve(moduleName);
1750
1777
  }
1751
- return Path.join(Path.dirname(baseDir),path||'');
1778
+ var resultDir = Path.join(Path.dirname(baseDir),path||'');
1779
+ if (fileToCheck) {
1780
+ fs.stat(Path.join(resultDir, fileToCheck)).then(function(status){
1781
+ if (!status) {
1782
+ console.error('Error resolve_module_dir in:',resultDir,'no:',fileToCheck);
1783
+ } else if(!status.isFile()) {
1784
+ console.error('Error resolve_module_dir in:',resultDir,',',fileToCheck,'is not a file');
1785
+ }
1786
+ },function(err){
1787
+ console.error('Error resolve_module_dir in:',resultDir,'err:',err);
1788
+ });
1789
+ }
1790
+ return resultDir;
1752
1791
  }
1753
1792
 
1754
1793
  AppBackend.prototype.optsGenericForFiles = function optsGenericForFiles(req, opts){
@@ -1911,6 +1950,9 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1911
1950
  // http://localhost:3033/img/login-logo-icon.png
1912
1951
  mainApp.get(Path.posix.join(baseUrl,'/img/login-logo-icon.png'), async function(req,res,next){
1913
1952
  var buscar = [
1953
+ 'unlogged/img/login-logo-icon.svg',
1954
+ 'dist/unlogged/img/login-logo-icon.svg',
1955
+ 'dist/client/unlogged/img/login-logo-icon.svg',
1914
1956
  'unlogged/img/login-logo-icon.png',
1915
1957
  'dist/unlogged/img/login-logo-icon.png',
1916
1958
  'dist/client/unlogged/img/login-logo-icon.png',
@@ -1938,7 +1980,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1938
1980
  if(baseUrl=='/'){
1939
1981
  baseUrl='';
1940
1982
  }
1941
- let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : moduleDef.type == 'js'? 'lib': 'css');
1983
+ let baseLib = baseUrl + '/' + (moduleDef.path ? moduleDef.path : be.esJavascript(moduleDef.type)? 'lib': 'css');
1942
1984
  try {
1943
1985
  var allowedExts=(moduleDef.type=='js'?['js','map']:[moduleDef.type]);
1944
1986
  mainApp.use(baseLib, serveContent(resolve_module_dir(moduleDef.module, moduleDef.modPath), { allowedExts }));
@@ -1952,7 +1994,7 @@ AppBackend.prototype.addUnloggedServices = function addUnloggedServices(mainApp,
1952
1994
  }
1953
1995
  }
1954
1996
  } catch (error) {
1955
- console.error('ERROR: No se pudo servir el módulo ' + moduleDef.module);
1997
+ console.error('ERROR: No se pudo servir el módulo', moduleDef.module);
1956
1998
  throw error;
1957
1999
  }
1958
2000
  })
@@ -2073,6 +2115,7 @@ AppBackend.prototype.clientIncludes = function clientIncludes(req, opts) {
2073
2115
 
2074
2116
  AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(req, opts) {
2075
2117
  let list = this.clientIncludes(req, opts);
2118
+ var be = this;
2076
2119
  if(this.config.devel && this.config.devel.useFileDevelopment){
2077
2120
  list=list.map(includeDef=>{
2078
2121
  if(includeDef.fileProduction){
@@ -2088,14 +2131,15 @@ AppBackend.prototype.clientIncludesCompleted = function clientIncludesCompleted(
2088
2131
  return list.map(inclusion =>{
2089
2132
  var filename = inclusion.file? inclusion.file: (inclusion.module? Path.basename(require_resolve(inclusion.module,rPaths)): '');
2090
2133
  return changing({
2091
- src: inclusion.type == 'js' ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2134
+ src: be.esJavascript(inclusion.type) ? ((inclusion.path ? inclusion.path : 'lib') + '/' + (inclusion.file ? inclusion.file : filename)) : '',
2092
2135
  href: inclusion.type == 'css' ? ((inclusion.path ? inclusion.path : 'css') + '/' + (inclusion.file ? inclusion.file : (inclusion.module + '.css'))) : ''
2093
2136
  }, inclusion);
2094
2137
  });
2095
2138
  }
2096
2139
 
2097
2140
  AppBackend.prototype.clientModules = function clientModules(req, opts) {
2098
- return { scripts: this.clientIncludesCompleted(req, opts).filter(x => x.type == 'js').map(mod => {return { src: mod.src }})};
2141
+ var be = this;
2142
+ return { scripts: this.clientIncludesCompleted(req, opts).filter(x => be.esJavascript(x.type)).map(mod => {return { src: mod.src, type:mod.type=='mjs'?"module":null }})};
2099
2143
  }
2100
2144
 
2101
2145
  /**
@@ -2638,6 +2682,30 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2638
2682
  }
2639
2683
  var schema=db.quoteIdent(be.config.db.schema);
2640
2684
  linesCreate.push("set role to "+owner+";");
2685
+ linesCreate.push(`do
2686
+ $do$
2687
+ declare
2688
+ vExpectedVersion text := '${be.config.db["min-version"]}';
2689
+ vCurrentVersion text := (select setting from pg_settings where name = 'server_version');
2690
+ begin
2691
+ if jsonb('['||replace(vCurrentVersion,'.',',')||']') < jsonb('['||replace(vExpectedVersion,'.',',')||']') then
2692
+ raise exception 'BACKEND-PLUS DUMP: Old PostrgreSQL Version % expected %', vCurrentVersion, vExpectedVersion;
2693
+ end if;
2694
+ end;
2695
+ $do$;
2696
+ `);
2697
+ linesCreate.push(`do
2698
+ $do$
2699
+ declare
2700
+ vExpected text := 'UTF8';
2701
+ vCurrent text := coalesce((select setting from pg_settings where name = 'server_encoding'),'unknown');
2702
+ begin
2703
+ if vCurrent is distinct from vExpected then
2704
+ raise exception 'BACKEND-PLUS DUMP: PostrgreSQL server_encoding %, expected %', vCurrent, vExpected;
2705
+ end if;
2706
+ end;
2707
+ $do$;
2708
+ `);
2641
2709
  linesCreate.push("drop schema if exists "+schema+' cascade;');
2642
2710
  if (be.config.install.dump["drop-his"]) {
2643
2711
  linesCreate.push("drop schema if exists his cascade;");
@@ -2796,6 +2864,9 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2796
2864
  case 'check':
2797
2865
  sql='check ('+cons.expr+')';
2798
2866
  break;
2867
+ case 'exclude':
2868
+ sql=`exclude using ${cons.using} (${cons.fields.map(f => typeof f == "string"?`${f} WITH =`:`${f.fieldName} WITH ${f.operator}`).join(', ')})`;
2869
+ break;
2799
2870
  default:
2800
2871
  throw new Error('constraintType not implemented: '+cons.constraintType);
2801
2872
  }
@@ -2860,8 +2931,14 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2860
2931
  if (err.code != 'ENOENT') throw err;
2861
2932
  try {
2862
2933
  var allTableContent = await fs.readFile('install/local-dump.psql','utf-8');
2863
- var startIndex = allTableContent.search(/\n-- Data for Name: /);
2864
- var lastIndex = allTableContent.search(/\nSELECT pg_catalog.setval/);
2934
+ var startIndex = allTableContent.indexOf('-- Data for Name: ');
2935
+ console.log('startIndex', startIndex);
2936
+ var lastUseful = allTableContent.lastIndexOf('\nSELECT pg_catalog.setval')
2937
+ console.log('lastUseful', lastUseful);
2938
+ if (lastUseful == -1) lastUseful = allTableContent.lastIndexOf('\n\\.\n');
2939
+ console.log('lastUseful', lastUseful);
2940
+ var lastIndex = allTableContent.indexOf('\n--', lastUseful);
2941
+ console.log('lastIndex', lastIndex);
2865
2942
  allTableData = allTableContent.slice(startIndex, lastIndex);
2866
2943
  } catch(err) {
2867
2944
  if (err.code != 'ENOENT') throw err;
@@ -2945,22 +3022,32 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2945
3022
  lines[0][0]=lines[0][0].replace('\ufeff','')
2946
3023
  }
2947
3024
  rows=lines.slice(1);
3025
+ var filteredFieldDef = lines[0].filter(filterField);
2948
3026
  if(tablesWithStrictSequence[tableName]){
2949
3027
  var dataString="COPY "+db.quoteIdent(tableName)+" ("+
2950
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
3028
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2951
3029
  ') FROM stdin;\n'+
2952
3030
  rows.map(function(line){
2953
- return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value){
2954
- return value===''?'\\N':value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
3031
+ return line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
3032
+ var def = tableDef.field[filteredFieldDef[i]];
3033
+ return value==='' ? (
3034
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? '' : '\\N'
3035
+ ): value.replace(/\\/g,'\\\\').replace(/\t/g,'\\t').replace(/\n/g,'\\n').replace(/\r/g,'\\r');
2955
3036
  }).join('\t')+'\n';
2956
3037
  }).join('')+'\\.\n';
2957
3038
  }else{
2958
3039
  var dataString="insert into "+db.quoteIdent(tableName)+" ("+
2959
- lines[0].filter(filterField).map(db.quoteIdent).join(', ')+
3040
+ filteredFieldDef.map(db.quoteIdent).join(', ')+
2960
3041
  ') values\n'+
2961
3042
  rows.map(function(line){
2962
- return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value){
2963
- return value===''?'null':db.quoteNullable(value);
3043
+ return "("+line.filter(function(_,i){ return filterField(lines[0][i]);}).map(function(value,i){
3044
+ var def = tableDef.field[filteredFieldDef[i]];
3045
+ if(def == null) {
3046
+ throw Error("no se encuentra la columna "+filteredFieldDef[i]+" en "+tableName);
3047
+ }
3048
+ return value==='' ? (
3049
+ def.allowEmptyText && ('nullable' in def) && !def.nullable ? "''" : 'null'
3050
+ ) : db.quoteNullable(value);
2964
3051
  }).join(', ')+")";
2965
3052
  }).join(',\n')+';\n';
2966
3053
  }
@@ -2985,27 +3072,33 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
2985
3072
  }
2986
3073
  let installFolders = be.config.install.dump.folders ?? ['install']
2987
3074
  let texts = await Promise.all(
2988
- ['prepare.sql','pre-adapt.sql','adapt.sql']
2989
- .concat(be.config.install.dump.scripts['prepare'])
2990
- .concat(be.config.install.dump.scripts['post-adapt'])
2991
- .map(async function(fileName){
2992
- if (!fileName) return '';
3075
+ [
3076
+ ['prepare.sql'],
3077
+ ['pre-adapt.sql'].concat(be.config.install.dump.scripts['pre-adapt']),
3078
+ ['adapt.sql'],
3079
+ be.config.install.dump.scripts['prepare'] ?? [],
3080
+ be.config.install.dump.scripts['post-adapt'] ?? []
3081
+ ]
3082
+ .map(async function(fileNames){
3083
+ if (!fileNames) return '';
2993
3084
  var i = 0;
2994
- var content;
2995
- do {
2996
- var folder = installFolders[i];
2997
- try{
2998
- content = await fs.readFile(be.rootPath+'/'+folder+'/'+fileName, {encoding:'UTF8'});
2999
- } catch (err) {
3000
- if(err.code!='ENOENT') throw err;
3001
- }
3002
- i++;
3003
- } while (i < installFolders.length && !content);
3004
- if (!content) {
3005
- return '-- no '+fileName+'\n';
3006
- } else {
3007
- return '-- '+folder+'/'+fileName+'\n'+content;
3008
- };
3085
+ return (await Promise.all(fileNames.map(async fileName => {
3086
+ var content;
3087
+ do {
3088
+ var folder = installFolders[i];
3089
+ try{
3090
+ content = await fs.readFile(be.rootPath+'/'+folder+'/'+fileName, {encoding:'UTF8'});
3091
+ } catch (err) {
3092
+ if(err.code!='ENOENT') throw err;
3093
+ }
3094
+ i++;
3095
+ } while (i < installFolders.length && !content);
3096
+ if (!content) {
3097
+ return '-- no '+fileName+'\n';
3098
+ } else {
3099
+ return '-- '+folder+'/'+fileName+'\n'+content;
3100
+ };
3101
+ }))).join('\n')
3009
3102
  })
3010
3103
  );
3011
3104
 
@@ -3035,7 +3128,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3035
3128
  '\n-- common'+
3036
3129
  common+'\n'+
3037
3130
  (complete? '\n\n--prepare.sql\n'+ texts[0]+'\n\n' :'' )+
3038
- (complete? texts.slice(3,3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3131
+ (complete? texts[3] + '\n\n' : '' )+
3039
3132
  '\n-- functions\n' + functionLines.join('\n')+
3040
3133
  '\n-- lines \n' + lines.join('\n')+
3041
3134
  (complete? ('\n\n-- pre-ADAPTs\n'+texts[1]+'\n\n') : '' )+
@@ -3045,7 +3138,7 @@ AppBackend.prototype.dumpDbSchemaPartial = async function dumpDbSchemaPartial(pa
3045
3138
  '\n-- FKs\n' + fkLines.join('\n')+
3046
3139
  '\n-- index\n' + indexLines.join('\n')+
3047
3140
  '\n-- policies\n' + policyLines.join('\n')+
3048
- (complete? texts.slice(3+prepareList.length).join('\n\n')+'\n\n' : '' )+
3141
+ (complete? texts[4] + '\n\n' : '' )+
3049
3142
  (complete? (be.config.install.dump.enances==='inline'?enancePart:'') :'')
3050
3143
  ).replace(/\uFEFF/g /*inner BOM replacing*/,'\n\n').replace(
3051
3144
  new RegExp(escapeRegExp(db.quoteIdent(be.config.install.dump.db["owner4special-scripts"])),'g'),
@@ -201,7 +201,7 @@ ProcedureTables = [
201
201
  tables:{},
202
202
  tableNames:{}
203
203
  };
204
- var expectedMainPrimaryKeyValues=parameters.primaryKeyValues;
204
+ var expectedMainPrimaryKeyValues = parameters.primaryKeyValues;
205
205
  /** @type { TableUpdater } */
206
206
  // @ts-expect-error parece no asignada pero al menos un nombre hay en el ciclo y por lo tanto va a tener valor
207
207
  var updateTarget = {}
@@ -257,6 +257,7 @@ ProcedureTables = [
257
257
  });
258
258
  var result;
259
259
  var mainResult;
260
+ var initialUpdatesLength = updates.length;
260
261
  while(updates.length){
261
262
  var {fieldNames, values, defTable, main} = notnull(updates.shift());
262
263
  var values4Insert = values.slice(0);
@@ -272,7 +273,7 @@ ProcedureTables = [
272
273
  values4Insert[i] = value;
273
274
  }
274
275
  }
275
- if(defField.generatedAs){
276
+ if(defField.generatedAs || defField.inTable === false){
276
277
  var i = fieldNames4Insert.indexOf(defField.name);
277
278
  if(i!==-1){
278
279
  fieldNames4Insert.splice(i,1);
@@ -284,10 +285,11 @@ ProcedureTables = [
284
285
  var primaryKeyValues=[]
285
286
  var primaryKeyValuesForUpdate=[]
286
287
  var primaryKeyValueObject={};
288
+ var fieldNames4InsertWithoutPkAddings = fieldNames4Insert.slice(0);
287
289
  defTable.primaryKey.forEach(function(name,i){
288
290
  var value = main?parameters.primaryKeyValues[i]:coalesce(parameters.newRow[name],parameters.oldRow[name],undefined);
291
+ var defField = defTable.field[name];
289
292
  if(value===undefined){
290
- var defField = defTable.field[name];
291
293
  value = defTable.prefilledField && name in defTable.prefilledField ? defTable.prefilledField[name] : (
292
294
  'defaultValue' in defField?defField.defaultValue:(
293
295
  'specialDefaultValue' in defField?new Error("my.specialDefaultValue[defField.specialDefaultValue](name, {row:{}}, {row:previousRow})"):undefined
@@ -304,13 +306,16 @@ ProcedureTables = [
304
306
  }else{
305
307
  primaryKeyValuesForUpdate[i]=value;
306
308
  }
307
- if(!fieldNames4Insert.includes(name) && value != null){
309
+ if(!fieldNames4Insert.includes(name) && value != null && defField.inTable !== false){
308
310
  fieldNames4Insert.push(name);
309
311
  values4Insert.push(value);
310
312
  }
311
313
  });
312
- if(main){
313
- expectedMainPrimaryKeyValues=primaryKeyValues;
314
+ if (main) {
315
+ expectedMainPrimaryKeyValues = primaryKeyValues;
316
+ } else if (defTable.sql.setExpectedPkValues) {
317
+ expectedMainPrimaryKeyValues = primaryKeyValues.slice(0);
318
+ while (expectedMainPrimaryKeyValues.length < mainDefTable.primaryKey.length) expectedMainPrimaryKeyValues.push(null);
314
319
  }
315
320
  var returningClausule='';
316
321
  if(opts && opts.forImport){
@@ -331,7 +336,9 @@ ProcedureTables = [
331
336
  return result;
332
337
  }
333
338
  var sql;
334
- if(defTable && parameters.status=='new'){
339
+ if (defTable?.sql?.isTable === false && initialUpdatesLength > 1 && fieldNames4InsertWithoutPkAddings.length == 0) {
340
+ // New feature. Ignore attempts to save to non tables when pseudo tables save in others
341
+ } else if(defTable && parameters.status=='new'){
335
342
  result = await insertFun();
336
343
  }else if(defTable && primaryKeyValues.length==defTable.primaryKey.length){
337
344
  var values4Update = [];
@@ -400,6 +407,12 @@ ProcedureTables = [
400
407
  var rowFrom = parameters.status!='new' && !plainUpdate.values.length?
401
408
  likeAr.toPlainObject(mainDefTable.primaryKey,expectedMainPrimaryKeyValues):
402
409
  (mainResult||result).row;
410
+ for (var fieldname of mainDefTable.primaryKey) {
411
+ if (rowFrom[fieldname] == null) {
412
+ var newValue = parameters.newRow[fieldname] ?? parameters.oldRow[fieldname];
413
+ if (newValue != null) rowFrom[fieldname] = newValue
414
+ }
415
+ }
403
416
  result = await be.queryValuesOfUniqueRow(context, mainDefTable, rowFrom);
404
417
  }
405
418
  return {command:insertOrUpdate, row:result.row};
@@ -417,16 +430,26 @@ ProcedureTables = [
417
430
  if(!defTable.allow.delete){
418
431
  throw changing(new Error("Deletes not allowed"),{status:403});
419
432
  }
420
- var primaryKeyFields=defTable.primaryKey4Delete||defTable.primaryKey;
421
- var primaryKeyValues=parameters.primaryKeyValues;
433
+ if (defTable.primaryKey4Delete) {
434
+ console.log("DEPRECATED!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! defTable.primaryKey4Delete", defTable.name);
435
+ }
436
+ var primaryKeyFields = defTable.primaryKey4Delete ?? defTable.sql.primaryKey4Delete ?? defTable.primaryKey;
437
+ var primaryKeyValues = [];
438
+ var tableName4Delete = defTable.sql.tableName4Delete ?? defTable.sql.tableName;
439
+ for (var fieldName of primaryKeyFields) {
440
+ var pos = defTable.primaryKey.indexOf(fieldName);
441
+ if (pos > -1) {
442
+ primaryKeyValues.push(parameters.primaryKeyValues[pos]);
443
+ }
444
+ }
422
445
  if(defTable && primaryKeyValues.length==primaryKeyFields.length){
423
446
  var sqlDelete;
424
447
  if(defTable.sql.logicalDeletes){
425
- sqlDelete="UPDATE "+be.db.quoteIdent(defTable.sql.tableName)+
448
+ sqlDelete="UPDATE "+be.db.quoteIdent(tableName4Delete)+
426
449
  " SET "+be.db.quoteIdent(defTable.sql.logicalDeletes.fieldName)+
427
450
  " = "+be.db.quoteNullable(defTable.sql.logicalDeletes.valueToDelete);
428
451
  }else{
429
- sqlDelete="DELETE FROM "+be.db.quoteIdent(defTable.sql.tableName);
452
+ sqlDelete="DELETE FROM "+be.db.quoteIdent(tableName4Delete);
430
453
  }
431
454
  sqlDelete+=" WHERE "+primaryKeyFields.map(function(fieldName, i){
432
455
  return be.db.quoteIdent(fieldName)+" = $"+(i+1);
@@ -453,6 +476,7 @@ ProcedureTables = [
453
476
  context.informProgress({message:be.messages.server.deleting})
454
477
  var be=context.be;
455
478
  var defTable=be.tableStructures[parameters.table](context);
479
+ var tableName4Delete = defTable.sql.tableName4Delete ?? defTable.sql.tableName;
456
480
  if(!defTable.allow.delete && !defTable.allow.deleteAll){
457
481
  throw changing(new Error("Deletes not allowed"),{status:403});
458
482
  }
@@ -480,7 +504,7 @@ ProcedureTables = [
480
504
  }
481
505
  });
482
506
  return context.client.query(
483
- "DELETE FROM "+be.db.quoteIdent(defTable.sql.tableName)+
507
+ "DELETE FROM "+be.db.quoteIdent(tableName4Delete)+
484
508
  " WHERE "+whereParts.join(" AND ")+" RETURNING 1",
485
509
  dataParams
486
510
  ).fetchUniqueRow().then(function(){
@@ -490,7 +514,7 @@ ProcedureTables = [
490
514
  };
491
515
  var result = await Promise.all(parameters.rowsToDelete.map(deleteOneRow)).then(function(){
492
516
  return context.client.query(
493
- "SELECT count(*) remaining_record_count FROM "+be.db.quoteIdent(defTable.sql.tableName)
517
+ "SELECT count(*) remaining_record_count FROM "+be.db.quoteIdent(tableName4Delete)
494
518
  ).fetchUniqueRow().then(function(result){
495
519
  context.informProgress({lengthComputable:true, loaded:deleteCounter, total:parameters.rowsToDelete.length, force:true});
496
520
  return result.row;
@@ -693,7 +717,7 @@ ProcedureTables = [
693
717
  {name: 'replaceNewLineWithSpace'},
694
718
  ],
695
719
  files:{count:1},
696
- coreFunction:async function(context, parameters, files){
720
+ coreFunction:async function table_upload(context, parameters, files){
697
721
  var be=context.be;
698
722
  var doing="opening file";
699
723
  var skipUnknownFieldsAtImport = coalesce(parameters.skipUnknownFieldsAtImport, be.config.skipUnknownFieldsAtImport);
@@ -823,6 +847,9 @@ ProcedureTables = [
823
847
  if(value != null && parameters.skipUnknownFieldsAtImport){
824
848
  value = value.replace(/(\s|\u00A0)+/g,' ').trim()
825
849
  }
850
+ if (value == null && field.allowEmptyText && ('nullable' in field) && !field.nullable ) {
851
+ value = ''
852
+ }
826
853
  newRow[field.name]=value;
827
854
  }
828
855
  });
@@ -902,6 +929,9 @@ ProcedureTables = [
902
929
  if(Number.isNaN(value)){
903
930
  value=null;
904
931
  }
932
+ if (value == null && field.allowEmptyText && ('nullable' in field) && !field.nullable ) {
933
+ value = ''
934
+ }
905
935
  if(field.defaultForOtherFields){
906
936
  addFieldToOthers(othersArray, field, value);
907
937
  }else{