@rvoh/psychic 1.1.0 → 1.1.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.
package/CHANGELOG.md CHANGED
@@ -35,3 +35,7 @@ r.get('helloworld', (req, res, next) => {
35
35
  res.json({ hello: 'world' })
36
36
  })
37
37
  ```
38
+
39
+ ## 1.1.1
40
+
41
+ Fix route printing regression causing route printouts to show the path instead of the action
@@ -31,7 +31,7 @@ function buildExpressions(routes) {
31
31
  const beginningOfExpression = `${route.httpMethod.toUpperCase()}${' '.repeat(numMethodSpaces)}${formattedPath}`;
32
32
  const controllerRouteConf = route;
33
33
  const endOfExpression = controllerRouteConf.controller
34
- ? controllerRouteConf.controller.controllerActionPath(formattedPath)
34
+ ? controllerRouteConf.controller.controllerActionPath(controllerRouteConf.action)
35
35
  : 'middleware';
36
36
  return [beginningOfExpression, endOfExpression];
37
37
  });
@@ -18,7 +18,9 @@ function BeforeAction(opts = {}) {
18
18
  }
19
19
  if (!Object.getOwnPropertyDescriptor(psychicControllerClass, 'controllerHooks'))
20
20
  psychicControllerClass.controllerHooks = [...psychicControllerClass.controllerHooks];
21
- psychicControllerClass.controllerHooks.push(new hooks_js_1.ControllerHook(psychicControllerClass.name, methodName.toString(), opts));
21
+ if (!psychicControllerClass.controllerHooks.find(hook => hook.methodName === methodName)) {
22
+ psychicControllerClass.controllerHooks.push(new hooks_js_1.ControllerHook(psychicControllerClass.name, methodName.toString(), opts));
23
+ }
22
24
  });
23
25
  };
24
26
  }
@@ -7,7 +7,7 @@ exports.default = printFinalStepsMessage;
7
7
  const dream_1 = require("@rvoh/dream");
8
8
  const colorize_js_1 = __importDefault(require("../../../cli/helpers/colorize.js"));
9
9
  function printFinalStepsMessage(opts) {
10
- const importLine = (0, colorize_js_1.default)(`+ import { ${opts.exportName} } from '${opts.apiFile}'`, { color: 'green' });
10
+ const importLine = (0, colorize_js_1.default)(`+ import { ${opts.exportName} } from '${opts.outputFile}'`, { color: 'green' });
11
11
  const reducerLine = (0, colorize_js_1.default)(` + [${opts.exportName}.reducerPath]: ${opts.exportName}.reducer,`, {
12
12
  color: 'green',
13
13
  });
@@ -17,7 +17,11 @@ function printFinalStepsMessage(opts) {
17
17
  dream_1.DreamCLI.logger.log(`
18
18
  Finished generating redux bindings for your application,
19
19
  but to wire them into your app, we're going to need your help.
20
- Be sure to add the following to your root store to bind your new
20
+
21
+ First, you will need to be sure to sync, so that the new openapi
22
+ types are sent over to your client application.
23
+
24
+ Next, be sure to add the following to your root store to bind your new
21
25
  api module into your redux app, i.e.
22
26
 
23
27
  // add this line, fix the import path if necessary
