@rvoh/psychic 1.1.7 → 1.1.9

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
@@ -1,3 +1,43 @@
1
+ ## 1.1.9
2
+
3
+ - Throw a 422 if dream raises the `DataTypeColumnTypeMismatch` exception, which happens when a dream is saved to the database with data that cannot be inserted into the respective columns, usually because of a type mismatch.
4
+ - castParam will now encase params in an array when being explicitly casted as an array type, bypassing a known bug in express from causing arrays with single items in them to be treated as non-arrays.
5
+
6
+ ## 1.1.8
7
+
8
+ - Tap into CliFileWriter provided by dream to tap into file reversion for sync files, since the auto-sync function in psychic can fail and leave your file tree in a bad state.
9
+
10
+ ## 1.1.7
11
+
12
+ - Add support for middleware arrays, enabling express plugins like passport
13
+
14
+ ## 1.1.6
15
+
16
+ - fix regression caused by missing --schema-only option in psychic cli
17
+
18
+ ## 1.1.5
19
+
20
+ - pass packageManager through to dream, now that it accepts a packageManager setting.
21
+ - update dream shadowing within psychic application initialization to take place after initializers and plugins are processed, so that those initializers and plugins have an opportunity to adjust the settings.
22
+
23
+ ## 1.1.4
24
+
25
+ - fix regressions to redux bindings caused by default openapi path location changes
26
+ - resource generator can handle prefixing slashes
27
+
28
+ ## 1.1.3
29
+
30
+ - fix more minor issues with redux openapi bindings
31
+
32
+ ## 1.1.2
33
+
34
+ - Fix various issues with openapi redux bindings
35
+ - raise hard exception if accidentally using openapi route params in an expressjs route path
36
+
37
+ ## 1.1.1
38
+
39
+ Fix route printing regression causing route printouts to show the path instead of the action
40
+
1
41
  ## v1.1.0
2
42
 
3
43
  Provides easier access to express middleware by exposing `PsychicApp#use`, which enables a developer to provide express middleware directly through the psychcic application, without tapping into any hooks.
@@ -35,34 +75,3 @@ r.get('helloworld', (req, res, next) => {
35
75
  res.json({ hello: 'world' })
36
76
  })
37
77
  ```
38
-
39
- ## 1.1.1
40
-
41
- Fix route printing regression causing route printouts to show the path instead of the action
42
-
43
- ## 1.1.2
44
-
45
- - Fix various issues with openapi redux bindings
46
- - raise hard exception if accidentally using openapi route params in an expressjs route path
47
-
48
- ## 1.1.3
49
-
50
- - fix more minor issues with redux openapi bindings
51
-
52
- ## 1.1.4
53
-
54
- - fix regressions to redux bindings caused by default openapi path location changes
55
- - resource generator can handle prefixing slashes
56
-
57
- ## 1.1.5
58
-
59
- - pass packageManager through to dream, now that it accepts a packageManager setting.
60
- - update dream shadowing within psychic application initialization to take place after initializers and plugins are processed, so that those initializers and plugins have an opportunity to adjust the settings.
61
-
62
- ## 1.1.6
63
-
64
- - fix regression caused by missing --schema-only option in psychic cli
65
-
66
- ## 1.1.7
67
-
68
- - Add support for middleware arrays, enabling express plugins like passport
@@ -28,7 +28,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.default = generateRouteTypes;
30
30
  const dream_1 = require("@rvoh/dream");
31
- const fs = __importStar(require("node:fs/promises"));
32
31
  const path = __importStar(require("node:path"));
33
32
  const index_js_1 = __importDefault(require("../../psychic-app/index.js"));
34
33
  async function generateRouteTypes(routes) {
@@ -38,5 +37,5 @@ async function generateRouteTypes(routes) {
38
37
  `;
39
38
  const psychicApp = index_js_1.default.getOrFail();
