@rvoh/psychic 2.0.4 → 2.1.0

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.
@@ -1,7 +1,7 @@
1
1
  import { CliFileWriter, DreamBin, DreamCLI } from '@rvoh/dream/system';
2
2
  import * as fs from 'node:fs/promises';
3
3
  import * as path from 'node:path';
4
- import TypesBuilder from '../cli/helpers/TypesBuilder.js';
4
+ import ASTPsychicTypesBuilder from '../cli/helpers/ASTPsychicTypesBuilder.js';
5
5
  import generateController from '../generate/controller.js';
6
6
  import generateResource from '../generate/resource.js';
7
7
  import isObject from '../helpers/isObject.js';
@@ -54,11 +54,10 @@ export default class PsychicBin {
54
54
  await CliFileWriter.revert();
55
55
  }
56
56
  }
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- static async syncTypes(customTypes = undefined) {
59
- DreamCLI.logger.logStartProgress(`syncing types/psychic.ts...`);
60
- await TypesBuilder.sync(customTypes);
61
- DreamCLI.logger.logEndProgress();
57
+ static async syncTypes() {
58
+ await DreamCLI.logger.logProgress(`syncing types/psychic.ts...`, async () => {
59
+ await new ASTPsychicTypesBuilder().build();
60
+ });
62
61
  }
63
62
  static async syncOpenapiTypescriptFiles() {
64
63
  DreamCLI.logger.logStartProgress(`syncing openapi types...`);
@@ -117,8 +116,5 @@ export default class PsychicBin {
117
116
  output = { ...output, ...res };
118
117
  }
119
118
  }
120
- if (Object.keys(output).length) {
121
- await PsychicBin.syncTypes(output);
122
- }
123
119
  }
124
120
  }