@@ -54,10 +54,10 @@ async function writeInitializer({ exportName }) {
54
54
  const contents = `\
55
55
  import { DreamCLI } from '@rvoh/dream'
56
56
  import { PsychicApp } from '@rvoh/psychic'
57
- import AppEnv from '../AppEnv.js'
57
+ import AppEnv from '../../AppEnv.js'
58
58
 
59
59
  export default function initialize${pascalized}(psy: PsychicApp) {
60
- psy.on('sync', async () => {
60
+ psy.on('cli:sync', async () => {
61
61
  if (AppEnv.isDevelopmentOrTest) {
62
62
  DreamCLI.logger.logStartProgress(\`[${camelized}] syncing...\`)
63
63
  await DreamCLI.spawn('npx @rtk-query/codegen-openapi ${filePath}', {
@@ -50,7 +50,7 @@ import { PsychicApp } from "@rvoh/psychic"
50
50
  import AppEnv from '../AppEnv.js'
51
51
 
52
52
  export default (psy: PsychicApp) => {
53
- psy.on('sync', async () => {
53
+ psy.on('cli:sync', async () => {
54
54
  if (AppEnv.isDevelopmentOrTest) {
55
55
  DreamCLI.logger.logStartProgress(\`[${hyphenized}] extracting types from ${openapiFilepath} to ${outfile}...\`)
56
56
  await DreamCLI.spawn('npx openapi-typescript ${openapiFilepath} -o ${outfile}')
@@ -12,6 +12,7 @@ exports.namespacedControllerActionString = namespacedControllerActionString;
12
12
  exports.lookupControllerOrFail = lookupControllerOrFail;
13
13
  exports.applyResourcesAction = applyResourcesAction;
14
14
  exports.applyResourceAction = applyResourceAction;
15
+ exports.convertRouteParams = convertRouteParams;
15
16
  const dream_1 = require("@rvoh/dream");
16
17
  const cannot_find_inferred_controller_from_provided_namespace_js_1 = __importDefault(require("../error/router/cannot-find-inferred-controller-from-provided-namespace.js"));
17
18
  const cannot_infer_controller_from_top_level_route_js_1 = __importDefault(require("../error/router/cannot-infer-controller-from-top-level-route.js"));
@@ -123,6 +124,15 @@ function applyResourceAction(path, action, routingMechanism, options) {
123
124
  throw new Error(`unsupported resource method type: ${action}`);
124
125
  }
125
126
  }
127
+ /**
128
+ * Converts OpenAPI-style route parameters to Express.js-style parameters
129
+ * Example: users/{userId}/abc/{id} -> users/:userId/abc/:id
130
+ */
131
+ function convertRouteParams(path) {
132
+ // Use a more specific regex to avoid polynomial time complexity
133
+ // Only match valid parameter names (alphanumeric + underscore, 1-50 chars)
134
+ return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]{0,49})\}/g, ':$1');
135
+ }
126
136
  function httpMethodFromResourcefulAction(action) {
127
137
  switch (action) {
128
138
  case 'index':
@@ -74,6 +74,7 @@ class PsychicRouter {
74
74
  return '/' + this.currentNamespacePaths.join('/') + '/' + str;
75
75
  }
76
76
  crud(httpMethod, path, controllerOrMiddleware, action) {
77
+ this.checkPathForInvalidChars(path);
77
78
  const isMiddleware = typeof controllerOrMiddleware === 'function' &&
78
79
  !controllerOrMiddleware?.isPsychicController;
79
80
  // devs can provide custom express middleware which bypasses
@@ -98,6 +99,18 @@ class PsychicRouter {
98
99
  });
99
100
  }
100
101
  }
102
+ checkPathForInvalidChars(path) {
103
+ if (path.includes('{'))
104
+ throw new Error(`
105
+ The provided route "${path}" contains characters that are not supported.
106
+ If you are trying to write a uri param, you will need to use expressjs
107
+ param syntax, which is a prefixing colon, rather than using brackets
108
+ to surround the param.
109
+
110
+ provided route: "${path}"
111
+ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
112
+ `);
113
+ }
101
114
  namespace(namespace, cb) {
102
115
  const nestedRouter = new PsychicNestedRouter(this.app, this.config, this.routeManager, {
103
116
  namespaces: this.currentNamespaces,
@@ -25,7 +25,7 @@ function buildExpressions(routes) {
25
25
  const beginningOfExpression = `${route.httpMethod.toUpperCase()}${' '.repeat(numMethodSpaces)}${formattedPath}`;
26
26
  const controllerRouteConf = route;
27
27
  const endOfExpression = controllerRouteConf.controller
28
- ? controllerRouteConf.controller.controllerActionPath(formattedPath)
28
+ ? controllerRouteConf.controller.controllerActionPath(controllerRouteConf.action)
29
29
  : 'middleware';
30
30
  return [beginningOfExpression, endOfExpression];
31
31
  });
@@ -11,7 +11,9 @@ export function BeforeAction(opts = {}) {
11
11
  }
12
12
  if (!Object.getOwnPropertyDescriptor(psychicControllerClass, 'controllerHooks'))
13
13
  psychicControllerClass.controllerHooks = [...psychicControllerClass.controllerHooks];
14
- psychicControllerClass.controllerHooks.push(new ControllerHook(psychicControllerClass.name, methodName.toString(), opts));
14
+ if (!psychicControllerClass.controllerHooks.find(hook => hook.methodName === methodName)) {
15
+ psychicControllerClass.controllerHooks.push(new ControllerHook(psychicControllerClass.name, methodName.toString(), opts));
16
+ }
15
17
  });
16
18
  };
17
19
  }
@@ -1,7 +1,7 @@
1
1
  import { DreamCLI } from '@rvoh/dream';
2
2
  import colorize from '../../../cli/helpers/colorize.js';
3
3
  export default function printFinalStepsMessage(opts) {
4
- const importLine = colorize(`+ import { ${opts.exportName} } from '${opts.apiFile}'`, { color: 'green' });
4
+ const importLine = colorize(`+ import { ${opts.exportName} } from '${opts.outputFile}'`, { color: 'green' });
5
5
  const reducerLine = colorize(` + [${opts.exportName}.reducerPath]: ${opts.exportName}.reducer,`, {
6
6
  color: 'green',
7
7
  });
@@ -11,7 +11,11 @@ export default function printFinalStepsMessage(opts) {
11
11
  DreamCLI.logger.log(`
12
12
  Finished generating redux bindings for your application,
13
13
  but to wire them into your app, we're going to need your help.
14
- Be sure to add the following to your root store to bind your new
14
+
15
+ First, you will need to be sure to sync, so that the new openapi
16
+ types are sent over to your client application.
17
+
18
+ Next, be sure to add the following to your root store to bind your new
15
19
  api module into your redux app, i.e.
16
20
 
17
21
  // add this line, fix the import path if necessary
@@ -25,10 +25,10 @@ export default async function writeInitializer({ exportName }) {
25
25
  const contents = `\
26
26
  import { DreamCLI } from '@rvoh/dream'
27
27
  import { PsychicApp } from '@rvoh/psychic'
28
- import AppEnv from '../AppEnv.js'
28
+ import AppEnv from '../../AppEnv.js'
29
29
 
30
30
  export default function initialize${pascalized}(psy: PsychicApp) {
31
- psy.on('sync', async () => {
31
+ psy.on('cli:sync', async () => {
32
32
  if (AppEnv.isDevelopmentOrTest) {
33
33
  DreamCLI.logger.logStartProgress(\`[${camelized}] syncing...\`)
34
34
  await DreamCLI.spawn('npx @rtk-query/codegen-openapi ${filePath}', {
@@ -21,7 +21,7 @@ import { PsychicApp } from "@rvoh/psychic"
21
21
  import AppEnv from '../AppEnv.js'
22
22
 
23
23
  export default (psy: PsychicApp) => {
24
- psy.on('sync', async () => {
24
+ psy.on('cli:sync', async () => {
25
25
  if (AppEnv.isDevelopmentOrTest) {
26
26
  DreamCLI.logger.logStartProgress(\`[${hyphenized}] extracting types from ${openapiFilepath} to ${outfile}...\`)
27
27
  await DreamCLI.spawn('npx openapi-typescript ${openapiFilepath} -o ${outfile}')
@@ -109,6 +109,15 @@ export function applyResourceAction(path, action, routingMechanism, options) {
109
109
  throw new Error(`unsupported resource method type: ${action}`);
110
110
  }
111
111
  }
112
+ /**
113
+ * Converts OpenAPI-style route parameters to Express.js-style parameters
114
+ * Example: users/{userId}/abc/{id} -> users/:userId/abc/:id
115
+ */
116
+ export function convertRouteParams(path) {
117
+ // Use a more specific regex to avoid polynomial time complexity
118
+ // Only match valid parameter names (alphanumeric + underscore, 1-50 chars)
119
+ return path.replace(/\{([a-zA-Z_][a-zA-Z0-9_]{0,49})\}/g, ':$1');
120
+ }
112
121
  function httpMethodFromResourcefulAction(action) {
113
122
  switch (action) {
114
123
  case 'index':
@@ -6,7 +6,7 @@ import ParamValidationErrors from '../error/controller/ParamValidationErrors.js'
6
6
  import EnvInternal from '../helpers/EnvInternal.js';
7
7
  import errorIsRescuableHttpError from '../helpers/error/errorIsRescuableHttpError.js';
8
8
  import PsychicApp from '../psychic-app/index.js';
9
- import { applyResourceAction, applyResourcesAction, lookupControllerOrFail, routePath, } from '../router/helpers.js';
9
+ import { applyResourceAction, applyResourcesAction, convertRouteParams, lookupControllerOrFail, routePath, } from '../router/helpers.js';
10
10
  import RouteManager from './route-manager.js';
11
11
  import { ResourceMethods, ResourcesMethods, } from './types.js';
12
12
  export default class PsychicRouter {
@@ -68,6 +68,7 @@ export default class PsychicRouter {
68
68
  return '/' + this.currentNamespacePaths.join('/') + '/' + str;
69
69
  }
70
70
  crud(httpMethod, path, controllerOrMiddleware, action) {
71
+ this.checkPathForInvalidChars(path);
71
72
  const isMiddleware = typeof controllerOrMiddleware === 'function' &&
72
73
  !controllerOrMiddleware?.isPsychicController;
73
74
  // devs can provide custom express middleware which bypasses
@@ -92,6 +93,18 @@ export default class PsychicRouter {
92
93
  });
93
94
  }
94
95
  }
96
+ checkPathForInvalidChars(path) {
97
+ if (path.includes('{'))
98
+ throw new Error(`
99
+ The provided route "${path}" contains characters that are not supported.
100
+ If you are trying to write a uri param, you will need to use expressjs
101
+ param syntax, which is a prefixing colon, rather than using brackets
102
+ to surround the param.
103
+
104
+ provided route: "${path}"
105
+ suggested fix: "${convertRouteParams(path)}"
106
+ `);
107
+ }
95
108
  namespace(namespace, cb) {
96
109
  const nestedRouter = new PsychicNestedRouter(this.app, this.config, this.routeManager, {
97
110
  namespaces: this.currentNamespaces,
@@ -23,4 +23,9 @@ export interface NamespaceConfig {
23
23
  }
24
24
  export declare function applyResourcesAction(path: string, action: ResourcesMethodType, routingMechanism: RoutingMechanism, options?: ResourcesOptions): void;
25
25
  export declare function applyResourceAction(path: string, action: ResourcesMethodType, routingMechanism: PsychicRouter | PsychicNestedRouter, options?: ResourcesOptions): void;
26
+ /**
27
+ * Converts OpenAPI-style route parameters to Express.js-style parameters
28
+ * Example: users/{userId}/abc/{id} -> users/:userId/abc/:id
29
+ */
30
+ export declare function convertRouteParams(path: string): string;
26
31
  export {};
@@ -36,6 +36,7 @@ export default class PsychicRouter {
36
36
  crud(httpMethod: HttpMethod, path: string): void;
37
37
  crud(httpMethod: HttpMethod, path: string, middleware: RequestHandler): void;
38
38
  crud(httpMethod: HttpMethod, path: string, controller: typeof PsychicController, action: string): void;
39
+ private checkPathForInvalidChars;
39
40
  namespace(namespace: string, cb: (router: PsychicNestedRouter) => void): void;
40
41
  scope(scope: string, cb: (router: PsychicNestedRouter) => void): void;
41
42
  resources(path: string, optionsOrCb?: ResourcesOptions | ((router: PsychicNestedRouter) => void), cb?: (router: PsychicNestedRouter) => void): void;
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "type": "module",
3
3
  "name": "@rvoh/psychic",
4
4
  "description": "Typescript web framework",
5
- "version": "1.1.0",
5
+ "version": "1.1.2",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",