@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.
Files changed (40) hide show
  1. package/dist/cjs/src/bin/helpers/syncTypescriptOpenapiFiles.js +51 -0
  2. package/dist/cjs/src/bin/index.js +8 -1
  3. package/dist/cjs/src/cli/index.js +19 -9
  4. package/dist/cjs/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
  5. package/dist/cjs/src/generate/helpers/syncOpenapiTypescript/generateInitializer.js +63 -0
  6. package/dist/cjs/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.js +23 -0
  7. package/dist/cjs/src/generate/initializer/syncOpenapiTypescript.js +12 -0
  8. package/dist/cjs/src/helpers/openapiTypeHelpers.js +2 -0
  9. package/dist/cjs/src/openapi-renderer/endpoint.js +53 -9
  10. package/dist/cjs/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +9 -0
  11. package/dist/cjs/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +35 -0
  12. package/dist/cjs/src/psychic-app/index.js +10 -35
  13. package/dist/cjs/src/server/index.js +5 -3
  14. package/dist/esm/src/bin/helpers/syncTypescriptOpenapiFiles.js +22 -0
  15. package/dist/esm/src/bin/index.js +8 -1
  16. package/dist/esm/src/cli/index.js +19 -9
  17. package/dist/esm/src/generate/helpers/reduxBindings/writeApiFile.js +1 -1
  18. package/dist/esm/src/generate/helpers/syncOpenapiTypescript/generateInitializer.js +34 -0
  19. package/dist/esm/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.js +17 -0
  20. package/dist/esm/src/generate/initializer/syncOpenapiTypescript.js +6 -0
  21. package/dist/esm/src/helpers/openapiTypeHelpers.js +1 -0
  22. package/dist/esm/src/openapi-renderer/endpoint.js +53 -9
  23. package/dist/esm/src/openapi-renderer/helpers/pageParamOpenapiProperty.js +6 -0
  24. package/dist/esm/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.js +29 -0
  25. package/dist/esm/src/psychic-app/index.js +10 -35
  26. package/dist/esm/src/server/index.js +5 -3
  27. package/dist/types/src/bin/helpers/syncTypescriptOpenapiFiles.d.ts +1 -0
  28. package/dist/types/src/bin/index.d.ts +1 -0
  29. package/dist/types/src/generate/helpers/syncOpenapiTypescript/generateInitializer.d.ts +1 -0
  30. package/dist/types/src/generate/helpers/syncOpenapiTypescript/installOpenapiTypescript.d.ts +1 -0
  31. package/dist/types/src/generate/initializer/syncOpenapiTypescript.d.ts +1 -0
  32. package/dist/types/src/helpers/openapiTypeHelpers.d.ts +3 -0
  33. package/dist/types/src/helpers/path/psychicPath.d.ts +1 -1
  34. package/dist/types/src/helpers/typeHelpers.d.ts +1 -0
  35. package/dist/types/src/openapi-renderer/endpoint.d.ts +49 -1
  36. package/dist/types/src/openapi-renderer/helpers/pageParamOpenapiProperty.d.ts +4 -0
  37. package/dist/types/src/openapi-renderer/helpers/safelyAttachPaginationParamsToBodySegment.d.ts +15 -0
  38. package/dist/types/src/psychic-app/index.d.ts +240 -7
  39. package/dist/types/src/psychic-app/types.d.ts +1 -2
  40. 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.sync) {
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('generate:openapi:redux')
44
- .alias('g:openapi:redux')
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('generate:initializer:sync-enums')
64
- .alias('g:initializer:sync-enums')
65
- .description('generates an initializer in your app for syncing enums to a particular path.')
66
- .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"')
67
- .option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
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, syncEnums_js_1.default)(outfile, initializerName);
80
+ await (0, syncOpenapiTypescript_js_1.default)(openapiFilepath, outfile, initializerName);
71
81
  process.exit();
72
82
  });
73
83
  program
@@ -65,7 +65,7 @@ export const ${apiImport} = createApi({
65
65
  keepUnusedDataFor: 0,
66
66
 
67
67
  baseQuery: fetchBaseQuery({
68
- baseUrl: baseURL(),
68
+ baseUrl: baseUrl(),
69
69
  credentials: 'include',
70
70
 
71
71
  // we recommend that you use a function like this for preparing
@@ -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
+ }
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -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
- return (Object.keys(this.query || {}).map((queryName) => {
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 undefined;
354
+ return this.defaultRequestBody();
339
355
  const httpMethodsThatAllowBody = ['post', 'patch', 'put'];
340
356
  if (!httpMethodsThatAllowBody.includes(method))
341
- return undefined;
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
- const schema = this.recursivelyParseBody(openapiName, this.requestBody, processedSchemas, {
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 undefined;
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: this.recursivelyParseBody(openapiName, paramsShape, processedSchemas, {
548
- target: 'request',
549
- }),
593
+ schema: processedSchema,
550
594
  },
551
595
  },
552
596
  };
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.default = openapiPageParamProperty;
4
+ function openapiPageParamProperty() {
5
+ return {
6
+ type: 'integer',
7
+ description: 'Page number',
8
+ };
9
+ }
@@ -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
- sync: [],
235
- serverInit: [],
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.serverInit.push(cb);
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['sync'].push(cb);
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
- await this.config['runHooksFor']('boot');
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 expressInitHook of this.config.specialHooks.serverInit) {
91
- await expressInitHook(this);
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.sync) {
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('generate:openapi:redux')
39
- .alias('g:openapi:redux')
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('generate:initializer:sync-enums')
59
- .alias('g:initializer:sync-enums')
60
- .description('generates an initializer in your app for syncing enums to a particular path.')
61
- .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"')
62
- .option('--initializer-filename', 'the name you want the file to be in your initializers folder. defaults to `sync-enums.ts`')
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 generateSyncEnumsInitializer(outfile, initializerName);
75
+ await generateSyncOpenapiTypescriptInitializer(openapiFilepath, outfile, initializerName);
66
76
  process.exit();
67
77
  });
68
78
  program
@@ -39,7 +39,7 @@ export const ${apiImport} = createApi({
39
39
  keepUnusedDataFor: 0,
40
40
 
41
41
  baseQuery: fetchBaseQuery({
42
- baseUrl: baseURL(),
42
+ baseUrl: baseUrl(),
43
43
  credentials: 'include',
44
44
 
45
45
  // we recommend that you use a function like this for preparing