@@ -0,0 +1,175 @@
1
+ import { DreamApp } from '@rvoh/dream';
2
+ import * as path from 'node:path';
3
+ import ts from 'typescript';
4
+ const f = ts.factory;
5
+ /**
6
+ * @internal
7
+ *
8
+ * This is a base class, which is inherited by the ASTSchemaBuilder,
9
+ * the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
10
+ * each of which is responsible for building up the output of the various
11
+ * type files consumed by dream internally.
12
+ *
13
+ * This base class is just a container for common methods used by all
14
+ * classes.
15
+ */
16
+ export default class ASTBuilder {
17
+ /**
18
+ * @internal
19
+ *
20
+ * builds a new line, useful for injecting new lines into AST statements
21
+ */
22
+ newLine() {
23
+ return f.createIdentifier('\n');
24
+ }
25
+ /**
26
+ * @internal
27
+ *
28
+ * given an interface declaration, it will extrace the relevant property statement
29
+ * by the given property name.
30
+ */
31
+ getPropertyFromInterface(interfaceNode, propertyName) {
32
+ for (const member of interfaceNode.members) {
33
+ if (ts.isPropertySignature(member)) {
34
+ if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
35
+ return member;
36
+ }
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * @internal
43
+ *
44
+ * returns an array of string type literals which were extracted from
45
+ * either a type or type union, depending on what is provided
46
+ * for the typeAlias. this allows you to safely and easily collect
47
+ * an array of types given an alias
48
+ */
49
+ extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
50
+ const literals = [];
51
+ if (ts.isUnionTypeNode(typeAlias.type)) {
52
+ typeAlias.type.types.forEach(typeNode => {
53
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
54
+ literals.push(typeNode);
55
+ }
56
+ });
57
+ }
58
+ else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
59
+ literals.push(typeAlias.type);
60
+ }
61
+ return literals;
62
+ }
63
+ /**
64
+ * @internal
65
+ *
66
+ * returns an array of type literals which were extracted from
67
+ * either a type or type union, depending on what is provided
68
+ * for the typeAlias. this allows you to safely and easily collect
69
+ * an array of types given an alias
70
+ */
71
+ extractTypeNodesFromTypeOrUnion(typeAlias) {
72
+ const literals = [];
73
+ if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
74
+ typeAlias.type.types.forEach(typeNode => {
75
+ literals.push(typeNode);
76
+ });
77
+ }
78
+ else if (typeAlias.type) {
79
+ literals.push(typeAlias.type);
80
+ }
81
+ return literals;
82
+ }
83
+ /**
84
+ * @internal
85
+ *
86
+ * returns the provided node iff
87
+ * a.) the node is an exported type alias
88
+ * b.) the exported name matches the provided name (or else there was no name provided)
89
+ *
90
+ * otherwise, returns null
91
+ */
92
+ exportedTypeAliasOrNull(node, exportName) {
93
+ if (ts.isTypeAliasDeclaration(node) &&
94
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
95
+ (!exportName ? true : node.name.text === exportName))
96
+ return node;
97
+ return null;
98
+ }
99
+ /**
100
+ * @internal
101
+ *
102
+ * returns the provided node iff
103
+ * a.) the node is an exported interface
104
+ * b.) the exported name matches the provided name (or else there was no name provided)
105
+ *
106
+ * otherwise, returns null
107
+ */
108
+ exportedInterfaceOrNull(node, exportName) {
109
+ if (ts.isInterfaceDeclaration(node) &&
110
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
111
+ (!exportName ? true : node.name.text === exportName))
112
+ return node;
113
+ return null;
114
+ }
115
+ /**
116
+ * @internal
117
+ *
118
+ * returns the path to the dream.globals.ts file
119
+ */
120
+ psychicTypesPath() {
121
+ const dreamApp = DreamApp.getOrFail();
122
+ return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
123
+ }
124
+ /**
125
+ * @internal
126
+ *
127
+ * safely runs prettier against the provided output. If prettier
128
+ * is not installed, then the original output is returned
129
+ */
130
+ async prettier(output) {
131
+ try {
132
+ // dynamically, safely bring in prettier.
133
+ // ini the event that it fails, we will return the
134
+ // original output, unformatted, since prettier
135
+ // is technically not a real dependency of dream,
136
+ // though psychic and dream apps are provisioned
137
+ // with prettier by default, so this should usually work
138
+ const prettier = (await import('prettier')).default;
139
+ const results = await prettier.format(output, {
140
+ parser: 'typescript',
141
+ semi: false,
142
+ singleQuote: true,
143
+ tabWidth: 2,
144
+ lineWidth: 80,
145
+ });
146
+ return typeof results === 'string' ? results : output;
147
+ }
148
+ catch {
149
+ // intentional noop, we don't want to raise if prettier
150
+ // fails, since it is possible for the end user to not
151
+ // want to use prettier, and it is not a required peer
152
+ // dependency of dream
153
+ return output;
154
+ }
155
+ }
156
+ /**
157
+ * @internal
158
+ *
159
+ * given a type node, it will send back the first found generic
160
+ * provided to that type.
161
+ */
162
+ getFirstGenericType(node) {
163
+ if (ts.isTypeReferenceNode(node)) {
164
+ if (node.typeArguments && node.typeArguments.length > 0) {
165
+ return node.typeArguments[0];
166
+ }
167
+ }
168
+ else if (ts.isCallExpression(node)) {
169
+ if (node.typeArguments && node.typeArguments.length > 0) {
170
+ return node.typeArguments[0];
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ }
@@ -0,0 +1,59 @@
1
+ import { CliFileWriter, DreamCLI } from '@rvoh/dream/system';
2
+ import ts from 'typescript';
3
+ import PsychicApp from '../../psychic-app/index.js';
4
+ import ASTBuilder from './ASTBuilder.js';
5
+ const f = ts.factory;
6
+ /**
7
+ * Responsible for building dream globals, which can be found at
8
+ * types/dream.globals.ts.
9
+ *
10
+ * This class leverages internal AST building mechanisms built into
11
+ * typescript to manually build up object literals and interfaces
12
+ * for our app to consume.
13
+ */
14
+ export default class ASTPsychicTypesBuilder extends ASTBuilder {
15
+ async build() {
16
+ const logger = DreamCLI.logger;
17
+ const sourceFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
18
+ await logger.logProgress('[psychic] building psychic types', async () => {
19
+ const output = await this.prettier(this.printStatements(this.buildPsychicTypes(), sourceFile));
20
+ await CliFileWriter.write(this.psychicTypesPath(), output);
21
+ });
22
+ }
23
+ /**
24
+ * @internal
25
+ *
26
+ * builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
27
+ * file. It does this by leveraging low-level AST utils built into typescript
28
+ * to manually build up an object literal, cast it as a const, and write it to
29
+ * an exported variable.
30
+ */
31
+ buildPsychicTypes() {
32
+ const psychicApp = PsychicApp.getOrFail();
33
+ const psychicTypesObjectLiteral = f.createObjectLiteralExpression([
34
+ f.createPropertyAssignment(f.createIdentifier('openapiNames'), f.createArrayLiteralExpression(Object.keys(psychicApp.openapi).map(key => f.createStringLiteral(key)))),
35
+ ], true);
36
+ // add "as const" to the end of the schema object we
37
+ // have built before returning it
38
+ const constAssertion = f.createAsExpression(psychicTypesObjectLiteral, f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword));
39
+ const psychicTypesObjectLiteralConst = f.createVariableStatement(undefined, f.createVariableDeclarationList([
40
+ f.createVariableDeclaration(f.createIdentifier('psychicTypes'), undefined, undefined, constAssertion),
41
+ ], ts.NodeFlags.Const));
42
+ const defaultExportIdentifier = f.createIdentifier('psychicTypes');
43
+ const exportDefaultStatement = f.createExportDefault(defaultExportIdentifier);
44
+ return [psychicTypesObjectLiteralConst, this.newLine(), exportDefaultStatement];
45
+ }
46
+ /**
47
+ * @internal
48
+ *
49
+ * writes the compiled statements to string.
50
+ *
51
+ */
52
+ printStatements(statements, sourceFile) {
53
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, omitTrailingSemicolon: true });
54
+ const result = printer.printList(ts.ListFormat.SourceFileStatements, f.createNodeArray(statements), sourceFile);
55
+ // TODO: add autogenerate disclaimer
56
+ return `\
57
+ ${result}`;
58
+ }
59
+ }
@@ -1,7 +1,7 @@
1
1
  import { CliFileWriter, DreamBin, DreamCLI } from '@rvoh/dream/system';
