5htp-core 0.2.2 → 0.2.4-1

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.
Files changed (50) hide show
  1. package/package.json +1 -1
  2. package/src/client/app/component.tsx +1 -2
  3. package/src/client/app/index.ts +2 -6
  4. package/src/client/assets/css/components/button.less +4 -3
  5. package/src/client/assets/css/components/card.less +26 -11
  6. package/src/client/assets/css/components/lists.less +1 -1
  7. package/src/client/assets/css/components/other.less +0 -11
  8. package/src/client/assets/css/components.less +17 -3
  9. package/src/client/assets/css/core.less +55 -0
  10. package/src/client/assets/css/theme.less +0 -4
  11. package/src/client/assets/css/utils/layouts.less +1 -1
  12. package/src/client/assets/css/utils/medias.less +0 -47
  13. package/src/client/components/Dialog/Manager.tsx +1 -4
  14. package/src/client/components/Dialog/card.tsx +1 -1
  15. package/src/client/components/Dialog/index.less +2 -2
  16. package/src/client/components/Form.ts +154 -0
  17. package/src/client/components/{Form → Form_old}/index.tsx +0 -0
  18. package/src/client/components/{Form → Form_old}/index.tsx.old +0 -0
  19. package/src/client/components/Select/index.tsx +159 -24
  20. package/src/client/components/containers/Popover/index.tsx +38 -120
  21. package/src/client/components/data/progressbar/circular/index.tsx +1 -3
  22. package/src/client/components/dropdown/index.tsx +8 -13
  23. package/src/client/components/inputv3/base.less +0 -1
  24. package/src/client/components/inputv3/base.tsx +17 -6
  25. package/src/client/components/inputv3/string/index.tsx +23 -10
  26. package/src/client/services/router/components/Page.tsx +16 -18
  27. package/src/client/services/router/components/router.tsx +3 -3
  28. package/src/client/services/router/index.tsx +17 -14
  29. package/src/client/services/router/request/api.ts +6 -3
  30. package/src/client/services/router/response/index.tsx +4 -0
  31. package/src/client/services/router/response/page.ts +2 -1
  32. package/src/common/router/index.ts +1 -1
  33. package/src/common/router/layouts.ts +38 -6
  34. package/src/common/router/register.ts +3 -9
  35. package/src/common/router/request/api.ts +3 -1
  36. package/src/common/validation/index.ts +1 -0
  37. package/src/common/validation/schema.ts +8 -3
  38. package/src/common/validation/validators.ts +21 -10
  39. package/src/server/app/index.ts +4 -1
  40. package/src/server/services/console/bugReporter.ts +2 -19
  41. package/src/server/services/database/connection.ts +8 -18
  42. package/src/server/services/database/index.ts +27 -19
  43. package/src/server/services/router/index.ts +3 -3
  44. package/src/server/services/router/request/api.ts +3 -0
  45. package/src/server/services/router/response/index.ts +7 -4
  46. package/src/server/services/router/response/mask/Filter.ts +16 -72
  47. package/src/server/services/router/response/mask/index.ts +4 -7
  48. package/src/server/services/router/response/page/document.tsx +2 -2
  49. package/src/server/services/router/response/page/index.tsx +2 -2
  50. package/src/server/services/schema/request.ts +4 -2
@@ -34,26 +34,24 @@ export default ({ page, isCurrent }: { page: Page, isCurrent?: boolean }) => {
34
34
 
35
35
  }, [page]);
36
36
 
37
- return (
37
+ /*
38
38
  <div
39
39
  class={"page" + (isCurrent ? ' current' : '')}
40
40
  id={page.chunkId === undefined ? undefined : 'page_' + page.chunkId}
41
41
  >
42
-
43
- {/* Make request parameters and api data accessible from the page component */}
44
- {page.renderer ? (
45
-
46
- <page.renderer
47
- // Services
48
- {...context}
49
- // URL params
50
- {...context.request.data}
51
- // API data
52
- {...apiData}
53
- />
54
-
55
- ) : null}
56
-
57
- </div>
58
- )
42
+ */
43
+
44
+ // Make request parameters and api data accessible from the page component
45
+ return page.renderer ? (
46
+
47
+ <page.renderer
48
+ // Services
49
+ {...context}
50
+ // URL params
51
+ {...context.request.data}
52
+ // API data
53
+ {...apiData}
54
+ />
55
+
56
+ ) : null
59
57
  }