40
39
  const routeTypesPath = path.join(psychicApp.apiRoot, 'src', 'conf', 'routeTypes.ts');
41
- await fs.writeFile(routeTypesPath, fileStr);
40
+ await dream_1.CliFileWriter.write(routeTypesPath, fileStr);
42
41
  }
@@ -74,22 +74,15 @@ class PsychicBin {
74
74
  dream_1.DreamCLI.logger.logEndProgress();
75
75
  }
76
76
  static async postSync() {
77
- const psychicApp = index_js_1.default.getOrFail();
78
- await PsychicBin.syncOpenapiJson();
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- let output = {};
81
- for (const hook of psychicApp.specialHooks.cliSync) {
82
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
83
- const res = await hook();
84
- if ((0, isObject_js_1.default)(res)) {
85
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
86
- output = { ...output, ...res };
87
- }
77
+ try {
78
+ await this.syncOpenapiJson();
79
+ await this.runCliHooksAndUpdatePsychicTypesFileWithOutput();
80
+ await this.syncTypescriptOpenapiFiles();
88
81
  }
89
- if (Object.keys(output).length) {
90
- await PsychicBin.syncTypes(output);
82
+ catch (error) {
83
+ console.error(error);
84
+ await dream_1.CliFileWriter.revert();
91
85
  }
92
- await this.syncTypescriptOpenapiFiles();
93
86
  }
94
87
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
95
88
  static async syncTypes(customTypes = undefined) {
@@ -125,8 +118,34 @@ class PsychicBin {
125
118
  catch {
126
119
  // noop
127
120
  }
128
- await fs.writeFile(outfile, enumsStr, {});
121
+ await dream_1.CliFileWriter.write(outfile, enumsStr);
129
122
  dream_1.DreamCLI.logger.logEndProgress();
130
123
  }
124
+ /**
125
+ * @internal
126
+ *
127
+ * runs all the custom cli hooks provided for the user's application.
128
+ * if any of the cli hooks returns an object-based output, we will splat
129
+ * it all together into a single `output` variable, which we then
130
+ * feed into the `syncTypes` method to provide custom type data.
131
+ * This enables psychic plugins to add custom types to the psychic type
132
+ * bindings.
133
+ */
134
+ static async runCliHooksAndUpdatePsychicTypesFileWithOutput() {
135
+ const psychicApp = index_js_1.default.getOrFail();
136
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
137
+ let output = {};
138
+ for (const hook of psychicApp.specialHooks.cliSync) {
139
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
140
+ const res = await hook();
141
+ if ((0, isObject_js_1.default)(res)) {
142
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
143
+ output = { ...output, ...res };
144
+ }
145
+ }
146
+ if (Object.keys(output).length) {
147
+ await PsychicBin.syncTypes(output);
148
+ }
149
+ }
131
150
  }
132
151
  exports.default = PsychicBin;
@@ -27,7 +27,6 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  const dream_1 = require("@rvoh/dream");
30
- const fs = __importStar(require("node:fs/promises"));
31
30
  const path = __importStar(require("node:path"));
32
31
  const index_js_1 = __importDefault(require("../../psychic-app/index.js"));
33
32
  class TypesBuilder {
@@ -35,7 +34,7 @@ class TypesBuilder {
35
34
  static async sync(customTypes = undefined) {
36
35
  const dreamApp = dream_1.DreamApp.getOrFail();
37
36
  const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
38
- await fs.writeFile(schemaPath, this.build(customTypes));
37
+ await dream_1.CliFileWriter.write(schemaPath, this.build(customTypes));
39
38
  }
40
39
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
41
40
  static build(customTypes = undefined) {
@@ -1,33 +1,9 @@
1
1
  "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
2
  var __importDefault = (this && this.__importDefault) || function (mod) {
26
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
4
  };
28
5
  Object.defineProperty(exports, "__esModule", { value: true });
29
6
  const dream_1 = require("@rvoh/dream");
30
- const fs = __importStar(require("node:fs/promises"));
31
7
  const node_util_1 = require("node:util");
32
8
  const UnexpectedUndefined_js_1 = __importDefault(require("../error/UnexpectedUndefined.js"));
33
9
  const openapiJsonPath_js_1 = __importDefault(require("../helpers/openapiJsonPath.js"));
@@ -49,9 +25,7 @@ class OpenapiAppRenderer {
49
25
  const psychicApp = index_js_1.default.getOrFail();
50
26
  const asyncWriteOpenapiFile = async (key) => {
51
27
  const jsonPath = (0, openapiJsonPath_js_1.default)(key);
52
- await fs.writeFile(jsonPath, JSON.stringify(openapiContents[key], null, 2), {
53
- flag: 'w+',
54
- });
28
+ await dream_1.CliFileWriter.write(jsonPath, JSON.stringify(openapiContents[key], null, 2), { flag: 'w+' });
55
29
  };
56
30
  await Promise.all(Object.keys(psychicApp.openapi).map(key => asyncWriteOpenapiFile(key)));
57
31
  }
@@ -245,6 +245,9 @@ suggested fix: "${(0, helpers_js_1.convertRouteParams)(path)}"
245
245
  else if (err instanceof dream_1.RecordNotFound) {
246
246
  res.sendStatus(404);
247
247
  }
248
+ else if (err instanceof dream_1.DataTypeColumnTypeMismatch) {
249
+ res.status(422).json();
250
+ }
248
251
  else if (err instanceof dream_1.ValidationError) {
249
252
  res.status(422).json({ errors: err.errors || {} });
250
253
  }
@@ -287,8 +287,9 @@ class Params {
287
287
  case 'number[]':
288
288
  case 'string[]':
289
289
  case 'uuid[]':
290
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
290
291
  if (!Array.isArray(paramValue))
291
- throw new ParamValidationError_js_1.default(paramName, [typeToError(expectedType)]);
292
+ paramValue = [paramValue];
292
293
  return (0, dream_1.compact)(paramValue.map(param => this.cast(paramName, param, arrayTypeToNonArrayType(expectedType), { ...opts, allowNull: true })));
293
294
  case 'null[]':
294
295
  if (!Array.isArray(paramValue))
@@ -1,5 +1,4 @@
1
- import { uniq } from '@rvoh/dream';
2
- import * as fs from 'node:fs/promises';
1
+ import { CliFileWriter, uniq } from '@rvoh/dream';
3
2
  import * as path from 'node:path';
4
3
  import PsychicApp from '../../psychic-app/index.js';
5
4
  export default async function generateRouteTypes(routes) {
@@ -9,5 +8,5 @@ export default async function generateRouteTypes(routes) {
9
8
  `;
10
9
  const psychicApp = PsychicApp.getOrFail();
11
10
  const routeTypesPath = path.join(psychicApp.apiRoot, 'src', 'conf', 'routeTypes.ts');
12
- await fs.writeFile(routeTypesPath, fileStr);
11
+ await CliFileWriter.write(routeTypesPath, fileStr);
13
12
  }
@@ -1,4 +1,4 @@
1
- import { DreamBin, DreamCLI } from '@rvoh/dream';
1
+ import { CliFileWriter, DreamBin, DreamCLI } from '@rvoh/dream';
2
2
  import * as fs from 'node:fs/promises';
3
3
  import * as path from 'node:path';
4
4
  import TypesBuilder from '../cli/helpers/TypesBuilder.js';
@@ -46,22 +46,15 @@ export default class PsychicBin {
46
46
  DreamCLI.logger.logEndProgress();
47
47
  }
48
48
  static async postSync() {
49
- const psychicApp = PsychicApp.getOrFail();
50
- await PsychicBin.syncOpenapiJson();
51
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
52
- let output = {};
53
- for (const hook of psychicApp.specialHooks.cliSync) {
54
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
55
- const res = await hook();
56
- if (isObject(res)) {
57
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
58
- output = { ...output, ...res };
59
- }
49
+ try {
50
+ await this.syncOpenapiJson();
51
+ await this.runCliHooksAndUpdatePsychicTypesFileWithOutput();
52
+ await this.syncTypescriptOpenapiFiles();
60
53
  }
61
- if (Object.keys(output).length) {
62
- await PsychicBin.syncTypes(output);
54
+ catch (error) {
55
+ console.error(error);
56
+ await CliFileWriter.revert();
63
57
  }
64
- await this.syncTypescriptOpenapiFiles();
65
58
  }
66
59
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
67
60
  static async syncTypes(customTypes = undefined) {
@@ -97,7 +90,33 @@ export default class PsychicBin {
97
90
  catch {
98
91
  // noop
99
92
  }
100
- await fs.writeFile(outfile, enumsStr, {});
93
+ await CliFileWriter.write(outfile, enumsStr);
101
94
  DreamCLI.logger.logEndProgress();
102
95
  }
96
+ /**
97
+ * @internal
98
+ *
99
+ * runs all the custom cli hooks provided for the user's application.
100
+ * if any of the cli hooks returns an object-based output, we will splat
101
+ * it all together into a single `output` variable, which we then
102
+ * feed into the `syncTypes` method to provide custom type data.
103
+ * This enables psychic plugins to add custom types to the psychic type
104
+ * bindings.
105
+ */
106
+ static async runCliHooksAndUpdatePsychicTypesFileWithOutput() {
107
+ const psychicApp = PsychicApp.getOrFail();
108
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
109
+ let output = {};
110
+ for (const hook of psychicApp.specialHooks.cliSync) {
111
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
112
+ const res = await hook();
113
+ if (isObject(res)) {
114
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
115
+ output = { ...output, ...res };
116
+ }
117
+ }
118
+ if (Object.keys(output).length) {
119
+ await PsychicBin.syncTypes(output);
120
+ }
121
+ }
103
122
  }
@@ -1,5 +1,4 @@
1
- import { DreamApp } from '@rvoh/dream';
2
- import * as fs from 'node:fs/promises';
1
+ import { CliFileWriter, DreamApp } from '@rvoh/dream';
3
2
  import * as path from 'node:path';
4
3
  import PsychicApp from '../../psychic-app/index.js';
5
4
  export default class TypesBuilder {
@@ -7,7 +6,7 @@ export default class TypesBuilder {
7
6
  static async sync(customTypes = undefined) {
8
7
  const dreamApp = DreamApp.getOrFail();
9
8
  const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
10
- await fs.writeFile(schemaPath, this.build(customTypes));
9
+ await CliFileWriter.write(schemaPath, this.build(customTypes));
11
10
  }
12
11
  // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
12
  static build(customTypes = undefined) {
@@ -1,5 +1,4 @@
1
- import { compact, groupBy, sortObjectByKey } from '@rvoh/dream';
2
- import * as fs from 'node:fs/promises';
1
+ import { CliFileWriter, compact, groupBy, sortObjectByKey } from '@rvoh/dream';
3
2
  import { debuglog } from 'node:util';
4
3
  import UnexpectedUndefined from '../error/UnexpectedUndefined.js';
5
4
  import openapiJsonPath from '../helpers/openapiJsonPath.js';
@@ -21,9 +20,7 @@ export default class OpenapiAppRenderer {
21
20
  const psychicApp = PsychicApp.getOrFail();
22
21
  const asyncWriteOpenapiFile = async (key) => {
23
22
  const jsonPath = openapiJsonPath(key);
24
- await fs.writeFile(jsonPath, JSON.stringify(openapiContents[key], null, 2), {
25
- flag: 'w+',
26
- });
23
+ await CliFileWriter.write(jsonPath, JSON.stringify(openapiContents[key], null, 2), { flag: 'w+' });
27
24
  };
28
25
  await Promise.all(Object.keys(psychicApp.openapi).map(key => asyncWriteOpenapiFile(key)));
29
26
  }
@@ -1,4 +1,4 @@
1
- import { RecordNotFound, ValidationError, camelize } from '@rvoh/dream';
1
+ import { DataTypeColumnTypeMismatch, RecordNotFound, ValidationError, camelize } from '@rvoh/dream';
2
2
  import { Router } from 'express';
3
3
  import pluralize from 'pluralize-esm';
4
4
  import ParamValidationError from '../error/controller/ParamValidationError.js';
@@ -239,6 +239,9 @@ suggested fix: "${convertRouteParams(path)}"
239
239
  else if (err instanceof RecordNotFound) {
240
240
  res.sendStatus(404);
241
241
  }
242
+ else if (err instanceof DataTypeColumnTypeMismatch) {
243
+ res.status(422).json();
244
+ }
242
245
  else if (err instanceof ValidationError) {
243
246
  res.status(422).json({ errors: err.errors || {} });
244
247
  }
@@ -282,8 +282,9 @@ export default class Params {
282
282
  case 'number[]':
283
283
  case 'string[]':
284
284
  case 'uuid[]':
285
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
285
286
  if (!Array.isArray(paramValue))
286
- throw new ParamValidationError(paramName, [typeToError(expectedType)]);
287
+ paramValue = [paramValue];
287
288
  return compact(paramValue.map(param => this.cast(paramName, param, arrayTypeToNonArrayType(expectedType), { ...opts, allowNull: true })));
288
289
  case 'null[]':
289
290
  if (!Array.isArray(paramValue))
@@ -17,4 +17,15 @@ export default class PsychicBin {
17
17
  static syncOpenapiJson(): Promise<void>;
18
18
  static syncRoutes(): Promise<void>;
19
19
  static syncClientEnums(outfile: string): Promise<void>;
20
+ /**
21
+ * @internal
22
+ *
23
+ * runs all the custom cli hooks provided for the user's application.
24
+ * if any of the cli hooks returns an object-based output, we will splat
25
+ * it all together into a single `output` variable, which we then
26
+ * feed into the `syncTypes` method to provide custom type data.
27
+ * This enables psychic plugins to add custom types to the psychic type
28
+ * bindings.
29
+ */
30
+ private static runCliHooksAndUpdatePsychicTypesFileWithOutput;
20
31
  }
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.7",
5
+ "version": "1.1.9",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",
@@ -59,7 +59,7 @@
59
59
  "devDependencies": {
60
60
  "@eslint/js": "^9.19.0",
61
61
  "@jest-mock/express": "^3.0.0",
62
- "@rvoh/dream": "^1.0.2",
62
+ "@rvoh/dream": "^1.2.1",
63
63
  "@rvoh/dream-spec-helpers": "^1.0.0",
64
64
  "@rvoh/psychic-spec-helpers": "^1.0.0",
65
65
  "@types/express": "^5.0.1",
@@ -1,11 +0,0 @@
1
- "use strict";
2
- Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.loadFile = loadFile;
4
- exports.writeFile = writeFile;
5
- const node_fs_1 = require("node:fs");
6
- async function loadFile(filepath) {
7
- return await node_fs_1.promises.readFile(filepath);
8
- }
9
- async function writeFile(filepath, contents) {
10
- return await node_fs_1.promises.writeFile(filepath, contents);
11
- }
@@ -1,7 +0,0 @@
1
- import { promises as fs } from 'node:fs';
2
- export async function loadFile(filepath) {
3
- return await fs.readFile(filepath);
4
- }
5
- export async function writeFile(filepath, contents) {
6
- return await fs.writeFile(filepath, contents);
7
- }
@@ -1,2 +0,0 @@
1
- export declare function loadFile(filepath: string): Promise<Buffer>;
2
- export declare function writeFile(filepath: string, contents: string): Promise<void>;