2
2
  import * as fs from 'node:fs/promises';
3
3
  import * as path from 'node:path';
4
- import TypesBuilder from '../cli/helpers/TypesBuilder.js';
4
+ import ASTPsychicTypesBuilder from '../cli/helpers/ASTPsychicTypesBuilder.js';
5
5
  import generateController from '../generate/controller.js';
6
6
  import generateResource from '../generate/resource.js';
7
7
  import isObject from '../helpers/isObject.js';
@@ -54,11 +54,10 @@ export default class PsychicBin {
54
54
  await CliFileWriter.revert();
55
55
  }
56
56
  }
57
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
58
- static async syncTypes(customTypes = undefined) {
59
- DreamCLI.logger.logStartProgress(`syncing types/psychic.ts...`);
60
- await TypesBuilder.sync(customTypes);
61
- DreamCLI.logger.logEndProgress();
57
+ static async syncTypes() {
58
+ await DreamCLI.logger.logProgress(`syncing types/psychic.ts...`, async () => {
59
+ await new ASTPsychicTypesBuilder().build();
60
+ });
62
61
  }
63
62
  static async syncOpenapiTypescriptFiles() {
64
63
  DreamCLI.logger.logStartProgress(`syncing openapi types...`);
@@ -117,8 +116,5 @@ export default class PsychicBin {
117
116
  output = { ...output, ...res };
118
117
  }
119
118
  }
120
- if (Object.keys(output).length) {
121
- await PsychicBin.syncTypes(output);
122
- }
123
119
  }
124
120
  }
