backend-plus 2.0.0-rc.2 → 2.0.0-rc.20

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/README.md CHANGED
@@ -6,11 +6,11 @@ Backend for the anti Pareto rule.
6
6
 
7
7
 
8
8
  ![stable](https://img.shields.io/badge/stability-stable-blue.svg)
9
- [![npm-version](https://img.shields.io/npm/v/backend-star.svg)](https://npmjs.org/package/backend-star)
10
- [![downloads](https://img.shields.io/npm/dm/backend-star.svg)](https://npmjs.org/package/backend-star)
11
- [![build](https://img.shields.io/travis/codenautas/backend-star/master.svg)](https://travis-ci.org/codenautas/backend-star)
12
- [![coverage](https://img.shields.io/coveralls/codenautas/backend-star/master.svg)](https://coveralls.io/r/codenautas/backend-star)
13
- [![dependencies](https://img.shields.io/david/codenautas/backend-star.svg)](https://david-dm.org/codenautas/backend-star)
9
+ [![npm-version](https://img.shields.io/npm/v/backend-plus.svg)](https://npmjs.org/package/backend-plus)
10
+ [![downloads](https://img.shields.io/npm/dm/backend-plus.svg)](https://npmjs.org/package/backend-plus)
11
+ [![build](https://img.shields.io/travis/codenautas/backend-plus/master.svg)](https://travis-ci.org/codenautas/backend-plus)
12
+ [![coverage](https://img.shields.io/coveralls/codenautas/backend-plus/master.svg)](https://coveralls.io/r/codenautas/backend-plus)
13
+ [![dependencies](https://img.shields.io/david/codenautas/backend-plus.svg)](https://david-dm.org/codenautas/backend-plus)
14
14
 
15
15
 
16
16
  language: ![English](https://raw.githubusercontent.com/codenautas/multilang/master/img/lang-en.png)
@@ -205,7 +205,7 @@ entry | usage
205
205
  -----------------------------|---------------
206
206
  server |
207
207
  .port | port where is listening
208
- .base-rul | base url added to domain name
208
+ .base-url | base url added to domain name
209
209
  .module-store |
210
210
  install | (see Spanish)
211
211
  .dump | (see Spanish)
@@ -161,5 +161,9 @@ a.disabled
161
161
  font-size 80%
162
162
  text-align right
163
163
  color #777
164
+ > .json-container
165
+ position sticky
166
+ top 5%
167
+ bottom 5%
164
168
 
165
169
 
@@ -152,7 +152,7 @@ myOwn.wScreens.procAux = {
152
152
  button.disabled=false;
153
153
  divProgressOutside.style.opacity=0.33;
154
154
  toggleProgress.disabled=false;
155
- labelProgress.textContent=resultOk?my.messages.completed:'error';
155
+ labelProgress.textContent=typeof resultOk == "string" ? resultOk : resultOk !== false ? my.messages.completed : 'error';
156
156
  }).catch(function(err){
157
157
  my.log(err);
158
158
  divProgress.textContent=err.message;
@@ -183,20 +183,15 @@ myOwn.i18n.messages.es=changing(myOwn.i18n.messages.es, {
183
183
  /** @param {string} text */
184
184
  function regex4search(text){
185
185
  return new RegExp(
186
- text.trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
186
+ text.toString().trim().replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
187
187
  // .replace(/"/g,"\\b")
188
188
  .replace(/[ñÑ]/g,'(?:gn|nn?i?|[ñÑ])')
189
- .replace(/[cCçÇ]/g,'[cçÇ]')
190
- .replace(/[áÁàÀäÄãÃ]/gi,'[AáÁàÀäÄãÃ]')
191
- .replace(/[éÉèÈëË]/gi,'[EéÉèÈëË]')
192
- .replace(/[íÍìÌïÏ]/gi,'[IíÍìÌïÏ]')
193
- .replace(/[óÓòÒöÖõÕ]/gi,'[OóÓòÒöÖõÕ]')
194
- .replace(/[úÚùÙüÜ]/gi,'[UúÚùÙüÜ]')
195
- .replace(/a/gi,'[AáÁàÀäÄãÃ]')
196
- .replace(/e/gi,'[EéÉèÈëË]')
197
- .replace(/i/gi,'[IíÍìÌïÏ]')
198
- .replace(/o/gi,'[OóÓòÒöÖõÕ]')
199
- .replace(/u/gi,'[UúÚùÙüÜ]')
189
+ .replace(/[cCçÇ]/g,'[cCçÇ]')
190
+ .replace(/[AáÁàÀäÄãÃ]/gi,'[AáÁàÀäÄãÃ]')
191
+ .replace(/[EéÉèÈëË]/gi,'[EéÉèÈëË]')
192
+ .replace(/[IíÍìÌïÏ]/gi,'[IíÍìÌïÏ]')
193
+ .replace(/[OóÓòÒöÖõÕ]/gi,'[OóÓòÒöÖõÕ]')
194
+ .replace(/[UúÚùÙüÜ]/gi,'[UúÚùÙüÜ]')
200
195
  // .replace(/\s+/g,'.*\\s+.*') mas estricto, exige el espacio
201
196
  .replace(/\s+/g,'.*')
202
197
  , 'i');
@@ -212,9 +207,9 @@ myOwn.comparatorParameterNull={
212
207
  '!=\u2205':true,
213
208
  }
214
209
  myOwn.comparator={
215
- '=':function(valueToCheck,condition){return valueToCheck == condition;},
216
- '~':function(valueToCheck,condition){return condition==null || regex4search(condition.toString()).test(valueToCheck);},
217
- '!~':function(valueToCheck,condition){return condition==null || !regex4search(condition.toString()).test(valueToCheck);},
210
+ '=':function(valueToCheck,condition){return sameValue(valueToCheck,condition);},
211
+ '~': function(valueToCheck,condition){return condition==null || condition instanceof Date ? Math.abs(condition-valueToCheck) <= 1000*60*60*24 : regex4search(condition).test(valueToCheck);},
212
+ '!~':function(valueToCheck,condition){return condition==null || condition instanceof Date ? Math.abs(condition-valueToCheck) > 1000*60*60*24 :!regex4search(condition).test(valueToCheck);},
218
213
  '/R/i':function(valueToCheck,condition){return condition==null || RegExp(condition,'i').test(valueToCheck);},
219
214
  '\u2205':function(valueToCheck,condition){return valueToCheck == null;},//\u2205 = conjunto vacío
220
215
  '!=\u2205':function(valueToCheck,condition){return valueToCheck != null;},//\u2205 = conjunto vacío
@@ -690,11 +685,18 @@ myOwn.tableGrid = function tableGrid(tableName, mainElement, opts){
690
685
  if (myOwn.config.config['grid-row-retain-moved-or-deleted'] && !force) {
691
686
  var depotsToRetain = grid.depots.filter(depot => depot.tick == tick);
692
687
  for (depot of depotsToRetain) {
688
+ depot['$refreshed'] = false
693
689
  if (depot.tr && depot.tr.getAttribute('not-here')) depot.tr.removeAttribute('not-here')
694
690
  }
695
691
  }
696
692
  while (depot = depotsToDelete.pop()) {
697
693
  depot.manager.displayAsDeleted(depot, force ? 'change-ff' : 'unknown');
694
+ if (myOwn.config.config['grid-row-retain-moved-or-deleted']) {
695
+ if(!depot['$refreshed']){
696
+ grid.retrieveRowAndRefresh(depot,{retrieveIgnoringWhere:true})
697
+ depot['$refreshed'] = true
698
+ }
699
+ }
698
700
  }
699
701
  }
700
702
  })
@@ -998,7 +1000,7 @@ myOwn.DataColumnGrid.prototype.thFilter = function thFilter(depot, iColumn){
998
1000
  var grid = this.grid;
999
1001
  var fieldDef = this.fieldDef;
1000
1002
  var fieldName=fieldDef.name;
1001
- depot.rowSymbols[fieldDef.name]=depot.rowSymbols[fieldDef.name]||'~';
1003
+ depot.rowSymbols[fieldDef.name]=depot.rowSymbols[fieldDef.name]||(fieldDef.typeName == 'text' ? '~' : '=');
1002
1004
  var filterImage=my.path.img+my.comparator.traductor[depot.rowSymbols[fieldDef.name]]+'.png';
1003
1005
  var th=html.td(this.cellAttributes({class:"autoFilter", "typed-controls-direct-input":true},{skipMandatory:true})).create();
1004
1006
  var symbolFilter=th;
@@ -1615,7 +1617,7 @@ myOwn.dialogDownload = function dialogDownload(grid){
1615
1617
  }else{
1616
1618
  separator='|';
1617
1619
  var trans={
1618
- '|':'\\|',
1620
+ '|':'\\x7C',
1619
1621
  '\\':'\\\\',
1620
1622
  '\r':'\\r',
1621
1623
  '\n':'\\n',
@@ -2246,7 +2248,8 @@ myOwn.TableGrid.prototype.createRowInsertElements = function createRowInsertElem
2246
2248
  }
2247
2249
  var depotForInsert = grid.createDepotFromRow({$allow:{delete:true, update:true}}, 'new');
2248
2250
  grid.connector.fixedFields.forEach(function(pair){
2249
- if(!pair.range && grid.def.field[pair.fieldName].inTable !== false){
2251
+ var fieldDef = grid.def.field[pair.fieldName];
2252
+ if(!pair.range && (fieldDef.inTable !== false || fieldDef.isPk && pair.value !=null)){
2250
2253
  depotForInsert.row[pair.fieldName] = pair.value;
2251
2254
  depotForInsert.rowPendingForUpdate[pair.fieldName] = pair.value;
2252
2255
  }
@@ -2385,7 +2388,8 @@ myOwn.TableGrid.prototype.displayGrid = function displayGrid(){
2385
2388
  fixedFields:grid.def.primaryKey.map(function(fieldName, i){
2386
2389
  return {fieldName:fieldName, value:depot.primaryKeyValues[i]};
2387
2390
  }),
2388
- pick:grid.def.pick
2391
+ pick:grid.def.pick,
2392
+ retrieveIgnoringWhere: opts?opts.retrieveIgnoringWhere:false
2389
2393
  }).then(function(result){
2390
2394
  grid.depotRefresh(depot,{updatedRow:result[0], sendedForUpdate:{}}, opts);
2391
2395
  })
@@ -0,0 +1,6 @@
1
+ create or replace function get_app_user() returns text
2
+ stable language sql
3
+ as
4
+ $sql$
5
+ select split_part(current_setting('application_name'),' ',1);
6
+ $sql$;
@@ -12,9 +12,7 @@ export type LangId = 'en'|'es'|'etc...';
12
12
 
13
13
  export type Server=net.Server;
14
14
 
15
- export interface CoreFunctionParameters{
16
- [key:string]: any
17
- }
15
+ export type CoreFunctionParameters<T extends Record<string, any>> = T;
18
16
 
19
17
  export type MarkdownDoc = 'markdown documentation with `` can content newlines. The identation of the first line is deleted in all others'|'etc...';
20
18
 
@@ -35,7 +33,7 @@ export type UploadedFileInfo={
35
33
 
36
34
  }
37
35
  export type CoreFunction = ((context: ProcedureContext, parameters: CoreFunctionParameters) => Promise<any>)
38
- | ((context: ProcedureContext, parameters: CoreFunctionParameters, files?:UploadedFileInfo[]) => Promise<any>);
36
+ | ((context: ProcedureContext, parameters: CoreFunctionParameters, files?:UploadedFileInfo[]) => Promise<any>);
39
37
 
40
38
  export interface ProcedureDef {
41
39
  action: string
@@ -78,7 +76,7 @@ export interface ContextForDump extends Context {
78
76
  forDump?:boolean
79
77
  }
80
78
 
81
- export type InformProgressFunction=(opts:Error|{data:any}|{start:any}|{message:string}|{message?:string, lengthComputable:boolean, loaded:number, total:number, force?:boolean})=>void
79
+ export type InformProgressFunction=(opts:Error|{data:any}|{start:any}|{message:string}|{idGroup?:string, message?:string, lengthComputable:boolean, loaded:number, total:number, force?:boolean})=>void
82
80
 
83
81
  export interface ProcedureContext extends Context{
84
82
  client:Client
@@ -136,7 +134,7 @@ export type MenuInfoPath={
136
134
  path:string
137
135
  } & MenuInfoMinimo;
138
136
  export interface ClientModuleDefinition{
139
- type:'js'|'css'|'ttf'
137
+ type:'js'|'css'|'ttf'|'mjs'
140
138
  module?:string // module where to search in node_modules (node_modules/module/modPath/file) to serve
141
139
  modPath?:string // path inside module where to find file to serve
142
140
  src?:string // full path where browser search file (path/file)
@@ -186,7 +184,7 @@ export type FieldDefinition = EditableDbDefinition & {
186
184
  serverSide?:boolean /* default:!clientSide if the value is retrived from the database */
187
185
  inTable?:boolean /* default:!clientSide && !sql.fields[...].expr. Is a real fisical field in the table */
188
186
  /* sizeByte?:number deprecated size in bytes for numbers */
189
- allowEmtpyText?:boolean /* if a text field accepts '' as a valid value */
187
+ allowEmptyText?:boolean /* if a text field accepts '' as a valid value */
190
188
  mobileInputType?:string
191
189
  extraRow?:number
192
190
  inexactNumber?:number /* default:depends on typeName if = means abs(x-v)<espilon
@@ -252,22 +250,24 @@ export type Constraint = {constraintType:string, consName?:string} & (
252
250
  {constraintType:'unique', fields:string[], where?:string} |
253
251
  {constraintType:'check', expr?:string}
254
252
  )
253
+ export type OtherTableDefs = Record<string,Partial<TableDefinition & {prefilledField:Record<string,any>}>>
255
254
  export type TableDefinition = EditableDbDefinition & {
256
255
  name:string
257
256
  elementName?:string
258
257
  tableName?:string
259
258
  schema?:string
260
259
  title?:string
261
- fields:FieldDefinition[],
262
- primaryKey:string[],
263
- refrescable?: boolean;
260
+ fields:FieldDefinition[]
261
+ primaryKey:string[]
262
+ refrescable?: boolean
264
263
  sql?:{
264
+ primaryKey4Delete?:string[]
265
265
  isTable?:boolean
266
266
  from?:string
267
267
  where?:string
268
268
  postCreateSqls?:string
269
- skipEnance?: boolean,
270
- isReferable?: boolean,
269
+ skipEnance?: boolean
270
+ isReferable?: boolean
271
271
  logicalDeletes?:{
272
272
  fieldName:string
273
273
  valueToDelete:string
@@ -276,24 +276,26 @@ export type TableDefinition = EditableDbDefinition & {
276
276
  fieldName:string
277
277
  }
278
278
  tableName?:string
279
+ tableName4Delete?:string
279
280
  fields?:{
280
281
  [k:string]:{
281
282
  expr:string
282
283
  }
283
- },
284
+ }
284
285
  orderBy?:string[]
285
286
  viewBody?:string
286
287
  insertIfNotUpdate?:boolean
287
288
  policies?:{
288
- all ?:{using?:string, check?:string}
289
- select?:{using?:string}
290
- insert?:{ check?:string}
291
- update?:{using?:string, check?:string}
292
- delete?:{using?:string}
289
+ all ?:{name?:string, using?:string, check?:string}
290
+ select?:{name?:string, using?:string}
291
+ insert?:{name?:string, check?:string}
292
+ update?:{name?:string, using?:string, check?:string}
293
+ delete?:{name?:string, using?:string}
293
294
  }
294
295
  join?:string
295
296
  constraintsDeferred?:boolean
296
- otherTableDefs?:Record<string,Partial<TableDefinition & {prefilledField:Record<string,any>}>>
297
+ otherTableDefs?:OtherTableDefs
298
+ setExpectedPkValues?:boolean
297
299
  }
298
300
  foreignKeys?:ForeignKey[]
299
301
  softForeignKeys?:ForeignKey[]
@@ -319,10 +321,9 @@ export type TableDefinition = EditableDbDefinition & {
319
321
  specialValidator?:string
320
322
  saveAfter?:boolean
321
323
  selfRefresh?:boolean
322
- filterColumns?:{column:string, operator:string, value:any}[],
324
+ filterColumns?:{column:string, operator:string, value:any}[]
323
325
  gridAlias?:string /* front-end css my-table = gridAlias */
324
326
  }
325
- export type OtherTableDefs = TableDefinition['sql']['otherTableDefs']
326
327
  export interface DetailTable { table?: string, fields: FieldsForConnectDetailTable, abr: string, label?: string, refreshParent?:boolean, refreshFromParent?:boolean, wScreen?:string, condition?:string }
327
328
  export type TableDefinitionFunction = (context: ContextForDump, opts?:any) => TableDefinition;
328
329
  export type TableItemDef=string|{name:string, path?:string, tableGenerator?:(context:TableContext)=>TableDefinition}
@@ -365,6 +366,129 @@ export interface Caches {
365
366
  procedures:Record<string, {timestamp:number, result:any}>
366
367
  }
367
368
 
369
+ export interface AppConfigBin { // executables in SO
370
+ "zip-password-parameter-flag": string // parameter to pass the password to the zipper
371
+ "zip-password-prefix": string // password prefix
372
+ "zip-fixed-parameters":string // fixed parameters to pass to zipper
373
+ }
374
+
375
+ export interface AppConfig {
376
+ package: {
377
+ version: string
378
+ }
379
+ server: {
380
+ "base-url": string // rool path in the url
381
+ port: number // port of the API services
382
+ "session-store": string // strategies to store session info
383
+ "ip-replacer": string // ip that can be not showed or deduced in logs
384
+ "silent-startup": boolean // less logs when startup
385
+ "kill-9": string // a way to kill from URL with a token
386
+ bitacoraSchema: string
387
+ bitacoraTableName: string
388
+ }
389
+ db: {
390
+ motor: 'postgresql'
391
+ database: string
392
+ user: string
393
+ password: string
394
+ schema: string
395
+ search_path: string
396
+ tablespace: string // for creation scripts
397
+ "min-version": string // min version of the motor needed
398
+ nodb: boolean // if there is no database needed in the app
399
+ no_login: boolean // if no login is needed. Used only for all public sites
400
+ "downloadable-backup-path": string // OS path of the encrypted downloadable backup
401
+ }
402
+ login: {
403
+ schema: string // schema of the user table
404
+ table: string // user table
405
+ userFieldname: string // fieldname in user table that stores the user name
406
+ passFieldname: string // fieldname in user table that stores the password hash
407
+ rolFieldname: string // fieldname in user table that stores the rol
408
+ unloggedLandPage: string // land page when there is no user logged when the backend has public services
409
+ noLoggedUrlPath: string // path of non logged users when the backend has no public services
410
+ "preserve-case": boolean // preserve the case of the user name
411
+ activeClausule: string // SQL expression over the user table to check if a user is active
412
+ lockedClausule: string // SQL expression over the user table to check if a user is locked
413
+ disableChangePassword: boolean // disallow password change
414
+ skipBitacora: boolean // don't register logins
415
+ keepAlive: number // secs to keep alive a session if only keep alive request where received
416
+ plus: {
417
+ userFieldName:string
418
+ store:{
419
+ module: string
420
+ }
421
+ }
422
+ forget: { // forget password configurations:
423
+ urlPath: string // url sent by mail. default: `/new-pass`
424
+ urlPathOk: string // confirmation page
425
+ mailFields: string[] // fields for the forget pass mail
426
+
427
+ }
428
+ "double-dragon": boolean // app user must match db user
429
+ }
430
+ install: {
431
+ "table-data-dir": string // SO path to the .tab files in the db creation script
432
+ dump: { // configuration of --dump-db, the db creation script
433
+ "drop-his": boolean // include drop schema his in the db creation script
434
+ db: {
435
+ owner: string
436
+ extensions: string[] // extensions to be installed (gist, pg_trgm, pgcrypto)
437
+ enances: 'file' // if the enances must be dumped in a separate file
438
+ // from here info to set the owner and replace owner and user used in devel when script creation
439
+ "owner4special-scripts": string
440
+ "user4special-scripts": string
441
+ "apply-generic-user-replaces": string
442
+ }
443
+ "admin-can-create-tables": boolean // for apps that allows the user to create tables
444
+ "skip-content": boolean // don't create data from "table-data-dir"
445
+ folders: string //
446
+ scripts: {
447
+ prepare: string // SO path to the prepare scripts that will be run before the functions creations and inserts
448
+ "post-adapt": string // SO path to the post-adapt scripts that will be run after data inserts (of .tab tables)
449
+ }
450
+ }
451
+ }
452
+ "client-setup": { // front-end config
453
+ title:string // title of the app (common sufix of the title bar)
454
+ }
455
+ log: {
456
+ "serve-content": never
457
+ req: {
458
+ "keep-alive": boolean
459
+ }
460
+ db: {
461
+ "last-error": boolean // store last db error in a log file
462
+ devel: boolean //
463
+ "on-demand": string // if log db level can be changed on the fly
464
+ until: string | Date // full log until...
465
+ results: boolean // if query results must be included in full db logs
466
+ }
467
+ session: boolean // if all session activity must be logged
468
+ }
469
+ devel: {
470
+ delay: number // msec avg random delay in API responses (to emulate slow nets)
471
+ "cache-content": boolean // if the cache header must be sent to the client (when no devel config the default is true)
472
+ forceShowAsEditable: boolean // force "editable" behavior in grids
473
+ }
474
+ mailer: { // config to send mails
475
+ conn: string // connection string
476
+ "mail-info": {} // static mail config
477
+ supervise: {
478
+ to: string // email addres of the supervisor
479
+ event: {
480
+ }
481
+ }
482
+ }
483
+ bin: AppConfigBin
484
+ data: {
485
+ transformers: {
486
+ text: string // define the inputTransformers for text comming from the fron-end via the API
487
+ }
488
+ }
489
+ skipUnknownFieldsAtImport: boolean // if unknown fields must be skipped by default in import
490
+ }
491
+
368
492
  export class AppBackend{
369
493
  procedures:ProcedureDef[]
370
494
  procedure:{ [key:string]:ProcedureDef }
@@ -372,7 +496,7 @@ export class AppBackend{
372
496
  getTableDefinition: TableDefinitionsGetters
373
497
  tableStructures: TableDefinitions
374
498
  db: MotorDb
375
- config: any
499
+ config: AppConfig
376
500
  rootPath: string
377
501
  caches:Caches
378
502
  fieldDomain:{[k:string]:Partial<FieldDefinition>}
@@ -420,7 +544,8 @@ export class AppBackend{
420
544
  i18n:{
421
545
  messages:Record<LangId,Record<string, string>>
422
546
  }
423
- shootDownBackend():Promise<void>
547
+ shutdownCallbackListAdd(param:{message:string, fun:()=>Promise<void>}):void
548
+ shutdownBackend():Promise<void>
424
549
  setLog(opts:{until:string, results?:boolean}):void
425
550
  getDataDumpTransformations(rawData:string):Promise<{rawData:string, prepareTransformationSql:string[], endTransformationSql:string[]}>
426
551
  }