@@ -77,8 +77,8 @@ export default ({ service: router }: { service: Router }) => {
77
77
  }
78
78
 
79
79
  // If if the layout changed
80
- const curLayout = currentRoute?.options.layout;
81
- const newLayout = newpage?.route.options.layout;
80
+ const curLayout = pages.current?.layout;
81
+ const newLayout = newpage?.layout;
82
82
  if (newLayout && curLayout && newLayout.path !== curLayout.path) {
83
83
 
84
84
  // TEMPORARY FIX: reload everything when we change layout
@@ -126,7 +126,7 @@ export default ({ service: router }: { service: Router }) => {
126
126
  return history?.listen(async (locationUpdate) => {
127
127
 
128
128
  // Load the concerned route
129
- const request = new ClientRequest(locationUpdate.location, context);
129
+ const request = new ClientRequest(locationUpdate.location, context.router);
130
130
  await resolvePage(request);
131
131
 
132
132
  // Scroll to the selected content via url hash
@@ -116,21 +116,24 @@ export type THookCallback<TRouter extends ClientRouter> = (request: ClientReques
116
116
 
117
117
  type THookName = 'location.change' | 'page.changed'
118
118
 
119
- type Config = {
119
+ type Config<TAdditionnalContext extends {} = {}> = {
120
120
  preload: string[], // List of globs
121
+ context: () => TAdditionnalContext
121
122
  }
122
123
 
123
124
  /*----------------------------------
124
125
  - ROUTER
125
126
  ----------------------------------*/
126
127
  export default class ClientRouter<
127
- TApplication extends ClientApplication = ClientApplication
128
- > extends Service<Config, ClientApplication> implements BaseRouter {
128
+ TApplication extends ClientApplication = ClientApplication,
129
+ TAdditionnalContext extends {} = {}
130
+ > extends Service<Config<TAdditionnalContext>, ClientApplication> implements BaseRouter {
129
131
 
130
- public ssrData = window["ssr"] as (TBasicSSrData | undefined);
132
+ // Context data
133
+ public context = window["ssr"] as (TBasicSSrData | undefined);
131
134
  public ssrRoutes = window["routes"] as TSsrUnresolvedRoute[];
132
135
 
133
- public constructor(app: TApplication, config: Config) {
136
+ public constructor(app: TApplication, config: Config<TAdditionnalContext>) {
134
137
 
135
138
  super(app, config);
136
139
  }
@@ -157,7 +160,7 @@ export default class ClientRouter<
157
160
 
158
161
  const loaders: TRoutesLoaders = { ...coreRoutes, ...appRoutes }
159
162
  let currentRoute: TUnresolvedRoute | undefined;
160
- debug && console.log(LogPrefix, `Indexing routes and finding the current route from ssr data:`, this.ssrData);
163
+ debug && console.log(LogPrefix, `Indexing routes and finding the current route from ssr data:`, this.context);
161
164
 
162
165
  // Associe la liste des routes (obtenue via ssr) à leur loader
163
166
  for (let routeIndex = 0; routeIndex < this.ssrRoutes.length; routeIndex++) {
@@ -195,9 +198,9 @@ export default class ClientRouter<
195
198
  if (currentRoute === undefined) {
196
199
 
197
200
  const isCurrentRoute = (
198
- this.ssrData !== undefined
201
+ this.context !== undefined
199
202
  &&
200
- route.chunk === this.ssrData.page.chunkId
203
+ route.chunk === this.context.page.chunkId
201
204
  );
202
205
 
203
206
  if (isCurrentRoute) {
@@ -212,7 +215,7 @@ export default class ClientRouter<
212
215
 
213
216
  public page(...args: TRegisterPageArgs): TRoute {
214
217
 
215
- const { path, options, controller, renderer } = getRegisterPageArgs(...args);
218
+ const { path, options, controller, renderer, layout } = getRegisterPageArgs(...args);
216
219
 
217
220
  // S'il s'agit d'une page, son id doit avoir été injecté via le plugin babel
218
221
  const id = options["id"];
@@ -230,7 +233,7 @@ export default class ClientRouter<
230
233
  ...defaultOptions,
231
234
  ...options
232
235
  },
233
- controller: (context: TClientOrServerContext) => new ClientPage(controller, renderer, context)
236
+ controller: (context: TClientOrServerContext) => new ClientPage(controller, renderer, context, layout)
234
237
  };
235
238
 
236
239
  this.routes.push(route);
@@ -343,15 +346,15 @@ export default class ClientRouter<
343
346
 
344
347
  // Restituate SSR response
345
348
  let apiData: {} = {}
346
- if (this.ssrData) {
349
+ if (this.context) {
347
350
 
348
351
  console.log("SSR Response restitution ...");
349
352
 
350
- request.user = this.ssrData.user || null;
353
+ request.user = this.context.user || null;
351
354
 
352
- request.data = this.ssrData.request.data;
355
+ request.data = this.context.request.data;
353
356
 
354
- apiData = this.ssrData.page.data || {};
357
+ apiData = this.context.page.data || {};
355
358
  }
356
359
 
357
360
  // Replacer api data par ssr data
@@ -127,7 +127,7 @@ export default class ApiClient implements ApiClientService {
127
127
 
128
128
  return await this.fetch<TData>(method, path, data, options).catch((e) => {
129
129
  this.app.handleError(e);
130
- throw e;
130
+ throw e; // Throw to break the loop
131
131
  })
132
132
  }
133
133
 
@@ -136,8 +136,9 @@ export default class ApiClient implements ApiClientService {
136
136
  // Pick the fetchers where the data is needed
137
137
  const fetchersToRun: TFetcherList = {};
138
138
  let fetchersCount: number = 0;
139
- for (const fetcherId in fetchers)
140
- if (!( fetcherId in alreadyLoadedData )) {
139
+ for (const fetcherId in fetchers)
140
+ // The fetcher can be undefined
141
+ if (!( fetcherId in alreadyLoadedData ) && fetchers[ fetcherId ]) {
141
142
  fetchersToRun[ fetcherId ] = fetchers[ fetcherId ]
142
143
  fetchersCount++;
143
144
  }
@@ -157,6 +158,8 @@ export default class ApiClient implements ApiClientService {
157
158
 
158
159
  });
159
160
 
161
+ // Errors will be catched in the caller
162
+
160
163
  return { ...alreadyLoadedData, ...fetchedData }
161
164
  }
162
165
 
@@ -42,6 +42,8 @@ export type TRouterContext<TRouter extends ClientRouter = ClientRouter, TApplica
42
42
  // Expose client application services (api, socket, ...)
43
43
  //TRouter["app"]
44
44
  TApplication
45
+ &
46
+ ReturnType<TRouter["config"]["context"]>
45
47
  )
46
48
 
47
49
  /*----------------------------------
@@ -80,6 +82,8 @@ export default class ClientPageResponse<
80
82
  request: this.request,
81
83
  route: this.route,
82
84
  api: this.request.api,
85
+
86
+ ...this.request.router.config.context()
83
87
  }
84
88
 
85
89
  context.context = context;
@@ -6,7 +6,7 @@
6
6
  import type { ComponentChild } from 'preact';
7
7
 
8
8
  // Core
9
- import type { TClientOrServerContext } from '@common/router';
9
+ import type { TClientOrServerContext, Layout } from '@common/router';
10
10
  import PageResponse, { TDataProvider, TFrontRenderer } from "@common/router/response/page";
11
11
 
12
12
  // Specific
@@ -32,6 +32,7 @@ export default class ClientPage<TRouter = ClientRouter> extends PageResponse<TRo
32
32
  public dataProvider: TDataProvider | null,
33
33
  public component: TFrontRenderer,
34
34
  public context: TClientOrServerContext,
35
+ public layout?: Layout,
35
36
 
36
37
  public route = context.route
37
38
  ) {
@@ -98,7 +98,7 @@ export type TRouteOptions = {
98
98
  // Access Restriction
99
99
  auth?: TUserRole | boolean,
100
100
  //form?: TSchema,
101
- layout?: false | Layout,
101
+ layout?: false | string, // The nale of the layout
102
102
 
103
103
  TESTING?: boolean,
104
104
  logging?: boolean,
@@ -6,6 +6,7 @@
6
6
  import type { ComponentChild } from 'preact';
7
7
  // Core
8
8
  import type { ClientContext } from '@/client/context';
9
+ import type { TRouteOptions } from '.';
9
10
  // App
10
11
  import layouts from '@/client/pages/**/_layout/index.tsx';
11
12
 
@@ -30,20 +31,51 @@ export type ImportedLayouts = {
30
31
  - UTILS
31
32
  ----------------------------------*/
32
33
  // TODO: getLayot only on server side, and pass the layout chunk id
33
- export const getLayout = (routePath: string | undefined): Layout | undefined => {
34
+ export const getLayout = (routePath: string, routeOptions?: TRouteOptions): Layout | undefined => {
35
+
36
+ if (routeOptions === undefined)
37
+ return undefined;
38
+ // W don't want a layout on this page
39
+ if (routeOptions.layout === false)
40
+ return undefined;
41
+
42
+ // options.id has been injected via the babel plugon
43
+ const chunkId = routeOptions["id"];
44
+ if (chunkId === undefined)
45
+ throw new Error(`ID has not injected for the following page route: ${routePath}`);
34
46
 
35
- //console.log(`[router][layouts] Get layout for "${routePath}".`);
36
-
37
47
  let layout: Layout | undefined;
38
- if (routePath) {
48
+
49
+ // Layout via name
50
+ if (routeOptions.layout !== undefined) {
51
+
52
+ const LayoutComponent = layouts[routeOptions.layout];
53
+ if (LayoutComponent === undefined)
54
+ throw new Error(`No layout found with ID: ${routeOptions.layout}. registered layouts: ${Object.keys(layouts)}`);
55
+
56
+ layout = {
57
+ path: routeOptions.layout,
58
+ Component: layouts[routeOptions.layout]
59
+ }
60
+
61
+ } else {
62
+
63
+ // Automatic layout via the nearest _layout folder
39
64
  for (const layoutPath in layouts)
40
- if (routePath === layoutPath || routePath.startsWith( layoutPath + '_' ))
65
+ if (
66
+ // The layout is nammed index when it's at the root (@/client/pages/_layout)
67
+ layoutPath === 'index'
68
+ // Exact match
69
+ || chunkId === layoutPath
70
+ // Parent
71
+ || chunkId.startsWith( layoutPath + '_' )
72
+ )
41
73
  layout = {
42
74
  path: layoutPath,
43
75
  Component: layouts[layoutPath]
44
76
  };
45
77
  }
46
-
78
+
47
79
  //console.log(`[router][layouts] Get layout for "${routePath}". Found:`, layout);
48
80
 
49
81
  return layout;
@@ -29,16 +29,10 @@ export const getRegisterPageArgs = (...args: TRegisterPageArgs) => {
29
29
  else
30
30
  ([path, options, controller, renderer] = args)
31
31
 
32
- // S'il s'agit d'une page, son id doit avoir été injecté via le plugin babel
33
- const chunkId = options["id"];
34
- if (chunkId === undefined)
35
- throw new Error(`ID has not injected for the following page route: ${path}`);
32
+ // Automatic layout form the nearest _layout folder
33
+ const layout = getLayout(path, options);
36
34
 
37
- // Bind layout
38
- if (options.layout !== false)
39
- options.layout = getLayout(chunkId);
40
-
41
- return { path, options, controller, renderer }
35
+ return { path, options, controller, renderer, layout }
42
36
 
43
37
  }
44
38
 
@@ -10,7 +10,9 @@ import type { FileToUpload } from '@client/components/inputv3/file';
10
10
  - TYPES
11
11
  ----------------------------------*/
12
12
 
13
- export type TFetcherList = { [id: string]: TFetcher }
13
+ // The fetcher can be undefined if we put a condition on it
14
+ // By example if we want to fetch an api endpoint only if the url contains a certain url parameter
15
+ export type TFetcherList = { [id: string]: TFetcher | undefined }
14
16
 
15
17
  export type TPostData = {[key: string]: PrimitiveValue}
16
18
 
@@ -1,3 +1,4 @@
1
1
  export { default as Schema } from './schema';
2
+ export type { TSchemaFields } from './schema';
2
3
  export { default as Validators } from './validators';
3
4
  export { default as Validator } from './validator';
@@ -14,6 +14,10 @@ import { default as Validator, EXCLUDE_VALUE } from './validator';
14
14
 
15
15
  export type TSchemaFields = { [fieldName: string]: Schema<{}> | Validator<any> }
16
16
 
17
+ type TSchemaOptions = {
18
+ opt?: boolean
19
+ }
20
+
17
21
  type TOptsValider = {
18
22
  debug?: boolean,
19
23
  throwError?: boolean,
@@ -46,7 +50,8 @@ const LogPrefix = '[schema][validator]';
46
50
  export default class Schema<TFields extends TSchemaFields> {
47
51
 
48
52
  public constructor(
49
- public fields: TFields
53
+ public fields: TFields,
54
+ public options: TSchemaOptions = {}
50
55
  ) {
51
56
 
52
57
  }
@@ -88,7 +93,7 @@ export default class Schema<TFields extends TSchemaFields> {
88
93
  opts.debug && console.warn(LogPrefix, '[' + champ + ']', 'Exclusion (pas présent dans le schéma)');
89
94
  continue;
90
95
  }
91
-
96
+
92
97
  const cheminA = [...chemin, champ]
93
98
  const cheminAstr = cheminA.join('.')
94
99
 
@@ -173,7 +178,7 @@ export default class Schema<TFields extends TSchemaFields> {
173
178
  if (nbErreurs !== 0 && opts.throwError === true) {
174
179
  throw new InputErrorSchema(erreurs);
175
180
  }
176
-
181
+
177
182
  opts.debug && console.log(LogPrefix, '', dataToValidate, '=>', output);
178
183
 
179
184
  return {
@@ -63,8 +63,10 @@ export default class SchemaValidator {
63
63
  return val;
64
64
  }, opts)
65
65
 
66
- public array = (subtype?: Validator<any[]>, { choice, ...opts }: TValidator<any[]> & {
67
- choice?: any[]
66
+ public array = (subtype?: Validator<any>, { choice, min, max, ...opts }: TValidator<any[]> & {
67
+ choice?: any[],
68
+ min?: number,
69
+ max?: number
68
70
  } = {}) => {
69
71
 
70
72
  if (subtype !== undefined)
@@ -72,12 +74,17 @@ export default class SchemaValidator {
72
74
 
73
75
  return new Validator<any[]>('array', (items, input, output, corriger) => {
74
76
 
75
- //console.log('VALIDER ARRAY', items, input);
76
-
77
+ // Type
77
78
  if (!Array.isArray(items))
78
- throw new InputError("This value must be an array.");
79
+ throw new InputError("This value must be a list.");
80
+
81
+ // Items number
82
+ if ((min !== undefined && items.length < min))
83
+ throw new InputError(`Please select at least ${min} items.`);
84
+ if ((max !== undefined && items.length > max))
85
+ throw new InputError(`Please select maximum ${max} items.`);
79
86
 
80
- // Verif items
87
+ // Verif each item
81
88
  if (subtype !== undefined) {
82
89
  if (false/*subtype instanceof Schema*/) {
83
90
 
@@ -85,9 +92,9 @@ export default class SchemaValidator {
85
92
 
86
93
  } else {
87
94
 
88
- for (let i = 0; i < items.length; i++)
89
- items[i] = subtype.validate( items[i], items, items, corriger );
90
-
95
+ items = items.map( item =>
96
+ subtype.validate( item, items, items, corriger )
97
+ )
91
98
  }
92
99
  }
93
100
 
@@ -101,7 +108,7 @@ export default class SchemaValidator {
101
108
  }
102
109
 
103
110
  public choice = (values: any[], opts: TValidator<any> & {} = {}) =>
104
- new Validator<any>('object', (val, input, output) => {
111
+ new Validator<any>('choice', (val, input, output) => {
105
112
 
106
113
  if (!values.includes(val))
107
114
  throw new InputError("Invalid value. Must be: " + values.join(', '));
@@ -116,6 +123,10 @@ export default class SchemaValidator {
116
123
  public string = ({ min, max, ...opts }: TValidator<string> & { min?: number, max?: number } = {}) =>
117
124
  new Validator<string>('string', (val, input, output, corriger?: boolean) => {
118
125
 
126
+ // Choice value from Select component
127
+ if (typeof val === 'object' && val.value)
128
+ val = val.value;
129
+
119
130
  if (val === '')
120
131
  return undefined;
121
132
  else if (typeof val === 'number')
@@ -87,7 +87,7 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
87
87
  ----------------------------------*/
88
88
 
89
89
  public env: TEnvConfig;
90
- public abstract identity: Config.Identity;
90
+ public identity: Config.Identity;
91
91
 
92
92
  public constructor() {
93
93
 
@@ -103,6 +103,9 @@ export default abstract class Application extends Service<Config, Hooks, /* TODO
103
103
  // Load config files
104
104
  const configParser = new ConfigParser( this.path.root );
105
105
  this.env = configParser.env();
106
+ this.identity = configParser.identity();
107
+
108
+ console.log(`[boot] Environment:`, this.env);
106
109
  }
107
110
 
108
111
  /*----------------------------------
@@ -170,7 +170,7 @@ export default class BugReporter {
170
170
  channelType,
171
171
  channelId,
172
172
  // User
173
- user: request?.user?.name,
173
+ user: request?.user?.email,
174
174
  ip: request?.ip,
175
175
  // Error
176
176
  error,
@@ -180,25 +180,8 @@ export default class BugReporter {
180
180
 
181
181
  await this.sendToTransporters(bugReport);
182
182
 
183
- // TODO: Move on App side
184
- /*if (app.isLoaded('sql'))
185
- // Memorize
186
- $.sql.insert('BugServer', {
187
- // Context
188
- hash: hash,
189
- date: now,
190
- channelType,
191
- channelId,
192
- // User
193
- user: request?.user?.name,
194
- ip: request?.ip,
195
- // Error
196
- stacktrace: error.stack || error.message,
197
- logs: logsHtml
198
- });*/
199
-
200
183
  // Update error message
201
- error.message = "A bug report has been sent to my personal mailbox. Sorry for the inconvenience.";
184
+ error.message = "We encountered an internal error, and our team has just been notified. Sorry for the inconvenience.";
202
185
  }
203
186
 
204
187
  public async application( report: AppBugInfos ) {
@@ -28,18 +28,10 @@ const LogPrefix = '[database][connection]';
28
28
 
29
29
  export type DatabaseServiceConfig = {
30
30
  list: string[],
31
- dev: {
32
- host: string,
33
- port: number,
34
- login: string,
35
- password: string,
36
- },
37
- prod: {
38
- host: string,
39
- port: number,
40
- login: string,
41
- password: string,
42
- }
31
+ host: string,
32
+ port: number,
33
+ login: string,
34
+ password: string,
43
35
  }
44
36
 
45
37
  export type THooks = {
@@ -113,15 +105,13 @@ export default class DatabaseConnection extends Service<DatabaseServiceConfig, T
113
105
 
114
106
  console.info(LogPrefix, `Connecting to databases ...`);
115
107
 
116
- const creds = this.config[ this.app.env.profile ];
117
-
118
108
  return await mysql.createPool({
119
109
 
120
110
  // Identification
121
- host: creds.host,
122
- port: creds.port,
123
- user: creds.login,
124
- password: creds.password,
111
+ host: this.config.host,
112
+ port: this.config.port,
113
+ user: this.config.login,
114
+ password: this.config.password,
125
115
  database: this.config.list[0],
126
116
 
127
117
  // Pool
@@ -279,7 +279,7 @@ export default class SQL extends Service<Config, Hooks, Application> {
279
279
  public update<TData extends TObjetDonnees>(
280
280
  tableName: string,
281
281
  data: TData[],
282
- where: (keyof TData)[],
282
+ where?: (keyof TData)[],
283
283
  opts?: TUpdateQueryOptions<TData>
284
284
  );
285
285
 
@@ -287,30 +287,42 @@ export default class SQL extends Service<Config, Hooks, Application> {
287
287
  public update<TData extends TObjetDonnees>(
288
288
  tableName: string,
289
289
  data: TData,
290
- where: (keyof TData)[] | TObjetDonnees,
290
+ where?: (keyof TData)[] | TObjetDonnees,
291
291
  opts?: TUpdateQueryOptions<TData>
292
292
  );
293
293
 
294
294
  public update<TData extends TObjetDonnees>(...args: [
295
295
  tableName: string,
296
296
  data: TData[],
297
- where: (keyof TData)[],
297
+ where?: (keyof TData)[],
298
298
  opts?: TUpdateQueryOptions<TData>
299
299
  ] | [
300
300
  tableName: string,
301
301
  data: TData,
302
- where: (keyof TData)[] | TObjetDonnees,
302
+ where?: (keyof TData)[] | TObjetDonnees,
303
303
  opts?: TUpdateQueryOptions<TData>
304
- ]) {
304
+ ]): Promise<ResultSetHeader> {
305
305
 
306
306
  let [tableName, data, where, opts] = args;
307
307
 
308
308
  // Multiple updates in one
309
309
  if (Array.isArray( data ))
310
310
  return this.database.query(
311
- data.map(record => this.update(tableName, record, where, opts)).join(';\n')
311
+ data.map( record =>
312
+ this.update(tableName, record, where, { ...opts, returnQuery: true })
313
+ ).join(';\n')
312
314
  )
313
315
 
316
+ // Automatic where based on pks
317
+ if (where === undefined) {
318
+
319
+ const tableMetas = this.database.getTable(tableName);
320
+ if (tableMetas.pk.length === 0)
321
+ throw new Error(`Tried to build the where condition based on the pks list, but no pk is attached to this table ${tableMetas.chemin}`);
322
+
323
+ where = tableMetas.pk;
324
+ }
325
+
314
326
  // No condition specified = use the pks
315
327
  if (Array.isArray(where)) {
316
328
  const whereColNames = where;
@@ -380,14 +392,9 @@ export default class SQL extends Service<Config, Hooks, Application> {
380
392
  let querySuffix: string = '';
381
393
 
382
394
  // Upsert
383
- if (opts.upsert !== undefined) {
384
- const upsertStatement = this.buildUpsertStatement<TData>(table, opts as With<TInsertQueryOptions<TData>, 'upsert'>);
385
- if (upsertStatement === null)
386
- opts.try = true;
387
- else
388
- querySuffix += ' ' + upsertStatement;
389
- }
390
-
395
+ if (opts.upsert !== undefined)
396
+ querySuffix += ' ' + this.buildUpsertStatement<TData>(table, opts as With<TInsertQueryOptions<TData>, 'upsert'>);
397
+
391
398
  // Create basic insert query
392
399
  const query = this.buildInsertStatement(table, data, opts) + querySuffix;
393
400
 
@@ -436,15 +443,14 @@ export default class SQL extends Service<Config, Hooks, Application> {
436
443
  private buildUpsertStatement<TData extends TObjetDonnees>(
437
444
  table: TMetasTable,
438
445
  opts: With<TInsertQueryOptions<TData>, 'upsert'>
439
- ): string | null {
446
+ ): string {
440
447
 
441
448
  const valuesToUpdate = this.getValuesToUpdate(table, opts.upsert);
442
449
 
443
450
  // All columns are ps
444
451
  const valuesToUpdatesEntries = Object.entries(valuesToUpdate);
445
452
  if (valuesToUpdatesEntries.length === 0)
446
- // Replace by insert ignore
447
- return null;
453
+ throw new Error(`You should provide at least one column to update in case of the record already exists.`);
448
454
 
449
455
  return 'ON DUPLICATE KEY UPDATE ' + valuesToUpdatesEntries.map(([ colName, value ]) =>
450
456
  '`' + colName + '` = ' + value
@@ -465,7 +471,9 @@ export default class SQL extends Service<Config, Hooks, Application> {
465
471
  if (colsToUpdate === '*') {
466
472
 
467
473
  console.log(LogPrefix, `Automatic upsert into ${table.chemin} using ${table.pk.join(', ')} as pk`);
468
- valuesNamesToUpdate = table.columnNamesButPk;
474
+ valuesNamesToUpdate = Object.keys(table.colonnes);// table.columnNamesButPk;
475
+ // We don't take columnNamesButPk, because if all the columns are pks, we don't have yny value for the ON DUPLICATE KEY
476
+ // Meaning
469
477
 
470
478
  } else if (Array.isArray( colsToUpdate )) {
471
479
 
@@ -479,7 +487,7 @@ export default class SQL extends Service<Config, Hooks, Application> {
479
487
  valuesToUpdate[ colKey ] = this.esc(customValuesToUpdate[ colKey ]);
480
488
 
481
489
  if (updateAll)
482
- valuesNamesToUpdate = table.columnNamesButPk;
490
+ valuesNamesToUpdate = Object.keys(table.colonnes);//table.columnNamesButPk;
483
491
 
484
492
  }
485
493