@@ -0,0 +1,175 @@
1
+ import { DreamApp } from '@rvoh/dream';
2
+ import * as path from 'node:path';
3
+ import ts from 'typescript';
4
+ const f = ts.factory;
5
+ /**
6
+ * @internal
7
+ *
8
+ * This is a base class, which is inherited by the ASTSchemaBuilder,
9
+ * the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
10
+ * each of which is responsible for building up the output of the various
11
+ * type files consumed by dream internally.
12
+ *
13
+ * This base class is just a container for common methods used by all
14
+ * classes.
15
+ */
16
+ export default class ASTBuilder {
17
+ /**
18
+ * @internal
19
+ *
20
+ * builds a new line, useful for injecting new lines into AST statements
21
+ */
22
+ newLine() {
23
+ return f.createIdentifier('\n');
24
+ }
25
+ /**
26
+ * @internal
27
+ *
28
+ * given an interface declaration, it will extrace the relevant property statement
29
+ * by the given property name.
30
+ */
31
+ getPropertyFromInterface(interfaceNode, propertyName) {
32
+ for (const member of interfaceNode.members) {
33
+ if (ts.isPropertySignature(member)) {
34
+ if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
35
+ return member;
36
+ }
37
+ }
38
+ }
39
+ return null;
40
+ }
41
+ /**
42
+ * @internal
43
+ *
44
+ * returns an array of string type literals which were extracted from
45
+ * either a type or type union, depending on what is provided
46
+ * for the typeAlias. this allows you to safely and easily collect
47
+ * an array of types given an alias
48
+ */
49
+ extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
50
+ const literals = [];
51
+ if (ts.isUnionTypeNode(typeAlias.type)) {
52
+ typeAlias.type.types.forEach(typeNode => {
53
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
54
+ literals.push(typeNode);
55
+ }
56
+ });
57
+ }
58
+ else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
59
+ literals.push(typeAlias.type);
60
+ }
61
+ return literals;
62
+ }
63
+ /**
64
+ * @internal
65
+ *
66
+ * returns an array of type literals which were extracted from
67
+ * either a type or type union, depending on what is provided
68
+ * for the typeAlias. this allows you to safely and easily collect
69
+ * an array of types given an alias
70
+ */
71
+ extractTypeNodesFromTypeOrUnion(typeAlias) {
72
+ const literals = [];
73
+ if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
74
+ typeAlias.type.types.forEach(typeNode => {
75
+ literals.push(typeNode);
76
+ });
77
+ }
78
+ else if (typeAlias.type) {
79
+ literals.push(typeAlias.type);
80
+ }
81
+ return literals;
82
+ }
83
+ /**
84
+ * @internal
85
+ *
86
+ * returns the provided node iff
87
+ * a.) the node is an exported type alias
88
+ * b.) the exported name matches the provided name (or else there was no name provided)
89
+ *
90
+ * otherwise, returns null
91
+ */
92
+ exportedTypeAliasOrNull(node, exportName) {
93
+ if (ts.isTypeAliasDeclaration(node) &&
94
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
95
+ (!exportName ? true : node.name.text === exportName))
96
+ return node;
97
+ return null;
98
+ }
99
+ /**
100
+ * @internal
101
+ *
102
+ * returns the provided node iff
103
+ * a.) the node is an exported interface
104
+ * b.) the exported name matches the provided name (or else there was no name provided)
105
+ *
106
+ * otherwise, returns null
107
+ */
108
+ exportedInterfaceOrNull(node, exportName) {
109
+ if (ts.isInterfaceDeclaration(node) &&
110
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
111
+ (!exportName ? true : node.name.text === exportName))
112
+ return node;
113
+ return null;
114
+ }
115
+ /**
116
+ * @internal
117
+ *
118
+ * returns the path to the dream.globals.ts file
119
+ */
120
+ psychicTypesPath() {
121
+ const dreamApp = DreamApp.getOrFail();
122
+ return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
123
+ }
124
+ /**
125
+ * @internal
126
+ *
127
+ * safely runs prettier against the provided output. If prettier
128
+ * is not installed, then the original output is returned
129
+ */
130
+ async prettier(output) {
131
+ try {
132
+ // dynamically, safely bring in prettier.
133
+ // ini the event that it fails, we will return the
134
+ // original output, unformatted, since prettier
135
+ // is technically not a real dependency of dream,
136
+ // though psychic and dream apps are provisioned
137
+ // with prettier by default, so this should usually work
138
+ const prettier = (await import('prettier')).default;
139
+ const results = await prettier.format(output, {
140
+ parser: 'typescript',
141
+ semi: false,
142
+ singleQuote: true,
143
+ tabWidth: 2,
144
+ lineWidth: 80,
145
+ });
146
+ return typeof results === 'string' ? results : output;
147
+ }
148
+ catch {
149
+ // intentional noop, we don't want to raise if prettier
150
+ // fails, since it is possible for the end user to not
151
+ // want to use prettier, and it is not a required peer
152
+ // dependency of dream
153
+ return output;
154
+ }
155
+ }
156
+ /**
157
+ * @internal
158
+ *
159
+ * given a type node, it will send back the first found generic
160
+ * provided to that type.
161
+ */
162
+ getFirstGenericType(node) {
163
+ if (ts.isTypeReferenceNode(node)) {
164
+ if (node.typeArguments && node.typeArguments.length > 0) {
165
+ return node.typeArguments[0];
166
+ }
167
+ }
168
+ else if (ts.isCallExpression(node)) {
169
+ if (node.typeArguments && node.typeArguments.length > 0) {
170
+ return node.typeArguments[0];
171
+ }
172
+ }
173
+ return null;
174
+ }
175
+ }
@@ -0,0 +1,59 @@
1
+ import { CliFileWriter, DreamCLI } from '@rvoh/dream/system';
2
+ import ts from 'typescript';
3
+ import PsychicApp from '../../psychic-app/index.js';
4
+ import ASTBuilder from './ASTBuilder.js';
5
+ const f = ts.factory;
6
+ /**
7
+ * Responsible for building dream globals, which can be found at
8
+ * types/dream.globals.ts.
9
+ *
10
+ * This class leverages internal AST building mechanisms built into
11
+ * typescript to manually build up object literals and interfaces
12
+ * for our app to consume.
13
+ */
14
+ export default class ASTPsychicTypesBuilder extends ASTBuilder {
15
+ async build() {
16
+ const logger = DreamCLI.logger;
17
+ const sourceFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
18
+ await logger.logProgress('[psychic] building psychic types', async () => {
19
+ const output = await this.prettier(this.printStatements(this.buildPsychicTypes(), sourceFile));
20
+ await CliFileWriter.write(this.psychicTypesPath(), output);
21
+ });
22
+ }
23
+ /**
24
+ * @internal
25
+ *
26
+ * builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
27
+ * file. It does this by leveraging low-level AST utils built into typescript
28
+ * to manually build up an object literal, cast it as a const, and write it to
29
+ * an exported variable.
30
+ */
31
+ buildPsychicTypes() {
32
+ const psychicApp = PsychicApp.getOrFail();
33
+ const psychicTypesObjectLiteral = f.createObjectLiteralExpression([
34
+ f.createPropertyAssignment(f.createIdentifier('openapiNames'), f.createArrayLiteralExpression(Object.keys(psychicApp.openapi).map(key => f.createStringLiteral(key)))),
35
+ ], true);
36
+ // add "as const" to the end of the schema object we
37
+ // have built before returning it
38
+ const constAssertion = f.createAsExpression(psychicTypesObjectLiteral, f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword));
39
+ const psychicTypesObjectLiteralConst = f.createVariableStatement(undefined, f.createVariableDeclarationList([
40
+ f.createVariableDeclaration(f.createIdentifier('psychicTypes'), undefined, undefined, constAssertion),
41
+ ], ts.NodeFlags.Const));
42
+ const defaultExportIdentifier = f.createIdentifier('psychicTypes');
43
+ const exportDefaultStatement = f.createExportDefault(defaultExportIdentifier);
44
+ return [psychicTypesObjectLiteralConst, this.newLine(), exportDefaultStatement];
45
+ }
46
+ /**
47
+ * @internal
48
+ *
49
+ * writes the compiled statements to string.
50
+ *
51
+ */
52
+ printStatements(statements, sourceFile) {
53
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, omitTrailingSemicolon: true });
54
+ const result = printer.printList(ts.ListFormat.SourceFileStatements, f.createNodeArray(statements), sourceFile);
55
+ // TODO: add autogenerate disclaimer
56
+ return `\
57
+ ${result}`;
58
+ }
59
+ }
@@ -13,7 +13,7 @@ export default class PsychicBin {
13
13
  schemaOnly?: boolean;
14
14
  }): Promise<void>;
