5htp 0.6.3-2 → 0.6.3-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.
@@ -5,7 +5,6 @@
5
5
  // Npm
6
6
  import * as types from '@babel/types'
7
7
  import type { PluginObj, NodePath } from '@babel/core';
8
- import generate from '@babel/generator';
9
8
 
10
9
  // Core
11
10
  import cli from '@cli';
@@ -206,7 +205,7 @@ function Plugin(babel, { app, side, debug }: TOptions) {
206
205
 
207
206
  Events.Create( form.data ).then(res => toast.success(res.message))
208
207
  =>
209
- api.post( '/api/events/create', form.data ).then(res => toast.success(res.message))
208
+ api.post( '/api/events/create', form.data ).then(res => toast.success(res.message)).catch(app.handleError)
210
209
  */
211
210
  if (side === 'client' && !clientServices.includes(serviceName)) {
212
211
 
@@ -227,6 +226,8 @@ function Plugin(babel, { app, side, debug }: TOptions) {
227
226
  ), apiPostArgs
228
227
  )
229
228
  )
229
+
230
+ ensureBackendServicePromiseCatch(path);
230
231
 
231
232
  /* [server] Backend Service calls
232
233
 
@@ -275,11 +276,254 @@ function Plugin(babel, { app, side, debug }: TOptions) {
275
276
  }
276
277
  }
277
278
 
279
+ function ensureBackendServicePromiseCatch(path: NodePath<types.CallExpression>) {
280
+
281
+ const chainRoot = getPromiseChainRoot(path);
282
+
283
+ // Only append if we are in a promise chain (e.g. .then(...))
284
+ if (chainRoot === path)
285
+ return;
286
+
287
+ if (isCatchWithAppHandleErrorArrow(chainRoot))
288
+ return;
289
+
290
+ if (isCatchWithAppHandleErrorMember(chainRoot)) {
291
+ const updatedCatch = t.callExpression(
292
+ chainRoot.node.callee,
293
+ [buildCatchHandler()]
294
+ );
295
+ chainRoot.replaceWith(updatedCatch);
296
+ return;
297
+ }
298
+
299
+ if (isCatchWithAppHandleErrorWrapped(chainRoot)) {
300
+ const updatedCatch = t.callExpression(
301
+ chainRoot.node.callee,
302
+ [buildCatchHandler()]
303
+ );
304
+ chainRoot.replaceWith(updatedCatch);
305
+ return;
306
+ }
307
+
308
+ const newChain = t.callExpression(
309
+ t.memberExpression(chainRoot.node, t.identifier('catch')),
310
+ [buildCatchHandler()]
311
+ );
312
+
313
+ chainRoot.replaceWith(newChain);
314
+ }
315
+
316
+ function buildCatchHandler(): types.ArrowFunctionExpression {
317
+ const errorIdentifier = t.identifier('e');
318
+ return t.arrowFunctionExpression(
319
+ [errorIdentifier],
320
+ t.callExpression(
321
+ t.memberExpression(t.identifier('app'), t.identifier('handleError')),
322
+ [t.identifier(errorIdentifier.name)]
323
+ )
324
+ );
325
+ }
326
+
327
+ function getPromiseChainRoot(path: NodePath<types.CallExpression>): NodePath<types.CallExpression> {
328
+
329
+ let current = path;
330
+
331
+ while (1) {
332
+
333
+ const member = current.parentPath;
334
+ if (!member?.isMemberExpression())
335
+ break;
336
+
337
+ if (member.node.object !== current.node)
338
+ break;
339
+
340
+ if (member.node.property.type !== 'Identifier')
341
+ break;
342
+
343
+ const propName = member.node.property.name;
344
+ if (!['then', 'catch', 'finally'].includes(propName))
345
+ break;
346
+
347
+ const call = member.parentPath;
348
+ if (!call?.isCallExpression())
349
+ break;
350
+
351
+ if (call.node.callee !== member.node)
352
+ break;
353
+
354
+ current = call;
355
+ }
356
+
357
+ return current;
358
+ }
359
+
360
+ function isCatchCall(path: NodePath<types.CallExpression>): boolean {
361
+
362
+ const callee = path.node.callee;
363
+ if (callee.type !== 'MemberExpression')
364
+ return false;
365
+
366
+ if (callee.property.type !== 'Identifier' || callee.property.name !== 'catch')
367
+ return false;
368
+
369
+ return true;
370
+ }
371
+
372
+ function isCatchWithAppHandleErrorMember(path: NodePath<types.CallExpression>): boolean {
373
+
374
+ if (!isCatchCall(path))
375
+ return false;
376
+
377
+ if (path.node.arguments.length !== 1)
378
+ return false;
379
+
380
+ const handler = path.node.arguments[0];
381
+ if (handler.type !== 'MemberExpression')
382
+ return false;
383
+
384
+ if (handler.object.type !== 'Identifier' || handler.object.name !== 'app')
385
+ return false;
386
+
387
+ if (handler.property.type !== 'Identifier' || handler.property.name !== 'handleError')
388
+ return false;
389
+
390
+ return true;
391
+ }
392
+
393
+ function isCatchWithAppHandleErrorArrow(path: NodePath<types.CallExpression>): boolean {
394
+
395
+ if (!isCatchCall(path))
396
+ return false;
397
+
398
+ if (path.node.arguments.length !== 1)
399
+ return false;
400
+
401
+ const handler = path.node.arguments[0];
402
+ if (handler.type !== 'ArrowFunctionExpression')
403
+ return false;
404
+
405
+ if (
406
+ handler.params.length !== 1
407
+ ||
408
+ handler.params[0].type !== 'Identifier'
409
+ )
410
+ return false;
411
+
412
+ const errorName = handler.params[0].name;
413
+
414
+ if (handler.body.type !== 'CallExpression')
415
+ return false;
416
+
417
+ const call = handler.body;
418
+ if (call.callee.type !== 'MemberExpression')
419
+ return false;
420
+
421
+ if (call.callee.object.type !== 'Identifier' || call.callee.object.name !== 'app')
422
+ return false;
423
+
424
+ if (call.callee.property.type !== 'Identifier' || call.callee.property.name !== 'handleError')
425
+ return false;
426
+
427
+ if (call.arguments.length !== 1)
428
+ return false;
429
+
430
+ const arg = call.arguments[0];
431
+ if (arg.type !== 'Identifier' || arg.name !== errorName)
432
+ return false;
433
+
434
+ return true;
435
+ }
436
+
437
+ function isCatchWithAppHandleErrorWrapped(path: NodePath<types.CallExpression>): boolean {
438
+
439
+ if (!isCatchCall(path))
440
+ return false;
441
+
442
+ if (path.node.arguments.length !== 1)
443
+ return false;
444
+
445
+ const handler = path.node.arguments[0];
446
+ if (handler.type !== 'ArrowFunctionExpression')
447
+ return false;
448
+
449
+ if (
450
+ handler.params.length !== 1
451
+ ||
452
+ handler.params[0].type !== 'Identifier'
453
+ )
454
+ return false;
455
+
456
+ const errorName = handler.params[0].name;
457
+
458
+ if (handler.body.type !== 'BlockStatement')
459
+ return false;
460
+
461
+ const statements = handler.body.body;
462
+ if (statements.length < 2)
463
+ return false;
464
+
465
+ const first = statements[0];
466
+ if (!(
467
+ first.type === 'ExpressionStatement'
468
+ &&
469
+ first.expression.type === 'CallExpression'
470
+ &&
471
+ first.expression.callee.type === 'MemberExpression'
472
+ &&
473
+ first.expression.callee.object.type === 'Identifier'
474
+ &&
475
+ first.expression.callee.object.name === 'console'
476
+ &&
477
+ first.expression.callee.property.type === 'Identifier'
478
+ &&
479
+ first.expression.callee.property.name === 'log'
480
+ &&
481
+ first.expression.arguments.length === 2
482
+ &&
483
+ first.expression.arguments[0].type === 'StringLiteral'
484
+ &&
485
+ first.expression.arguments[0].value === 'Error catched'
486
+ &&
487
+ first.expression.arguments[1].type === 'Identifier'
488
+ &&
489
+ first.expression.arguments[1].name === errorName
490
+ ))
491
+ return false;
492
+
493
+ const second = statements[1];
494
+ if (!(
495
+ second.type === 'ExpressionStatement'
496
+ &&
497
+ second.expression.type === 'CallExpression'
498
+ &&
499
+ second.expression.callee.type === 'MemberExpression'
500
+ &&
501
+ second.expression.callee.object.type === 'Identifier'
502
+ &&
503
+ second.expression.callee.object.name === 'app'
504
+ &&
505
+ second.expression.callee.property.type === 'Identifier'
506
+ &&
507
+ second.expression.callee.property.name === 'handleError'
508
+ &&
509
+ second.expression.arguments.length === 1
510
+ &&
511
+ second.expression.arguments[0].type === 'Identifier'
512
+ &&
513
+ second.expression.arguments[0].name === errorName
514
+ ))
515
+ return false;
516
+
517
+ return true;
518
+ }
519
+
278
520
  function ensureApiExposedInRootFunction(
279
521
  path: NodePath<types.CallExpression>,
280
522
  pluginState: TPluginState
281
523
  ) {
282
- if (path.scope.hasBinding('api'))
524
+ const needsApi = !path.scope.hasBinding('api');
525
+ const needsApp = !path.scope.hasBinding('app');
526
+ if (!needsApi && !needsApp)
283
527
  return;
284
528
 
285
529
  const rootFunctionPath = getRootFunctionPath(path);
@@ -292,13 +536,42 @@ function Plugin(babel, { app, side, debug }: TOptions) {
292
536
  if (!isProgramBodyLevelFunction(rootFunctionPath))
293
537
  return;
294
538
 
539
+ const existingContextObjectPattern = findUseContextDestructuringObjectPattern(rootFunctionPath);
540
+ if (existingContextObjectPattern) {
541
+
542
+ const existingKeys = new Set(
543
+ existingContextObjectPattern.properties
544
+ .filter((p): p is types.ObjectProperty =>
545
+ p.type === 'ObjectProperty'
546
+ && p.key.type === 'Identifier'
547
+ )
548
+ .map(p => (p.key as types.Identifier).name)
549
+ );
550
+
551
+ if (needsApi && !existingKeys.has('api')) {
552
+ existingContextObjectPattern.properties.push(
553
+ t.objectProperty(t.identifier('api'), t.identifier('api'), false, true),
554
+ );
555
+ }
556
+
557
+ if (needsApp && !existingKeys.has('app')) {
558
+ existingContextObjectPattern.properties.push(
559
+ t.objectProperty(t.identifier('app'), t.identifier('app'), false, true),
560
+ );
561
+ }
562
+
563
+ pluginState.needsUseContextImport = true;
564
+ return;
565
+ }
566
+
295
567
  if (pluginState.apiInjectedRootFunctions.has(rootFunctionPath.node))
296
568
  return;
297
569
 
298
570
  const exposeApiDeclaration = t.variableDeclaration('const', [
299
571
  t.variableDeclarator(
300
572
  t.objectPattern([
301
- t.objectProperty(t.identifier('api'), t.identifier('api'), false, true),
573
+ ...(needsApi ? [t.objectProperty(t.identifier('api'), t.identifier('api'), false, true)] : []),
574
+ ...(needsApp ? [t.objectProperty(t.identifier('app'), t.identifier('app'), false, true)] : []),
302
575
  ]),
303
576
  t.callExpression(t.identifier('useContext'), [])
304
577
  )
@@ -318,6 +591,32 @@ function Plugin(babel, { app, side, debug }: TOptions) {
318
591
  pluginState.needsUseContextImport = true;
319
592
  }
320
593
 
594
+ function findUseContextDestructuringObjectPattern(
595
+ rootFunctionPath: NodePath<types.Function | types.ArrowFunctionExpression>
596
+ ): types.ObjectPattern | undefined {
597
+
598
+ const body = rootFunctionPath.node.body;
599
+ if (body.type !== 'BlockStatement')
600
+ return;
601
+
602
+ for (const stmt of body.body) {
603
+ if (stmt.type !== 'VariableDeclaration' || stmt.kind !== 'const')
604
+ continue;
605
+
606
+ for (const declarator of stmt.declarations) {
607
+ if (declarator.id.type !== 'ObjectPattern')
608
+ continue;
609
+ if (!declarator.init || declarator.init.type !== 'CallExpression')
610
+ continue;
611
+ if (declarator.init.callee.type !== 'Identifier' || declarator.init.callee.name !== 'useContext')
612
+ continue;
613
+ if (declarator.init.arguments.length !== 0)
614
+ continue;
615
+ return declarator.id;
616
+ }
617
+ }
618
+ }
619
+
321
620
  function getRootFunctionPath(path: NodePath): NodePath<types.Function | types.ArrowFunctionExpression> | undefined {
322
621
 
323
622
  let functionPath = path.getFunctionParent();
@@ -472,8 +771,6 @@ function Plugin(babel, { app, side, debug }: TOptions) {
472
771
  relativeFileName = filename.substring( cli.paths.appRoot.length );
473
772
  if (filename.startsWith( cli.paths.coreRoot ))
474
773
  relativeFileName = filename.substring( cli.paths.coreRoot.length );
475
- /*if (filename.startsWith('/node_modules/5htp-core/'))
476
- relativeFileName = filename.substring( '/node_modules/5htp-core/'.length );*/
477
774
 
