@rvoh/psychic 1.5.0 → 1.5.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
@@ -1,3 +1,11 @@
1
+ ## 1.5.1
2
+
3
+ - fix issue with enum syncing related to multi-db engine support regression
4
+
5
+ ## 1.5.0
6
+
7
+ - add support for multiple database engines in dream
8
+
1
9
  ## 1.2.3
2
10
 
3
11
  - add support for the connectionName argument when generating a resource
@@ -3,11 +3,11 @@ Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.default = enumsAndTheirValues;
4
4
  const dream_1 = require("@rvoh/dream");
5
5
  const kysely_1 = require("kysely");
6
- async function enumsAndTheirValues() {
6
+ async function enumsAndTheirValues(connectionName = 'default') {
7
7
  const { rows } = await (0, kysely_1.sql) `
8
8
  SELECT pg_type.typname AS enum_type, pg_enum.enumlabel AS enum_label FROM pg_type JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid;
9
9
 
10
- `.execute((0, dream_1.untypedDb)('primary'));
10
+ `.execute((0, dream_1.untypedDb)(connectionName, 'primary'));
11
11
  const rowData = {};
12
12
  rows.forEach(row => {
13
13
  const enumType = row.enumType;
@@ -27,9 +27,10 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
29
  exports.default = syncTypescriptOpenapiFiles;
30
- const dream_1 = require("@rvoh/dream");
31
30
  const fs = __importStar(require("node:fs/promises"));
32
31
  const path = __importStar(require("node:path"));
32
+ const openapi_typescript_1 = __importStar(require("openapi-typescript"));
33
+ const typescript_1 = __importDefault(require("typescript"));
33
34
  const psychicPath_js_1 = __importDefault(require("../../helpers/path/psychicPath.js"));
34
35
  const index_js_1 = __importDefault(require("../../psychic-app/index.js"));
35
36
  async function syncTypescriptOpenapiFiles() {
@@ -37,10 +38,22 @@ async function syncTypescriptOpenapiFiles() {
37
38
  const syncableKeys = Object.keys(psychicApp.openapi).filter(key => psychicApp.openapi[key]?.syncTypes);
38
39
  const targetDir = path.join((0, psychicPath_js_1.default)('types'), 'openapi');
39
40
  await fs.mkdir(targetDir, { recursive: true });
40
- await Promise.all(syncableKeys.map(key => {
41
+ await Promise.all(syncableKeys.map(async (key) => {
41
42
  const openapiOpts = psychicApp.openapi[key];
42
43
  const jsonPath = openapiOpts.outputFilepath;
43
44
  const outpath = path.join(targetDir, path.basename(jsonPath).replace(/\.json$/, '.d.ts'));
44
- return dream_1.DreamCLI.spawn(`npx openapi-typescript ${jsonPath} -o ${outpath}`);
45
+ const contents = (await fs.readFile(jsonPath)).toString();
46
+ const data = await (0, openapi_typescript_1.default)(contents, {
47
+ transform(schemaObject) {
48
+ if (schemaObject.format === 'bigint') {
49
+ const isNullable = schemaObject.nullable ||
50
+ (Array.isArray(schemaObject.type) && schemaObject.type.includes('null'));
51
+ return isNullable
52
+ ? typescript_1.default.factory.createUnionTypeNode([openapi_typescript_1.STRING, openapi_typescript_1.NUMBER, openapi_typescript_1.NULL])
53
+ : typescript_1.default.factory.createUnionTypeNode([openapi_typescript_1.STRING, openapi_typescript_1.NUMBER]);
54
+ }
55
+ },
56
+ });
57
+ await fs.writeFile(outpath, (0, openapi_typescript_1.astToString)(data));
45
58
  }));
46
59
  }
@@ -21,35 +21,55 @@ function dreamColumnToOpenapiType(dreamClass, columnName) {
21
21
  case 'integer':
22
22
  case 'integer[]':
23
23
  return {
24
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)(columnMetadata.dbType, nullableColumn),
24
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)(columnMetadata.dbType, {
25
+ maybeNull: nullableColumn,
26
+ }),
25
27
  };
26
28
  case 'character varying':
27
29
  case 'citext':
28
30
  case 'text':
29
31
  case 'uuid':
32
+ return {
33
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string', { maybeNull: nullableColumn }),
34
+ };
30
35
  case 'bigint':
36
+ // customize formats for bigint values, since the openapi-typescript
37
+ // integration has been customized to treat a bigint as
38
+ // "string | number", rather than simply "string", which helps
39
+ // the request types generated by psychic-spec-helpers to accept
40
+ // primary/foreign key values in the requestBody payloads without
41
+ // raising type errors.
31
42
  return {
32
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string', nullableColumn),
43
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string', {
44
+ maybeNull: nullableColumn,
45
+ format: 'bigint',
46
+ }),
33
47
  };
34
48
  case 'character varying[]':
35
49
  case 'citext[]':
36
50
  case 'text[]':
37
51
  case 'uuid[]':
52
+ return {
53
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string[]', { maybeNull: nullableColumn }),
54
+ };
38
55
  case 'bigint[]':
39
56
  return {
40
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string[]', nullableColumn),
57
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('string[]', {
58
+ maybeNull: nullableColumn,
59
+ format: 'bigint',
60
+ }),
41
61
  };
42
62
  case 'timestamp':
43
63
  case 'timestamp with time zone':
44
64
  case 'timestamp without time zone':
45
65
  return {
46
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('date-time', nullableColumn),
66
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('date-time', { maybeNull: nullableColumn }),
47
67
  };
48
68
  case 'timestamp[]':
49
69
  case 'timestamp with time zone[]':
50
70
  case 'timestamp without time zone[]':
51
71
  return {
52
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('date-time[]', nullableColumn),
72
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('date-time[]', { maybeNull: nullableColumn }),
53
73
  };
54
74
  case 'json':
55
75
  case 'jsonb':
@@ -70,11 +90,11 @@ function dreamColumnToOpenapiType(dreamClass, columnName) {
70
90
  };
71
91
  case 'numeric':
72
92
  return {
73
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('number', nullableColumn),
93
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('number', { maybeNull: nullableColumn }),
74
94
  };
75
95
  case 'numeric[]':
76
96
  return {
77
- [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('number[]', nullableColumn),
97
+ [columnName]: (0, primitiveOpenapiStatementToOpenapi_js_1.default)('number[]', { maybeNull: nullableColumn }),
78
98
  };
79
99
  default:
80
100
  if (dreamClass.isVirtualColumn(columnName)) {
@@ -23,15 +23,16 @@ function _openapiShorthandToOpenapi(shorthand, options) {
23
23
  const shorthandString = (0, maybeNullOpenapiShorthandToOpenapiShorthand_js_1.default)(shorthand);
24
24
  if (!shorthandString)
25
25
  return { type: 'null' };
26
- const openapi = simpleOpenapiShorthandToOpenapi(shorthandString);
27
- if (options.maybeNull || openapiShorthandIncludesNull(shorthand))
26
+ const openapi = simpleOpenapiShorthandToOpenapi(shorthandString, options);
27
+ if (options.maybeNull || openapiShorthandIncludesNull(shorthand)) {
28
28
  return { ...openapi, type: [openapi.type, 'null'] };
29
+ }
29
30
  return openapi;
30
31
  }
31
- function simpleOpenapiShorthandToOpenapi(shorthand) {
32
+ function simpleOpenapiShorthandToOpenapi(shorthand, options) {
32
33
  switch (shorthand) {
33
34
  case 'string':
34
- return { type: 'string' };
35
+ return options.format ? { type: 'string', format: options.format } : { type: 'string' };
35
36
  case 'boolean':
36
37
  return { type: 'boolean' };
37
38
  case 'number':
@@ -47,7 +48,10 @@ function simpleOpenapiShorthandToOpenapi(shorthand) {
47
48
  case 'null':
48
49
  return { type: 'null' };
49
50
  case 'string[]':
50
- return { type: 'array', items: { type: 'string' } };
51
+ return {
52
+ type: 'array',
53
+ items: options.format ? { type: 'string', format: options.format } : { type: 'string' },
54
+ };
51
55
  case 'boolean[]':
52
56
  return { type: 'array', items: { type: 'boolean' } };
53
57
  case 'number[]':
@@ -10,6 +10,6 @@ const openapiShorthandToOpenapi_js_1 = __importDefault(require("./openapiShortha
10
10
  *
11
11
  * parses a primitive stored type
12
12
  */
13
- function primitiveOpenapiStatementToOpenapi(data, maybeNull = false) {
14
- return (0, openapiShorthandToOpenapi_js_1.default)(data, { maybeNull });
13
+ function primitiveOpenapiStatementToOpenapi(data, options = {}) {
14
+ return (0, openapiShorthandToOpenapi_js_1.default)(data, options);
15
15
  }
@@ -1,10 +1,10 @@
1
1
  import { untypedDb } from '@rvoh/dream';
2
2
  import { sql } from 'kysely';
3
- export default async function enumsAndTheirValues() {
3
+ export default async function enumsAndTheirValues(connectionName = 'default') {
4
4
  const { rows } = await sql `
5
5
  SELECT pg_type.typname AS enum_type, pg_enum.enumlabel AS enum_label FROM pg_type JOIN pg_enum ON pg_enum.enumtypid = pg_type.oid;
6
6
 
7
- `.execute(untypedDb('primary'));
7
+ `.execute(untypedDb(connectionName, 'primary'));
8
8
  const rowData = {};
9
9
  rows.forEach(row => {
10
10
  const enumType = row.enumType;
@@ -1,6 +1,7 @@
1
- import { DreamCLI } from '@rvoh/dream';
2
1
  import * as fs from 'node:fs/promises';
3
2
  import * as path from 'node:path';
3
+ import openapiTS, { astToString, NULL, NUMBER, STRING } from 'openapi-typescript';
4
+ import ts from 'typescript';
4
5
  import psychicPath from '../../helpers/path/psychicPath.js';
5
6
  import PsychicApp from '../../psychic-app/index.js';
6
7
  export default async function syncTypescriptOpenapiFiles() {
@@ -8,10 +9,22 @@ export default async function syncTypescriptOpenapiFiles() {
8
9
  const syncableKeys = Object.keys(psychicApp.openapi).filter(key => psychicApp.openapi[key]?.syncTypes);
9
10
  const targetDir = path.join(psychicPath('types'), 'openapi');
10
11
  await fs.mkdir(targetDir, { recursive: true });
11
- await Promise.all(syncableKeys.map(key => {
12
+ await Promise.all(syncableKeys.map(async (key) => {
12
13
  const openapiOpts = psychicApp.openapi[key];
13
14
  const jsonPath = openapiOpts.outputFilepath;
14
15
  const outpath = path.join(targetDir, path.basename(jsonPath).replace(/\.json$/, '.d.ts'));
15
- return DreamCLI.spawn(`npx openapi-typescript ${jsonPath} -o ${outpath}`);
16
+ const contents = (await fs.readFile(jsonPath)).toString();
17
+ const data = await openapiTS(contents, {
18
+ transform(schemaObject) {
19
+ if (schemaObject.format === 'bigint') {
20
+ const isNullable = schemaObject.nullable ||
21
+ (Array.isArray(schemaObject.type) && schemaObject.type.includes('null'));
22
+ return isNullable
23
+ ? ts.factory.createUnionTypeNode([STRING, NUMBER, NULL])
24
+ : ts.factory.createUnionTypeNode([STRING, NUMBER]);
25
+ }
26
+ },
27
+ });
28
+ await fs.writeFile(outpath, astToString(data));
16
29
  }));
17
30
  }
@@ -15,35 +15,55 @@ export default function dreamColumnToOpenapiType(dreamClass, columnName) {
15
15
  case 'integer':
16
16
  case 'integer[]':
17
17
  return {
18
- [columnName]: primitiveOpenapiStatementToOpenapi(columnMetadata.dbType, nullableColumn),
18
+ [columnName]: primitiveOpenapiStatementToOpenapi(columnMetadata.dbType, {
19
+ maybeNull: nullableColumn,
20
+ }),
19
21
  };
20
22
  case 'character varying':
21
23
  case 'citext':
22
24
  case 'text':
23
25
  case 'uuid':
26
+ return {
27
+ [columnName]: primitiveOpenapiStatementToOpenapi('string', { maybeNull: nullableColumn }),
28
+ };
24
29
  case 'bigint':
30
+ // customize formats for bigint values, since the openapi-typescript
31
+ // integration has been customized to treat a bigint as
32
+ // "string | number", rather than simply "string", which helps
33
+ // the request types generated by psychic-spec-helpers to accept
34
+ // primary/foreign key values in the requestBody payloads without
35
+ // raising type errors.
25
36
  return {
26
- [columnName]: primitiveOpenapiStatementToOpenapi('string', nullableColumn),
37
+ [columnName]: primitiveOpenapiStatementToOpenapi('string', {
38
+ maybeNull: nullableColumn,
39
+ format: 'bigint',
40
+ }),
27
41
  };
28
42
  case 'character varying[]':
29
43
  case 'citext[]':
30
44
  case 'text[]':
31
45
  case 'uuid[]':
46
+ return {
47
+ [columnName]: primitiveOpenapiStatementToOpenapi('string[]', { maybeNull: nullableColumn }),
48
+ };
32
49
  case 'bigint[]':
33
50
  return {
34
- [columnName]: primitiveOpenapiStatementToOpenapi('string[]', nullableColumn),
51
+ [columnName]: primitiveOpenapiStatementToOpenapi('string[]', {
52
+ maybeNull: nullableColumn,
53
+ format: 'bigint',
54
+ }),
35
55
  };
36
56
  case 'timestamp':
37
57
  case 'timestamp with time zone':
38
58
  case 'timestamp without time zone':
39
59
  return {
40
- [columnName]: primitiveOpenapiStatementToOpenapi('date-time', nullableColumn),
60
+ [columnName]: primitiveOpenapiStatementToOpenapi('date-time', { maybeNull: nullableColumn }),
41
61
  };
42
62
  case 'timestamp[]':
43
63
  case 'timestamp with time zone[]':
44
64
  case 'timestamp without time zone[]':
45
65
  return {
46
- [columnName]: primitiveOpenapiStatementToOpenapi('date-time[]', nullableColumn),
66
+ [columnName]: primitiveOpenapiStatementToOpenapi('date-time[]', { maybeNull: nullableColumn }),
47
67
  };
48
68
  case 'json':
49
69
  case 'jsonb':
@@ -64,11 +84,11 @@ export default function dreamColumnToOpenapiType(dreamClass, columnName) {
64
84
  };
65
85
  case 'numeric':
66
86
  return {
67
- [columnName]: primitiveOpenapiStatementToOpenapi('number', nullableColumn),
87
+ [columnName]: primitiveOpenapiStatementToOpenapi('number', { maybeNull: nullableColumn }),
68
88
  };
69
89
  case 'numeric[]':
70
90
  return {
71
- [columnName]: primitiveOpenapiStatementToOpenapi('number[]', nullableColumn),
91
+ [columnName]: primitiveOpenapiStatementToOpenapi('number[]', { maybeNull: nullableColumn }),
72
92
  };
73
93
  default:
74
94
  if (dreamClass.isVirtualColumn(columnName)) {
@@ -16,15 +16,16 @@ function _openapiShorthandToOpenapi(shorthand, options) {
16
16
  const shorthandString = maybeNullOpenapiShorthandToOpenapiShorthand(shorthand);
17
17
  if (!shorthandString)
18
18
  return { type: 'null' };
19
- const openapi = simpleOpenapiShorthandToOpenapi(shorthandString);
20
- if (options.maybeNull || openapiShorthandIncludesNull(shorthand))
19
+ const openapi = simpleOpenapiShorthandToOpenapi(shorthandString, options);
20
+ if (options.maybeNull || openapiShorthandIncludesNull(shorthand)) {
21
21
  return { ...openapi, type: [openapi.type, 'null'] };
22
+ }
22
23
  return openapi;
23
24
  }
24
- function simpleOpenapiShorthandToOpenapi(shorthand) {
25
+ function simpleOpenapiShorthandToOpenapi(shorthand, options) {
25
26
  switch (shorthand) {
26
27
  case 'string':
27
- return { type: 'string' };
28
+ return options.format ? { type: 'string', format: options.format } : { type: 'string' };
28
29
  case 'boolean':
29
30
  return { type: 'boolean' };
30
31
  case 'number':
@@ -40,7 +41,10 @@ function simpleOpenapiShorthandToOpenapi(shorthand) {
40
41
  case 'null':
41
42
  return { type: 'null' };
42
43
  case 'string[]':
43
- return { type: 'array', items: { type: 'string' } };
44
+ return {
45
+ type: 'array',
46
+ items: options.format ? { type: 'string', format: options.format } : { type: 'string' },
47
+ };
44
48
  case 'boolean[]':
45
49
  return { type: 'array', items: { type: 'boolean' } };
46
50
  case 'number[]':
@@ -4,6 +4,6 @@ import openapiShorthandToOpenapi from './openapiShorthandToOpenapi.js';
4
4
  *
5
5
  * parses a primitive stored type
6
6
  */
7
- export default function primitiveOpenapiStatementToOpenapi(data, maybeNull = false) {
8
- return openapiShorthandToOpenapi(data, { maybeNull });
7
+ export default function primitiveOpenapiStatementToOpenapi(data, options = {}) {
8
+ return openapiShorthandToOpenapi(data, options);
9
9
  }
@@ -1 +1 @@
1
- export default function enumsAndTheirValues(): Promise<Record<string, string[]>>;
1
+ export default function enumsAndTheirValues(connectionName?: string): Promise<Record<string, string[]>>;
@@ -1,8 +1,9 @@
1
1
  import { OpenapiSchemaBody, OpenapiSchemaBodyShorthand, OpenapiShorthandPrimitiveTypes } from '@rvoh/dream';
2
- interface Options {
2
+ export interface OpenapiShorthandToOpenapiOptions {
3
3
  maybeNull?: boolean;
4
+ format?: string | undefined;
4
5
  }
5
- export default function openapiShorthandToOpenapi(openapi: OpenapiShorthandPrimitiveTypes | OpenapiSchemaBodyShorthand | undefined, options?: Options): OpenapiSchemaBody;
6
+ export default function openapiShorthandToOpenapi(openapi: OpenapiShorthandPrimitiveTypes | OpenapiSchemaBodyShorthand | undefined, options?: OpenapiShorthandToOpenapiOptions): OpenapiSchemaBody;
6
7
  export declare class UnrecognizedOpenapiShorthand extends Error {
7
8
  private shorthand;
8
9
  constructor(shorthand: any);
@@ -13,4 +14,3 @@ export declare class UnrecognizedOpenapiShape extends Error {
13
14
  constructor(openapi: any);
14
15
  get message(): string;
15
16
  }
16
- export {};
@@ -1,7 +1,8 @@
1
1
  import { OpenapiSchemaBody, OpenapiSchemaBodyShorthand, OpenapiShorthandPrimitiveTypes } from '@rvoh/dream';
2
+ import { OpenapiShorthandToOpenapiOptions } from './openapiShorthandToOpenapi.js';
2
3
  /**
3
4
  * @internal
4
5
  *
5
6
  * parses a primitive stored type
6
7
  */
7
- export default function primitiveOpenapiStatementToOpenapi(data: OpenapiShorthandPrimitiveTypes | OpenapiSchemaBodyShorthand | undefined, maybeNull?: boolean): OpenapiSchemaBody;
8
+ export default function primitiveOpenapiStatementToOpenapi(data: OpenapiShorthandPrimitiveTypes | OpenapiSchemaBodyShorthand | undefined, options?: OpenapiShorthandToOpenapiOptions): OpenapiSchemaBody;
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.5.0",
5
+ "version": "1.5.2",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",