15
15
  static postSync(): Promise<void>;
16
- static syncTypes(customTypes?: any): Promise<void>;
16
+ static syncTypes(): Promise<void>;
17
17
  static syncOpenapiTypescriptFiles(): Promise<void>;
18
18
  static syncOpenapiJson(): Promise<void>;
19
19
  static syncRoutes(): Promise<void>;
@@ -0,0 +1,89 @@
1
+ import ts from 'typescript';
2
+ /**
3
+ * @internal
4
+ *
5
+ * This is a base class, which is inherited by the ASTSchemaBuilder,
6
+ * the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
7
+ * each of which is responsible for building up the output of the various
8
+ * type files consumed by dream internally.
9
+ *
10
+ * This base class is just a container for common methods used by all
11
+ * classes.
12
+ */
13
+ export default class ASTBuilder {
14
+ /**
15
+ * @internal
16
+ *
17
+ * builds a new line, useful for injecting new lines into AST statements
18
+ */
19
+ protected newLine(): ts.Identifier;
20
+ /**
21
+ * @internal
22
+ *
23
+ * given an interface declaration, it will extrace the relevant property statement
24
+ * by the given property name.
25
+ */
26
+ protected getPropertyFromInterface(interfaceNode: ts.InterfaceDeclaration, propertyName: string): ts.PropertySignature | null;
27
+ /**
28
+ * @internal
29
+ *
30
+ * returns an array of string type literals which were extracted from
31
+ * either a type or type union, depending on what is provided
32
+ * for the typeAlias. this allows you to safely and easily collect
33
+ * an array of types given an alias
34
+ */
35
+ protected extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias: ts.TypeAliasDeclaration): (ts.LiteralTypeNode & {
36
+ literal: {
37
+ text: string;
38
+ };
39
+ })[];
40
+ /**
41
+ * @internal
42
+ *
43
+ * returns an array of type literals which were extracted from
44
+ * either a type or type union, depending on what is provided
45
+ * for the typeAlias. this allows you to safely and easily collect
46
+ * an array of types given an alias
47
+ */
48
+ protected extractTypeNodesFromTypeOrUnion(typeAlias: ts.TypeAliasDeclaration | ts.PropertySignature): ts.TypeNode[];
49
+ /**
50
+ * @internal
51
+ *
52
+ * returns the provided node iff
53
+ * a.) the node is an exported type alias
54
+ * b.) the exported name matches the provided name (or else there was no name provided)
55
+ *
56
+ * otherwise, returns null
57
+ */
58
+ protected exportedTypeAliasOrNull(node: ts.Node, exportName?: string): ts.TypeAliasDeclaration | null;
59
+ /**
60
+ * @internal
61
+ *
62
+ * returns the provided node iff
63
+ * a.) the node is an exported interface
64
+ * b.) the exported name matches the provided name (or else there was no name provided)
65
+ *
66
+ * otherwise, returns null
67
+ */
68
+ protected exportedInterfaceOrNull(node: ts.Node, exportName?: string): ts.InterfaceDeclaration | null;
69
+ /**
70
+ * @internal
71
+ *
72
+ * returns the path to the dream.globals.ts file
73
+ */
74
+ protected psychicTypesPath(): string;
75
+ /**
76
+ * @internal
77
+ *
78
+ * safely runs prettier against the provided output. If prettier
79
+ * is not installed, then the original output is returned
80
+ */
81
+ protected prettier(output: string): Promise<string>;
82
+ /**
83
+ * @internal
84
+ *
85
+ * given a type node, it will send back the first found generic
86
+ * provided to that type.
87
+ */
88
+ protected getFirstGenericType(node: ts.Node): ts.TypeNode | null;
89
+ }
@@ -0,0 +1,28 @@
1
+ import ASTBuilder from './ASTBuilder.js';
2
+ /**
3
+ * Responsible for building dream globals, which can be found at
4
+ * types/dream.globals.ts.
5
+ *
6
+ * This class leverages internal AST building mechanisms built into
7
+ * typescript to manually build up object literals and interfaces
8
+ * for our app to consume.
9
+ */
10
+ export default class ASTPsychicTypesBuilder extends ASTBuilder {
11
+ build(): Promise<void>;
12
+ /**
13
+ * @internal
14
+ *
15
+ * builds up the `export const psychicTypes = ...` statement within the types/psychic.ts
16
+ * file. It does this by leveraging low-level AST utils built into typescript
17
+ * to manually build up an object literal, cast it as a const, and write it to
18
+ * an exported variable.
19
+ */
20
+ private buildPsychicTypes;
21
+ /**
22
+ * @internal
23
+ *
24
+ * writes the compiled statements to string.
25
+ *
26
+ */
27
+ private printStatements;
28
+ }
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": "2.0.4",
5
+ "version": "2.1.0",
6
6
  "author": "RVOHealth",
