@rvoh/psychic 0.34.5 → 0.35.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.
- package/dist/cjs/src/bin/helpers/syncTypescriptOpenapiFiles.js +51 -0
- package/dist/cjs/src/bin/index.js +8 -1
- package/dist/cjs/src/cli/index.js +19 -9
- package/dist/cjs/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
- package/dist/cjs/src/generate/helpers/syncOpenapiTypescript/generateInitializer.js +63 -0
- package/dist/cjs/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.js +23 -0
- package/dist/cjs/src/generate/initializer/syncOpenapiTypescript.js +12 -0
- package/dist/cjs/src/helpers/openapiTypeHelpers.js +2 -0
- package/dist/cjs/src/openapi-renderer/endpoint.js +53 -9
- package/dist/cjs/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +9 -0
- package/dist/cjs/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +35 -0
- package/dist/cjs/src/psychic-app/index.js +10 -35
- package/dist/cjs/src/server/index.js +5 -3
- package/dist/esm/src/bin/helpers/syncTypescriptOpenapiFiles.js +22 -0
- package/dist/esm/src/bin/index.js +8 -1
- package/dist/esm/src/cli/index.js +19 -9
- package/dist/esm/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
- package/dist/esm/src/generate/helpers/syncOpenapiTypescript/generateInitializer.js +34 -0
- package/dist/esm/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.js +17 -0
- package/dist/esm/src/generate/initializer/syncOpenapiTypescript.js +6 -0
- package/dist/esm/src/helpers/openapiTypeHelpers.js +1 -0
- package/dist/esm/src/openapi-renderer/endpoint.js +53 -9
- package/dist/esm/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +6 -0
- package/dist/esm/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +29 -0
- package/dist/esm/src/psychic-app/index.js +10 -35
- package/dist/esm/src/server/index.js +5 -3
- package/dist/types/src/bin/helpers/syncTypescriptOpenapiFiles.d.ts +1 -0
- package/dist/types/src/bin/index.d.ts +1 -0
- package/dist/types/src/generate/helpers/syncOpenapiTypescript/generateInitializer.d.ts +1 -0
- package/dist/types/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.d.ts +1 -0
- package/dist/types/src/generate/initializer/syncOpenapiTypescript.d.ts +1 -0
- package/dist/types/src/helpers/openapiTypeHelpers.d.ts +3 -0
- package/dist/types/src/helpers/path/psychicPath.d.ts +1 -1
- package/dist/types/src/helpers/typeHelpers.d.ts +1 -0
- package/dist/types/src/openapi-renderer/endpoint.d.ts +49 -1
- package/dist/types/src/openapi-renderer/helpers/pageParamOpenapiProperty.d.ts +4 -0
- package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts +15 -0
- package/dist/types/src/psychic-app/index.d.ts +240 -7
- package/dist/types/src/psychic-app/types.d.ts +1 -2
- package/package.json +3 -2
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.default = syncTypescriptOpenapiFiles;
|
|
30
|
+
const dream_1 = require("@rvoh/dream");
|
|
31
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
32
|
+
const path = __importStar(require("node:path"));
|
|
33
|
+
const index_js_1 = __importDefault(require("../../psychic-app/index.js"));
|
|
34
|
+
const psychicPath_js_1 = __importDefault(require("../../helpers/path/psychicPath.js"));
|
|
35
|
+
async function syncTypescriptOpenapiFiles() {
|
|
36
|
+
const psychicApp = index_js_1.default.getOrFail();
|
|
37
|
+
const syncableKeys = Object.keys(psychicApp.openapi).filter(key => psychicApp.openapi[key]?.syncTypes);
|
|
38
|
+
await Promise.all(syncableKeys.map(key => {
|
|
39
|
+
const openapiOpts = psychicApp.openapi[key];
|
|
40
|
+
const jsonPath = openapiOpts.outputFilename;
|
|
41
|
+
const outpath = path.join((0, psychicPath_js_1.default)('types'), `${jsonPath.replace(/\.json$/, '')}.d.ts`);
|
|
42
|
+
return dream_1.DreamCLI.spawn(`npx openapi-typescript ${jsonPath} -o ${outpath}`).then(async () => {
|
|
43
|
+
const file = (await fs.readFile(outpath)).toString();
|
|
44
|
+
const exportName = (0, dream_1.camelize)(jsonPath
|
|
45
|
+
.split('/')
|
|
46
|
+
.at(-1)
|
|
47
|
+
?.replace(/\.json/, '')) + 'Paths';
|
|
48
|
+
await fs.writeFile(outpath, file.replace(/export interface paths/, `export interface ${exportName}`));
|
|
49
|
+
});
|
|
50
|
+
}));
|
|
51
|
+
}
|
|
@@ -39,6 +39,7 @@ const index_js_2 = __importDefault(require("../server/index.js"));
|
|
|
39
39
|
const enumsFileStr_js_1 = __importDefault(require("./helpers/enumsFileStr.js"));
|
|
40
40
|
const generateRouteTypes_js_1 = __importDefault(require("./helpers/generateRouteTypes.js"));
|
|
41
41
|
const printRoutes_js_1 = __importDefault(require("./helpers/printRoutes.js"));
|
|
42
|
+
const syncTypescriptOpenapiFiles_js_1 = __importDefault(require("./helpers/syncTypescriptOpenapiFiles.js"));
|
|
42
43
|
class PsychicBin {
|
|
43
44
|
static async generateController(controllerName, actions) {
|
|
44
45
|
await (0, controller_js_1.default)({ fullyQualifiedControllerName: controllerName, actions });
|
|
@@ -71,7 +72,7 @@ class PsychicBin {
|
|
|
71
72
|
await PsychicBin.syncOpenapiJson();
|
|
72
73
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
73
74
|
let output = {};
|
|
74
|
-
for (const hook of psychicApp.specialHooks.
|
|
75
|
+
for (const hook of psychicApp.specialHooks.cliSync) {
|
|
75
76
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
76
77
|
const res = await hook();
|
|
77
78
|
if ((0, typechecks_js_1.isObject)(res)) {
|
|
@@ -82,6 +83,7 @@ class PsychicBin {
|
|
|
82
83
|
if (Object.keys(output).length) {
|
|
83
84
|
await PsychicBin.syncTypes(output);
|
|
84
85
|
}
|
|
86
|
+
await this.syncTypescriptOpenapiFiles();
|
|
85
87
|
}
|
|
86
88
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
87
89
|
static async syncTypes(customTypes = undefined) {
|
|
@@ -89,6 +91,11 @@ class PsychicBin {
|
|
|
89
91
|
await TypesBuilder_js_1.default.sync(customTypes);
|
|
90
92
|
dream_1.DreamCLI.logger.logEndProgress();
|
|
91
93
|
}
|
|
94
|
+
static async syncTypescriptOpenapiFiles() {
|
|
95
|
+
dream_1.DreamCLI.logger.logStartProgress(`syncing openapi types...`);
|
|
96
|
+
await (0, syncTypescriptOpenapiFiles_js_1.default)();
|
|
97
|
+
dream_1.DreamCLI.logger.logEndProgress();
|
|
98
|
+
}
|
|
92
99
|
static async syncOpenapiJson() {
|
|
93
100
|
dream_1.DreamCLI.logger.logStartProgress(`syncing openapi...`);
|
|
94
101
|
await app_js_1.default.sync();
|
|
@@ -7,6 +7,7 @@ const dream_1 = require("@rvoh/dream");
|
|
|
7
7
|
const index_js_1 = __importDefault(require("../bin/index.js"));
|
|
8
8
|
const reduxBindings_js_1 = __importDefault(require("../generate/openapi/reduxBindings.js"));
|
|
9
9
|
const syncEnums_js_1 = __importDefault(require("../generate/initializer/syncEnums.js"));
|
|
10
|
+
const syncOpenapiTypescript_js_1 = __importDefault(require("../generate/initializer/syncOpenapiTypescript.js"));
|
|
10
11
|
class PsychicCLI {
|
|
11
12
|
static provide(program, { initializePsychicApp, seedDb, }) {
|
|
12
13
|
dream_1.DreamCLI.generateDreamCli(program, {
|
|
@@ -40,8 +41,17 @@ class PsychicCLI {
|
|
|
40
41
|
process.exit();
|
|
41
42
|
});
|
|
42
43
|
program
|
|
43
|
-
.command('
|
|
44
|
-
.
|
|
44
|
+
.command('setup:sync:enums')
|
|
45
|
+
.description('generates an initializer in your app for syncing enums to a particular path.')
|
|
46
|
+
.argument('<outfile>', 'the path from your backend directory to the location which you want the enums copied. Should end with .ts, i.e. "../client/src/api/enums.ts"')
|
|
47
|
+
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
|
|
48
|
+
.action(async (outfile, { initializerName, }) => {
|
|
49
|
+
await initializePsychicApp();
|
|
50
|
+
await (0, syncEnums_js_1.default)(outfile, initializerName);
|
|
51
|
+
process.exit();
|
|
52
|
+
});
|
|
53
|
+
program
|
|
54
|
+
.command('setup:sync:openapi-redux')
|
|
45
55
|
.description('generates openapi redux bindings to connect one of your openapi files to one of your clients')
|
|
46
56
|
.option('--schema-file', 'the path from your api root to the openapi file you wish to use to generate your schema, i.e. ./openapi/openapi.json')
|
|
47
57
|
.option('--api-file', 'the path to the boilerplate api file that will be used to scaffold your backend endpoints together with, i.e. ../client/app/api.ts')
|
|
@@ -60,14 +70,14 @@ class PsychicCLI {
|
|
|
60
70
|
process.exit();
|
|
61
71
|
});
|
|
62
72
|
program
|
|
63
|
-
.command('
|
|
64
|
-
.
|
|
65
|
-
.
|
|
66
|
-
.argument('<outfile>', 'the path from your backend directory to the location which you want the
|
|
67
|
-
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-
|
|
68
|
-
.action(async (outfile, { initializerName, }) => {
|
|
73
|
+
.command('setup:sync:openapi-typescript')
|
|
74
|
+
.description('generates an initializer in your app for converting one of your openapi files to typescript')
|
|
75
|
+
.argument('<openapiFilepath>', 'the path from your backend directory to the openapi file you wish to scan, i.e. "./openapi/openapi.json"')
|
|
76
|
+
.argument('<outfile>', 'the path from your backend directory to the location which you want the openapi types written to. Must end with .d.ts, i.e. "./src/conf/openapi/openapi.types.d.ts"')
|
|
77
|
+
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-openapi-typescript.ts`')
|
|
78
|
+
.action(async (openapiFilepath, outfile, { initializerName, }) => {
|
|
69
79
|
await initializePsychicApp();
|
|
70
|
-
await (0,
|
|
80
|
+
await (0, syncOpenapiTypescript_js_1.default)(openapiFilepath, outfile, initializerName);
|
|
71
81
|
process.exit();
|
|
72
82
|
});
|
|
73
83
|
program
|
|
@@ -0,0 +1,63 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
26
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
27
|
+
};
|
|
28
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
29
|
+
exports.default = generateInitializer;
|
|
30
|
+
const dream_1 = require("@rvoh/dream");
|
|
31
|
+
const fs = __importStar(require("node:fs/promises"));
|
|
32
|
+
const path = __importStar(require("node:path"));
|
|
33
|
+
const psychicPath_js_1 = __importDefault(require("../../../helpers/path/psychicPath.js"));
|
|
34
|
+
async function generateInitializer(openapiFilepath, outfile, initializerFilename) {
|
|
35
|
+
if (!/\.d\.ts$/.test(outfile))
|
|
36
|
+
throw new Error(`outfile must have extension .d.ts`);
|
|
37
|
+
const initializerFilenameWithoutExtension = initializerFilename.replace(/\.ts$/, '');
|
|
38
|
+
const hyphenized = (0, dream_1.hyphenize)(initializerFilenameWithoutExtension);
|
|
39
|
+
const destDir = path.join((0, psychicPath_js_1.default)('conf'), 'initializers');
|
|
40
|
+
const initializerPath = path.join(destDir, `${initializerFilenameWithoutExtension}.ts`);
|
|
41
|
+
try {
|
|
42
|
+
await fs.access(destDir);
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
await fs.mkdir(destDir, { recursive: true });
|
|
46
|
+
}
|
|
47
|
+
const contents = `\
|
|
48
|
+
import { DreamCLI } from '@rvoh/dream'
|
|
49
|
+
import { PsychicApp } from "@rvoh/psychic"
|
|
50
|
+
import AppEnv from '../AppEnv.js'
|
|
51
|
+
|
|
52
|
+
export default (psy: PsychicApp) => {
|
|
53
|
+
psy.on('sync', async () => {
|
|
54
|
+
if (AppEnv.isDevelopmentOrTest) {
|
|
55
|
+
DreamCLI.logger.logStartProgress(\`[${hyphenized}] extracting types from ${openapiFilepath} to ${outfile}...\`)
|
|
56
|
+
await DreamCLI.spawn('npx openapi-typescript ${openapiFilepath} -o ${outfile}')
|
|
57
|
+
DreamCLI.logger.logEndProgress()
|
|
58
|
+
}
|
|
59
|
+
})
|
|
60
|
+
}\
|
|
61
|
+
`;
|
|
62
|
+
await fs.writeFile(initializerPath, contents);
|
|
63
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = installOpenapiTypescript;
|
|
7
|
+
const dream_1 = require("@rvoh/dream");
|
|
8
|
+
const PackageManager_js_1 = __importDefault(require("../../../cli/helpers/PackageManager.js"));
|
|
9
|
+
const EnvInternal_js_1 = __importDefault(require("../../../helpers/EnvInternal.js"));
|
|
10
|
+
async function installOpenapiTypescript() {
|
|
11
|
+
if (EnvInternal_js_1.default.isTest)
|
|
12
|
+
return;
|
|
13
|
+
const cmd = PackageManager_js_1.default.add('openapi-typescript', { dev: true });
|
|
14
|
+
try {
|
|
15
|
+
await dream_1.DreamCLI.spawn(cmd);
|
|
16
|
+
}
|
|
17
|
+
catch (err) {
|
|
18
|
+
console.log(`Failed to install openapi-typescript as a dev dependency. Please make sure the following command succeeds:
|
|
19
|
+
|
|
20
|
+
"${cmd}"
|
|
21
|
+
`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = generateSyncOpenapiTypescriptInitializer;
|
|
7
|
+
const generateInitializer_js_1 = __importDefault(require("../helpers/syncOpenapiTypescript/generateInitializer.js"));
|
|
8
|
+
const installOpenapiTypescript_js_1 = __importDefault(require("../helpers/syncOpenapiTypescript/installOpenapiTypescript.js"));
|
|
9
|
+
async function generateSyncOpenapiTypescriptInitializer(openapiFilepath, outfile, initializerFilename = 'sync-openapi-typescript.ts') {
|
|
10
|
+
await (0, generateInitializer_js_1.default)(openapiFilepath, outfile, initializerFilename);
|
|
11
|
+
await (0, installOpenapiTypescript_js_1.default)();
|
|
12
|
+
}
|
|
@@ -15,6 +15,8 @@ const openapiRoute_js_1 = __importDefault(require("./helpers/openapiRoute.js"));
|
|
|
15
15
|
const primitiveOpenapiStatementToOpenapi_js_1 = __importDefault(require("./helpers/primitiveOpenapiStatementToOpenapi.js"));
|
|
16
16
|
const serializer_js_1 = __importDefault(require("./serializer.js"));
|
|
17
17
|
const FailedToLookupSerializerForEndpoint_js_1 = __importDefault(require("../error/openapi/FailedToLookupSerializerForEndpoint.js"));
|
|
18
|
+
const pageParamOpenapiProperty_js_1 = __importDefault(require("./helpers/pageParamOpenapiProperty.js"));
|
|
19
|
+
const safelyAttachPaginationParamsToBodySegment_js_1 = __importDefault(require("./helpers/safelyAttachPaginationParamsToBodySegment.js"));
|
|
18
20
|
class OpenapiEndpointRenderer {
|
|
19
21
|
dreamsOrSerializers;
|
|
20
22
|
controllerClass;
|
|
@@ -299,7 +301,7 @@ class OpenapiEndpointRenderer {
|
|
|
299
301
|
* "parameters" field for a single endpoint.
|
|
300
302
|
*/
|
|
301
303
|
queryArray(openapiName) {
|
|
302
|
-
|
|
304
|
+
const queryParams = Object.keys(this.query || {}).map((queryName) => {
|
|
303
305
|
const queryParam = this.query[queryName];
|
|
304
306
|
let output = {
|
|
305
307
|
in: 'query',
|
|
@@ -325,7 +327,21 @@ class OpenapiEndpointRenderer {
|
|
|
325
327
|
};
|
|
326
328
|
}
|
|
327
329
|
return output;
|
|
328
|
-
}) || []
|
|
330
|
+
}) || [];
|
|
331
|
+
const paginationName = this.paginate?.query;
|
|
332
|
+
if (paginationName) {
|
|
333
|
+
queryParams.push({
|
|
334
|
+
in: 'query',
|
|
335
|
+
required: false,
|
|
336
|
+
name: paginationName,
|
|
337
|
+
description: 'Page number',
|
|
338
|
+
allowReserved: true,
|
|
339
|
+
schema: {
|
|
340
|
+
type: 'string',
|
|
341
|
+
},
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
return queryParams;
|
|
329
345
|
}
|
|
330
346
|
/**
|
|
331
347
|
* @internal
|
|
@@ -335,17 +351,22 @@ class OpenapiEndpointRenderer {
|
|
|
335
351
|
computedRequestBody(openapiName, processedSchemas, routes) {
|
|
336
352
|
const method = this.computedMethod(routes);
|
|
337
353
|
if (this.requestBody === null)
|
|
338
|
-
return
|
|
354
|
+
return this.defaultRequestBody();
|
|
339
355
|
const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
|
|
340
356
|
if (!httpMethodsThatAllowBody.includes(method))
|
|
341
|
-
return
|
|
357
|
+
return this.defaultRequestBody();
|
|
342
358
|
if (this.shouldAutogenerateBody()) {
|
|
343
359
|
return this.generateRequestBodyForModel(openapiName, processedSchemas);
|
|
344
360
|
}
|
|
345
361
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
346
|
-
|
|
362
|
+
let schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
|
|
347
363
|
target: 'request',
|
|
348
364
|
});
|
|
365
|
+
const bodyPageParam = this.paginate?.body;
|
|
366
|
+
if (bodyPageParam) {
|
|
367
|
+
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
368
|
+
schema = (0, safelyAttachPaginationParamsToBodySegment_js_1.default)(bodyPageParam, schema);
|
|
369
|
+
}
|
|
349
370
|
if (!schema)
|
|
350
371
|
return undefined;
|
|
351
372
|
return {
|
|
@@ -357,6 +378,24 @@ class OpenapiEndpointRenderer {
|
|
|
357
378
|
},
|
|
358
379
|
};
|
|
359
380
|
}
|
|
381
|
+
defaultRequestBody() {
|
|
382
|
+
const bodyPageParam = this.paginate?.body;
|
|
383
|
+
if (bodyPageParam) {
|
|
384
|
+
return {
|
|
385
|
+
content: {
|
|
386
|
+
'application/json': {
|
|
387
|
+
schema: {
|
|
388
|
+
type: 'object',
|
|
389
|
+
properties: {
|
|
390
|
+
[bodyPageParam]: (0, pageParamOpenapiProperty_js_1.default)(),
|
|
391
|
+
},
|
|
392
|
+
},
|
|
393
|
+
},
|
|
394
|
+
},
|
|
395
|
+
};
|
|
396
|
+
}
|
|
397
|
+
return undefined;
|
|
398
|
+
}
|
|
360
399
|
/**
|
|
361
400
|
* @internal
|
|
362
401
|
*
|
|
@@ -390,7 +429,7 @@ class OpenapiEndpointRenderer {
|
|
|
390
429
|
const forDreamClass = this.requestBody?.for;
|
|
391
430
|
const dreamClass = forDreamClass || this.getSingleDreamModelClass();
|
|
392
431
|
if (!dreamClass)
|
|
393
|
-
return
|
|
432
|
+
return this.defaultRequestBody();
|
|
394
433
|
let paramSafeColumns = dreamClass.paramSafeColumnsOrFallback();
|
|
395
434
|
const only = this.requestBody?.only;
|
|
396
435
|
if (only) {
|
|
@@ -541,12 +580,17 @@ class OpenapiEndpointRenderer {
|
|
|
541
580
|
}
|
|
542
581
|
}
|
|
543
582
|
}
|
|
583
|
+
let processedSchema = this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
|
|
584
|
+
target: 'request',
|
|
585
|
+
});
|
|
586
|
+
const bodyPageParam = this.paginate?.body;
|
|
587
|
+
if (bodyPageParam) {
|
|
588
|
+
processedSchema = (0, safelyAttachPaginationParamsToBodySegment_js_1.default)(bodyPageParam, processedSchema);
|
|
589
|
+
}
|
|
544
590
|
return {
|
|
545
591
|
content: {
|
|
546
592
|
'application/json': {
|
|
547
|
-
schema:
|
|
548
|
-
target: 'request',
|
|
549
|
-
}),
|
|
593
|
+
schema: processedSchema,
|
|
550
594
|
},
|
|
551
595
|
},
|
|
552
596
|
};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.default = safelyAttachPaginationParamToRequestBodySegment;
|
|
7
|
+
const pageParamOpenapiProperty_js_1 = __importDefault(require("./pageParamOpenapiProperty.js"));
|
|
8
|
+
/**
|
|
9
|
+
* @internal
|
|
10
|
+
*
|
|
11
|
+
* Used to carefully bind implicit pagination params
|
|
12
|
+
* to the requestBody properties. It will not apply
|
|
13
|
+
* the pagination param unless the provided bodySegment
|
|
14
|
+
* is:
|
|
15
|
+
*
|
|
16
|
+
* a.) falsey (null, undefined, etc...)
|
|
17
|
+
* b.) an openapi object (must have { type: 'object'})
|
|
18
|
+
*
|
|
19
|
+
* If neither of these apply, it will simply return
|
|
20
|
+
* what was given to it, without any modifications
|
|
21
|
+
*/
|
|
22
|
+
function safelyAttachPaginationParamToRequestBodySegment(paramName, bodySegment) {
|
|
23
|
+
bodySegment ||= {
|
|
24
|
+
type: 'object',
|
|
25
|
+
properties: {},
|
|
26
|
+
};
|
|
27
|
+
if (bodySegment.type === 'object') {
|
|
28
|
+
;
|
|
29
|
+
bodySegment.properties = {
|
|
30
|
+
...bodySegment.properties,
|
|
31
|
+
[paramName]: (0, pageParamOpenapiProperty_js_1.default)(),
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
return bodySegment;
|
|
35
|
+
}
|
|
@@ -220,19 +220,10 @@ Try setting it to something valid, like:
|
|
|
220
220
|
get inflections() {
|
|
221
221
|
return this._inflections;
|
|
222
222
|
}
|
|
223
|
-
_bootHooks = {
|
|
224
|
-
boot: [],
|
|
225
|
-
load: [],
|
|
226
|
-
'load:dev': [],
|
|
227
|
-
'load:test': [],
|
|
228
|
-
'load:prod': [],
|
|
229
|
-
};
|
|
230
|
-
get bootHooks() {
|
|
231
|
-
return this._bootHooks;
|
|
232
|
-
}
|
|
233
223
|
_specialHooks = {
|
|
234
|
-
|
|
235
|
-
|
|
224
|
+
cliSync: [],
|
|
225
|
+
serverInitBeforeMiddleware: [],
|
|
226
|
+
serverInitAfterMiddleware: [],
|
|
236
227
|
serverInitAfterRoutes: [],
|
|
237
228
|
serverStart: [],
|
|
238
229
|
serverError: [],
|
|
@@ -297,18 +288,6 @@ Try setting it to something valid, like:
|
|
|
297
288
|
if (this.booted && !force)
|
|
298
289
|
return;
|
|
299
290
|
// await new IntegrityChecker().check()
|
|
300
|
-
await this.runHooksFor('load');
|
|
301
|
-
switch (EnvInternal_js_1.default.nodeEnv) {
|
|
302
|
-
case 'development':
|
|
303
|
-
await this.runHooksFor('load:dev');
|
|
304
|
-
break;
|
|
305
|
-
case 'production':
|
|
306
|
-
await this.runHooksFor('load:prod');
|
|
307
|
-
break;
|
|
308
|
-
case 'test':
|
|
309
|
-
await this.runHooksFor('load:test');
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
291
|
await this.inflections?.();
|
|
313
292
|
this.booted = true;
|
|
314
293
|
}
|
|
@@ -320,8 +299,11 @@ Try setting it to something valid, like:
|
|
|
320
299
|
case 'server:error':
|
|
321
300
|
this._specialHooks.serverError.push(cb);
|
|
322
301
|
break;
|
|
323
|
-
case 'server:init':
|
|
324
|
-
this._specialHooks.
|
|
302
|
+
case 'server:init:before-middleware':
|
|
303
|
+
this._specialHooks.serverInitBeforeMiddleware.push(cb);
|
|
304
|
+
break;
|
|
305
|
+
case 'server:init:after-middleware':
|
|
306
|
+
this._specialHooks.serverInitAfterMiddleware.push(cb);
|
|
325
307
|
break;
|
|
326
308
|
case 'server:start':
|
|
327
309
|
this._specialHooks.serverStart.push(cb);
|
|
@@ -332,12 +314,10 @@ Try setting it to something valid, like:
|
|
|
332
314
|
case 'server:init:after-routes':
|
|
333
315
|
this._specialHooks.serverInitAfterRoutes.push(cb);
|
|
334
316
|
break;
|
|
335
|
-
case 'sync':
|
|
317
|
+
case 'cli:sync':
|
|
336
318
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
337
|
-
this._specialHooks['
|
|
319
|
+
this._specialHooks['cliSync'].push(cb);
|
|
338
320
|
break;
|
|
339
|
-
default:
|
|
340
|
-
this.bootHooks[hookEventType].push(cb);
|
|
341
321
|
}
|
|
342
322
|
}
|
|
343
323
|
set(option, unknown1, unknown2) {
|
|
@@ -429,11 +409,6 @@ Try setting it to something valid, like:
|
|
|
429
409
|
this.overrides['server:start'] = value;
|
|
430
410
|
}
|
|
431
411
|
}
|
|
432
|
-
async runHooksFor(hookEventType) {
|
|
433
|
-
for (const hook of this.bootHooks[hookEventType]) {
|
|
434
|
-
await hook(this);
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
412
|
}
|
|
438
413
|
exports.default = PsychicApp;
|
|
439
414
|
exports.PsychicAppAllowedPackageManagersEnumValues = ['yarn', 'npm', 'pnpm'];
|
|
@@ -73,7 +73,9 @@ class PsychicServer {
|
|
|
73
73
|
});
|
|
74
74
|
next();
|
|
75
75
|
});
|
|
76
|
-
|
|
76
|
+
for (const serverInitBeforeMiddlewareHook of this.config.specialHooks.serverInitBeforeMiddleware) {
|
|
77
|
+
await serverInitBeforeMiddlewareHook(this);
|
|
78
|
+
}
|
|
77
79
|
this.initializeCors();
|
|
78
80
|
this.initializeJSON();
|
|
79
81
|
try {
|
|
@@ -87,8 +89,8 @@ class PsychicServer {
|
|
|
87
89
|
${error.message}
|
|
88
90
|
`);
|
|
89
91
|
}
|
|
90
|
-
for (const
|
|
91
|
-
await
|
|
92
|
+
for (const serverInitAfterMiddlewareHook of this.config.specialHooks.serverInitAfterMiddleware) {
|
|
93
|
+
await serverInitAfterMiddlewareHook(this);
|
|
92
94
|
}
|
|
93
95
|
this.initializeOpenapiValidation();
|
|
94
96
|
await this.buildRoutes();
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import { camelize, DreamCLI } from '@rvoh/dream';
|
|
2
|
+
import * as fs from 'node:fs/promises';
|
|
3
|
+
import * as path from 'node:path';
|
|
4
|
+
import PsychicApp from '../../psychic-app/index.js';
|
|
5
|
+
import psychicPath from '../../helpers/path/psychicPath.js';
|
|
6
|
+
export default async function syncTypescriptOpenapiFiles() {
|
|
7
|
+
const psychicApp = PsychicApp.getOrFail();
|
|
8
|
+
const syncableKeys = Object.keys(psychicApp.openapi).filter(key => psychicApp.openapi[key]?.syncTypes);
|
|
9
|
+
await Promise.all(syncableKeys.map(key => {
|
|
10
|
+
const openapiOpts = psychicApp.openapi[key];
|
|
11
|
+
const jsonPath = openapiOpts.outputFilename;
|
|
12
|
+
const outpath = path.join(psychicPath('types'), `${jsonPath.replace(/\.json$/, '')}.d.ts`);
|
|
13
|
+
return DreamCLI.spawn(`npx openapi-typescript ${jsonPath} -o ${outpath}`).then(async () => {
|
|
14
|
+
const file = (await fs.readFile(outpath)).toString();
|
|
15
|
+
const exportName = camelize(jsonPath
|
|
16
|
+
.split('/')
|
|
17
|
+
.at(-1)
|
|
18
|
+
?.replace(/\.json/, '')) + 'Paths';
|
|
19
|
+
await fs.writeFile(outpath, file.replace(/export interface paths/, `export interface ${exportName}`));
|
|
20
|
+
});
|
|
21
|
+
}));
|
|
22
|
+
}
|
|
@@ -11,6 +11,7 @@ import PsychicServer from '../server/index.js';
|
|
|
11
11
|
import enumsFileStr from './helpers/enumsFileStr.js';
|
|
12
12
|
import generateRouteTypes from './helpers/generateRouteTypes.js';
|
|
13
13
|
import printRoutes from './helpers/printRoutes.js';
|
|
14
|
+
import syncTypescriptOpenapiFiles from './helpers/syncTypescriptOpenapiFiles.js';
|
|
14
15
|
export default class PsychicBin {
|
|
15
16
|
static async generateController(controllerName, actions) {
|
|
16
17
|
await generateController({ fullyQualifiedControllerName: controllerName, actions });
|
|
@@ -43,7 +44,7 @@ export default class PsychicBin {
|
|
|
43
44
|
await PsychicBin.syncOpenapiJson();
|
|
44
45
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
45
46
|
let output = {};
|
|
46
|
-
for (const hook of psychicApp.specialHooks.
|
|
47
|
+
for (const hook of psychicApp.specialHooks.cliSync) {
|
|
47
48
|
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
|
|
48
49
|
const res = await hook();
|
|
49
50
|
if (isObject(res)) {
|
|
@@ -54,6 +55,7 @@ export default class PsychicBin {
|
|
|
54
55
|
if (Object.keys(output).length) {
|
|
55
56
|
await PsychicBin.syncTypes(output);
|
|
56
57
|
}
|
|
58
|
+
await this.syncTypescriptOpenapiFiles();
|
|
57
59
|
}
|
|
58
60
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
59
61
|
static async syncTypes(customTypes = undefined) {
|
|
@@ -61,6 +63,11 @@ export default class PsychicBin {
|
|
|
61
63
|
await TypesBuilder.sync(customTypes);
|
|
62
64
|
DreamCLI.logger.logEndProgress();
|
|
63
65
|
}
|
|
66
|
+
static async syncTypescriptOpenapiFiles() {
|
|
67
|
+
DreamCLI.logger.logStartProgress(`syncing openapi types...`);
|
|
68
|
+
await syncTypescriptOpenapiFiles();
|
|
69
|
+
DreamCLI.logger.logEndProgress();
|
|
70
|
+
}
|
|
64
71
|
static async syncOpenapiJson() {
|
|
65
72
|
DreamCLI.logger.logStartProgress(`syncing openapi...`);
|
|
66
73
|
await OpenapiAppRenderer.sync();
|
|
@@ -2,6 +2,7 @@ import { DreamCLI } from '@rvoh/dream';
|
|
|
2
2
|
import PsychicBin from '../bin/index.js';
|
|
3
3
|
import generateOpenapiReduxBindings from '../generate/openapi/reduxBindings.js';
|
|
4
4
|
import generateSyncEnumsInitializer from '../generate/initializer/syncEnums.js';
|
|
5
|
+
import generateSyncOpenapiTypescriptInitializer from '../generate/initializer/syncOpenapiTypescript.js';
|
|
5
6
|
export default class PsychicCLI {
|
|
6
7
|
static provide(program, { initializePsychicApp, seedDb, }) {
|
|
7
8
|
DreamCLI.generateDreamCli(program, {
|
|
@@ -35,8 +36,17 @@ export default class PsychicCLI {
|
|
|
35
36
|
process.exit();
|
|
36
37
|
});
|
|
37
38
|
program
|
|
38
|
-
.command('
|
|
39
|
-
.
|
|
39
|
+
.command('setup:sync:enums')
|
|
40
|
+
.description('generates an initializer in your app for syncing enums to a particular path.')
|
|
41
|
+
.argument('<outfile>', 'the path from your backend directory to the location which you want the enums copied. Should end with .ts, i.e. "../client/src/api/enums.ts"')
|
|
42
|
+
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
|
|
43
|
+
.action(async (outfile, { initializerName, }) => {
|
|
44
|
+
await initializePsychicApp();
|
|
45
|
+
await generateSyncEnumsInitializer(outfile, initializerName);
|
|
46
|
+
process.exit();
|
|
47
|
+
});
|
|
48
|
+
program
|
|
49
|
+
.command('setup:sync:openapi-redux')
|
|
40
50
|
.description('generates openapi redux bindings to connect one of your openapi files to one of your clients')
|
|
41
51
|
.option('--schema-file', 'the path from your api root to the openapi file you wish to use to generate your schema, i.e. ./openapi/openapi.json')
|
|
42
52
|
.option('--api-file', 'the path to the boilerplate api file that will be used to scaffold your backend endpoints together with, i.e. ../client/app/api.ts')
|
|
@@ -55,14 +65,14 @@ export default class PsychicCLI {
|
|
|
55
65
|
process.exit();
|
|
56
66
|
});
|
|
57
67
|
program
|
|
58
|
-
.command('
|
|
59
|
-
.
|
|
60
|
-
.
|
|
61
|
-
.argument('<outfile>', 'the path from your backend directory to the location which you want the
|
|
62
|
-
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-
|
|
63
|
-
.action(async (outfile, { initializerName, }) => {
|
|
68
|
+
.command('setup:sync:openapi-typescript')
|
|
69
|
+
.description('generates an initializer in your app for converting one of your openapi files to typescript')
|
|
70
|
+
.argument('<openapiFilepath>', 'the path from your backend directory to the openapi file you wish to scan, i.e. "./openapi/openapi.json"')
|
|
71
|
+
.argument('<outfile>', 'the path from your backend directory to the location which you want the openapi types written to. Must end with .d.ts, i.e. "./src/conf/openapi/openapi.types.d.ts"')
|
|
72
|
+
.option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-openapi-typescript.ts`')
|
|
73
|
+
.action(async (openapiFilepath, outfile, { initializerName, }) => {
|
|
64
74
|
await initializePsychicApp();
|
|
65
|
-
await
|
|
75
|
+
await generateSyncOpenapiTypescriptInitializer(openapiFilepath, outfile, initializerName);
|
|
66
76
|
process.exit();
|
|
67
77
|
});
|
|
68
78
|
program
|