478
775
  // The file isn't a route definition
479
776
  if (relativeFileName === undefined) {
package/compiler/index.ts CHANGED
@@ -273,17 +273,14 @@ export default class Compiler {
273
273
 
274
274
  import { ${appClassIdentifier} as ${appClassIdentifier}Client } from "@/client";
275
275
  import ${appClassIdentifier}Server from "@/server/.generated/app";
276
-
277
- import { ApplicationProperties as ClientApplicationProperties } from "@client/app";
278
- import { ApplicationProperties as ServerApplicationProperties } from "@server/app";
279
-
280
- type ClientServices = Omit<${appClassIdentifier}Client, ClientApplicationProperties>;
281
- type ServerServices = Omit<${appClassIdentifier}Server, ServerApplicationProperties | keyof ClientServices>;
282
-
283
- type CombinedServices = ClientServices & ServerServices;
284
276
 
285
- const appClass: CombinedServices;
286
- export = appClass;
277
+ export const Router: ${appClassIdentifier}Client['Router'];
278
+
279
+ ${sortedServices.map(service => service.name !== 'Router'
280
+ ? `export const ${service.name}: ${appClassIdentifier}Server["${service.name}"];`
281
+ : ''
282
+ ).join('\n')}
283
+
287
284
  }
288
285
 
289
286
  declare module '@models/types' {
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "5htp",
3
3
  "description": "Convenient TypeScript framework designed for Performance and Productivity.",
4
- "version": "0.6.3-2",
4
+ "version": "0.6.3-4",
5
5
  "author": "Gaetan Le Gac (https://github.com/gaetanlegac)",
6
6
  "repository": "git://github.com/gaetanlegac/5htp.git",
7
7
  "license": "MIT",