7
7
  "repository": {
8
8
  "type": "git",
@@ -81,7 +81,7 @@
81
81
  "@rvoh/dream": "^2.0.3",
82
82
  "@types/express": "^5.0.1",
83
83
  "commander": "^12.1.0",
84
- "express": "^5.1.0",
84
+ "express": "^5.2.1",
85
85
  "openapi-typescript": "^7.8.0"
86
86
  },
87
87
  "devDependencies": {
@@ -90,7 +90,7 @@
90
90
  "@rvoh/dream": "^2.0.3",
91
91
  "@rvoh/dream-spec-helpers": "^2.0.0",
92
92
  "@rvoh/psychic-spec-helpers": "^2.0.0",
93
- "@types/express": "^5.0.1",
93
+ "@types/express": "^5.0.6",
94
94
  "@types/express-session": "^1.18.2",
95
95
  "@types/node": "^22.17.1",
96
96
  "@types/passport": "^0",
@@ -99,7 +99,7 @@
99
99
  "@types/supertest": "^6.0.3",
100
100
  "@typescript/analyze-trace": "^0.10.1",
101
101
  "eslint": "^9.19.0",
102
- "express": "^5.1.0",
102
+ "express": "^5.2.1",
103
103
  "express-session": "^1.18.2",
104
104
  "jsdom": "^26.1.0",
105
105
  "kysely": "^0.28.5",
@@ -1,23 +0,0 @@
1
- import { DreamApp } from '@rvoh/dream';
2
- import { CliFileWriter } from '@rvoh/dream/system';
3
- import * as path from 'node:path';
4
- import PsychicApp from '../../psychic-app/index.js';
5
- export default class TypesBuilder {
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- static async sync(customTypes = undefined) {
8
- const dreamApp = DreamApp.getOrFail();
9
- const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
10
- await CliFileWriter.write(schemaPath, this.build(customTypes));
11
- }
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- static build(customTypes = undefined) {
14
- const psychicApp = PsychicApp.getOrFail();
15
- const output = {
16
- openapiNames: Object.keys(psychicApp.openapi),
17
- ...(customTypes || {}),
18
- };
19
- return `const psychicTypes = ${JSON.stringify(output, null, 2)} as const
20
-
21
- export default psychicTypes`;
22
- }
23
- }
@@ -1,23 +0,0 @@
1
- import { DreamApp } from '@rvoh/dream';
2
- import { CliFileWriter } from '@rvoh/dream/system';
3
- import * as path from 'node:path';
4
- import PsychicApp from '../../psychic-app/index.js';
5
- export default class TypesBuilder {
6
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
7
- static async sync(customTypes = undefined) {
8
- const dreamApp = DreamApp.getOrFail();
9
- const schemaPath = path.join(dreamApp.projectRoot, dreamApp.paths.types, 'psychic.ts');
10
- await CliFileWriter.write(schemaPath, this.build(customTypes));
11
- }
12
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
13
- static build(customTypes = undefined) {
14
- const psychicApp = PsychicApp.getOrFail();
15
- const output = {
16
- openapiNames: Object.keys(psychicApp.openapi),
17
- ...(customTypes || {}),
18
- };
19
- return `const psychicTypes = ${JSON.stringify(output, null, 2)} as const
20
-
21
- export default psychicTypes`;
22
- }
23
- }
@@ -1,7 +0,0 @@
1
- export default class TypesBuilder {
2
- static sync(customTypes?: any): Promise<void>;
3
- static build(customTypes?: any): string;
4
- }
5
- export interface PsychicTypeSync {
6
- openapiNames: string[];
7
- }