@rvoh/dream 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.
Files changed (215) hide show
  1. package/dist/cjs/src/bin/index.js +2 -0
  2. package/dist/cjs/src/db/helpers/dreamSchemaTypesFilenameForConnection.js +3 -0
  3. package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +2 -89
  4. package/dist/cjs/src/dream/QueryDriver/Base.js +2 -2
  5. package/dist/cjs/src/dream/QueryDriver/Kysely.js +9 -19
  6. package/dist/cjs/src/dream/QueryDriver/Postgres.js +2 -2
  7. package/dist/cjs/src/dream-app/index.js +15 -0
  8. package/dist/cjs/src/helpers/cli/ASTBuilder.js +277 -0
  9. package/dist/cjs/src/helpers/cli/ASTConnectionBuilder.js +284 -0
  10. package/dist/cjs/src/helpers/cli/ASTGlobalSchemaBuilder.js +57 -0
  11. package/dist/cjs/src/helpers/cli/ASTKyselyCodegenEnhancer.js +236 -0
  12. package/dist/cjs/src/helpers/cli/ASTSchemaBuilder.js +304 -0
  13. package/dist/cjs/src/helpers/cli/DBClassDeprecation.js +60 -0
  14. package/dist/esm/src/bin/index.js +2 -0
  15. package/dist/esm/src/db/helpers/dreamSchemaTypesFilenameForConnection.js +3 -0
  16. package/dist/esm/src/db/helpers/syncDbTypesFiles.js +2 -89
  17. package/dist/esm/src/dream/QueryDriver/Base.js +2 -2
  18. package/dist/esm/src/dream/QueryDriver/Kysely.js +9 -19
  19. package/dist/esm/src/dream/QueryDriver/Postgres.js +2 -2
  20. package/dist/esm/src/dream-app/index.js +15 -0
  21. package/dist/esm/src/helpers/cli/ASTBuilder.js +277 -0
  22. package/dist/esm/src/helpers/cli/ASTConnectionBuilder.js +284 -0
  23. package/dist/esm/src/helpers/cli/ASTGlobalSchemaBuilder.js +57 -0
  24. package/dist/esm/src/helpers/cli/ASTKyselyCodegenEnhancer.js +236 -0
  25. package/dist/esm/src/helpers/cli/ASTSchemaBuilder.js +304 -0
  26. package/dist/esm/src/helpers/cli/DBClassDeprecation.js +60 -0
  27. package/dist/types/src/db/helpers/dreamSchemaTypesFilenameForConnection.d.ts +1 -0
  28. package/dist/types/src/dream/QueryDriver/Base.d.ts +3 -3
  29. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +2 -14
  30. package/dist/types/src/dream/QueryDriver/Postgres.d.ts +2 -2
  31. package/dist/types/src/dream-app/index.d.ts +12 -2
  32. package/dist/types/src/helpers/cli/ASTBuilder.d.ts +159 -0
  33. package/dist/types/src/helpers/cli/ASTConnectionBuilder.d.ts +104 -0
  34. package/dist/types/src/helpers/cli/ASTGlobalSchemaBuilder.d.ts +28 -0
  35. package/dist/types/src/helpers/cli/ASTKyselyCodegenEnhancer.d.ts +68 -0
  36. package/dist/types/src/helpers/cli/ASTSchemaBuilder.d.ts +80 -0
  37. package/dist/types/src/helpers/cli/DBClassDeprecation.d.ts +14 -0
  38. package/docs/assets/search.js +1 -1
  39. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  40. package/docs/classes/db.KyselyQueryDriver.html +31 -44
  41. package/docs/classes/db.PostgresQueryDriver.html +32 -45
  42. package/docs/classes/db.QueryDriverBase.html +30 -30
  43. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  44. package/docs/classes/errors.ColumnOverflow.html +3 -3
  45. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  46. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  47. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  48. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  49. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  50. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  51. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  52. package/docs/classes/errors.NotNullViolation.html +3 -3
  53. package/docs/classes/errors.RecordNotFound.html +3 -3
  54. package/docs/classes/errors.ValidationError.html +3 -3
  55. package/docs/classes/index.CalendarDate.html +2 -2
  56. package/docs/classes/index.Decorators.html +19 -19
  57. package/docs/classes/index.Dream.html +113 -113
  58. package/docs/classes/index.DreamApp.html +7 -6
  59. package/docs/classes/index.DreamTransaction.html +2 -2
  60. package/docs/classes/index.Env.html +2 -2
  61. package/docs/classes/index.Query.html +53 -53
  62. package/docs/classes/system.CliFileWriter.html +2 -2
  63. package/docs/classes/system.DreamBin.html +2 -2
  64. package/docs/classes/system.DreamCLI.html +5 -5
  65. package/docs/classes/system.DreamImporter.html +2 -2
  66. package/docs/classes/system.DreamLogos.html +2 -2
  67. package/docs/classes/system.DreamSerializerBuilder.html +8 -8
  68. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  69. package/docs/classes/utils.Encrypt.html +2 -2
  70. package/docs/classes/utils.Range.html +2 -2
  71. package/docs/functions/db.closeAllDbConnections.html +1 -1
  72. package/docs/functions/db.dreamDbConnections.html +1 -1
  73. package/docs/functions/db.untypedDb.html +1 -1
  74. package/docs/functions/db.validateColumn.html +1 -1
  75. package/docs/functions/db.validateTable.html +1 -1
  76. package/docs/functions/errors.pgErrorType.html +1 -1
  77. package/docs/functions/index.DreamSerializer.html +1 -1
  78. package/docs/functions/index.ObjectSerializer.html +1 -1
  79. package/docs/functions/index.ReplicaSafe.html +1 -1
  80. package/docs/functions/index.STI.html +1 -1
  81. package/docs/functions/index.SoftDelete.html +1 -1
  82. package/docs/functions/utils.camelize.html +1 -1
  83. package/docs/functions/utils.capitalize.html +1 -1
  84. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  85. package/docs/functions/utils.compact.html +1 -1
  86. package/docs/functions/utils.groupBy.html +1 -1
  87. package/docs/functions/utils.hyphenize.html +1 -1
  88. package/docs/functions/utils.intersection.html +1 -1
  89. package/docs/functions/utils.isEmpty.html +1 -1
  90. package/docs/functions/utils.normalizeUnicode.html +1 -1
  91. package/docs/functions/utils.pascalize.html +1 -1
  92. package/docs/functions/utils.percent.html +1 -1
  93. package/docs/functions/utils.range-1.html +1 -1
  94. package/docs/functions/utils.round.html +1 -1
  95. package/docs/functions/utils.sanitizeString.html +1 -1
  96. package/docs/functions/utils.snakeify.html +1 -1
  97. package/docs/functions/utils.sort.html +1 -1
  98. package/docs/functions/utils.sortBy.html +1 -1
  99. package/docs/functions/utils.sortObjectByKey.html +1 -1
  100. package/docs/functions/utils.sortObjectByValue.html +1 -1
  101. package/docs/functions/utils.uncapitalize.html +1 -1
  102. package/docs/functions/utils.uniq.html +1 -1
  103. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  104. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  105. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  106. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  107. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  108. package/docs/interfaces/types.DecoratorContext.html +2 -2
  109. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  110. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  111. package/docs/interfaces/types.EncryptOptions.html +2 -2
  112. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  113. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  114. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  115. package/docs/modules/db.html +1 -1
  116. package/docs/modules/errors.html +1 -1
  117. package/docs/modules/index.html +1 -1
  118. package/docs/modules/openapi.html +1 -1
  119. package/docs/modules/system.html +1 -1
  120. package/docs/modules/types.html +1 -1
  121. package/docs/modules/utils.html +1 -1
  122. package/docs/types/index.DateTime.html +1 -1
  123. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  124. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  125. package/docs/types/openapi.OpenapiFormats.html +1 -1
  126. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  127. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  128. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  130. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  131. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  132. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  133. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  134. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  135. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  136. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  137. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  138. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +1 -1
  139. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaNull.html +1 -1
  142. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  145. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  146. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  147. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  148. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  150. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  161. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  162. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  163. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  164. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  165. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  166. package/docs/types/types.Camelized.html +1 -1
  167. package/docs/types/types.DbConnectionType.html +1 -1
  168. package/docs/types/types.DbTypes.html +1 -1
  169. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  170. package/docs/types/types.DreamAttributes.html +1 -1
  171. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  172. package/docs/types/types.DreamClassColumn.html +1 -1
  173. package/docs/types/types.DreamColumn.html +1 -1
  174. package/docs/types/types.DreamColumnNames.html +1 -1
  175. package/docs/types/types.DreamLogLevel.html +1 -1
  176. package/docs/types/types.DreamLogger.html +1 -1
  177. package/docs/types/types.DreamModelSerializerType.html +1 -1
  178. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  179. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  180. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  181. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  182. package/docs/types/types.DreamSerializable.html +1 -1
  183. package/docs/types/types.DreamSerializableArray.html +1 -1
  184. package/docs/types/types.DreamSerializerKey.html +1 -1
  185. package/docs/types/types.DreamSerializers.html +1 -1
  186. package/docs/types/types.DreamVirtualColumns.html +1 -1
  187. package/docs/types/types.EncryptAlgorithm.html +1 -1
  188. package/docs/types/types.HasManyStatement.html +1 -1
  189. package/docs/types/types.HasOneStatement.html +1 -1
  190. package/docs/types/types.Hyphenized.html +1 -1
  191. package/docs/types/types.Pascalized.html +1 -1
  192. package/docs/types/types.RoundingPrecision.html +1 -1
  193. package/docs/types/types.SerializerCasing.html +1 -1
  194. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  195. package/docs/types/types.Snakeified.html +1 -1
  196. package/docs/types/types.StrictInterface.html +1 -1
  197. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  198. package/docs/types/types.UpdateableProperties.html +1 -1
  199. package/docs/types/types.ValidationType.html +1 -1
  200. package/docs/types/types.ViewModel.html +1 -1
  201. package/docs/types/types.ViewModelClass.html +1 -1
  202. package/docs/types/types.WhereStatementForDream.html +1 -1
  203. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  204. package/docs/variables/index.DateTime-1.html +1 -1
  205. package/docs/variables/index.DreamConst.html +1 -1
  206. package/docs/variables/index.ops.html +1 -1
  207. package/docs/variables/openapi.openapiPrimitiveTypes-1.html +1 -1
  208. package/docs/variables/openapi.openapiShorthandPrimitiveTypes-1.html +1 -1
  209. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  210. package/docs/variables/types.TRIGRAM_OPERATORS.html +1 -1
  211. package/docs/variables/types.primaryKeyTypes.html +1 -1
  212. package/package.json +1 -1
  213. package/dist/cjs/src/helpers/cli/SchemaBuilder.js +0 -408
  214. package/dist/esm/src/helpers/cli/SchemaBuilder.js +0 -408
  215. package/dist/types/src/helpers/cli/SchemaBuilder.d.ts +0 -44
@@ -0,0 +1,277 @@
1
+ import * as path from 'node:path';
2
+ import ts from 'typescript';
3
+ import DreamApp from '../../dream-app/index.js';
4
+ import EnvInternal from '../EnvInternal.js';
5
+ const f = ts.factory;
6
+ /**
7
+ * @internal
8
+ *
9
+ * This is a base class, which is inherited by the ASTSchemaBuilder,
10
+ * the ASTKyselyCodegenEnhancer, and the ASTGlobalSchemaBuilder,
11
+ * each of which is responsible for building up the output of the various
12
+ * type files consumed by dream internally.
13
+ *
14
+ * This base class is just a container for common methods used by all
15
+ * classes.
16
+ */
17
+ export default class ASTBuilder {
18
+ /**
19
+ * @internal
20
+ *
21
+ * builds a new line, useful for injecting new lines into AST statements
22
+ */
23
+ newLine() {
24
+ return f.createIdentifier('\n');
25
+ }
26
+ /**
27
+ * @internal
28
+ *
29
+ * given an interface declaration, it will extrace the relevant property statement
30
+ * by the given property name.
31
+ */
32
+ getPropertyFromInterface(interfaceNode, propertyName) {
33
+ for (const member of interfaceNode.members) {
34
+ if (ts.isPropertySignature(member)) {
35
+ if (ts.isIdentifier(member.name) && member.name.text === propertyName) {
36
+ return member;
37
+ }
38
+ }
39
+ }
40
+ return null;
41
+ }
42
+ /**
43
+ * @internal
44
+ *
45
+ * returns an array of string type literals which were extracted from
46
+ * either a type or type union, depending on what is provided
47
+ * for the typeAlias. this allows you to safely and easily collect
48
+ * an array of types given an alias
49
+ */
50
+ extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias) {
51
+ const literals = [];
52
+ if (ts.isUnionTypeNode(typeAlias.type)) {
53
+ typeAlias.type.types.forEach(typeNode => {
54
+ if (ts.isLiteralTypeNode(typeNode) && ts.isStringLiteral(typeNode.literal)) {
55
+ literals.push(typeNode);
56
+ }
57
+ });
58
+ }
59
+ else if (ts.isLiteralTypeNode(typeAlias.type) && ts.isStringLiteral(typeAlias.type.literal)) {
60
+ literals.push(typeAlias.type);
61
+ }
62
+ return literals;
63
+ }
64
+ /**
65
+ * @internal
66
+ *
67
+ * returns an array of type literals which were extracted from
68
+ * either a type or type union, depending on what is provided
69
+ * for the typeAlias. this allows you to safely and easily collect
70
+ * an array of types given an alias
71
+ */
72
+ extractTypeNodesFromTypeOrUnion(typeAlias) {
73
+ const literals = [];
74
+ if (typeAlias.type && ts.isUnionTypeNode(typeAlias.type)) {
75
+ typeAlias.type.types.forEach(typeNode => {
76
+ literals.push(typeNode);
77
+ });
78
+ }
79
+ else if (typeAlias.type) {
80
+ literals.push(typeAlias.type);
81
+ }
82
+ return literals;
83
+ }
84
+ /**
85
+ * @internal
86
+ *
87
+ * returns the provided node iff
88
+ * a.) the node is an exported type alias
89
+ * b.) the exported name matches the provided name (or else there was no name provided)
90
+ *
91
+ * otherwise, returns null
92
+ */
93
+ exportedTypeAliasOrNull(node, exportName) {
94
+ if (ts.isTypeAliasDeclaration(node) &&
95
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
96
+ (!exportName ? true : node.name.text === exportName))
97
+ return node;
98
+ return null;
99
+ }
100
+ /**
101
+ * @internal
102
+ *
103
+ * returns the provided node iff
104
+ * a.) the node is an exported interface
105
+ * b.) the exported name matches the provided name (or else there was no name provided)
106
+ *
107
+ * otherwise, returns null
108
+ */
109
+ exportedInterfaceOrNull(node, exportName) {
110
+ if (ts.isInterfaceDeclaration(node) &&
111
+ node?.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword) &&
112
+ (!exportName ? true : node.name.text === exportName))
113
+ return node;
114
+ return null;
115
+ }
116
+ /**
117
+ * @internal
118
+ *
119
+ * extracts the exportName from the provided dbSourceFile
120
+ */
121
+ findDbExport(dbSourceFile, exportName) {
122
+ let foundNode;
123
+ ts.forEachChild(dbSourceFile, node => {
124
+ const hasModifiers = ts.isFunctionDeclaration(node) ||
125
+ ts.isClassDeclaration(node) ||
126
+ ts.isInterfaceDeclaration(node) ||
127
+ ts.isVariableStatement(node) ||
128
+ ts.isTypeAliasDeclaration(node);
129
+ if (hasModifiers) {
130
+ const isExported = node.modifiers?.some(modifier => modifier.kind === ts.SyntaxKind.ExportKeyword);
131
+ if (isExported && ts.isVariableStatement(node)) {
132
+ const declarations = node.declarationList.declarations;
133
+ const name = declarations?.[0]?.name;
134
+ if (declarations.length > 0 && ts.isIdentifier(name) && name?.text === exportName) {
135
+ foundNode = node;
136
+ return true; // Stop traversal
137
+ }
138
+ }
139
+ const declarationWithName = node;
140
+ if (isExported &&
141
+ declarationWithName.name &&
142
+ ts.isIdentifier(declarationWithName.name) &&
143
+ declarationWithName.name.text === exportName) {
144
+ foundNode = node;
145
+ return true; // Stop traversal
146
+ }
147
+ }
148
+ });
149
+ return foundNode;
150
+ }
151
+ /**
152
+ * @internal
153
+ *
154
+ * returns the path to the dream.globals.ts file
155
+ */
156
+ globalSchemaPath() {
157
+ const dreamApp = DreamApp.getOrFail();
158
+ return path.join(dreamApp.projectRoot, dreamApp.paths.types, 'dream.globals.ts');
159
+ }
160
+ /**
161
+ * @internal
162
+ *
163
+ * safely runs prettier against the provided output. If prettier
164
+ * is not installed, then the original output is returned
165
+ */
166
+ async prettier(output) {
167
+ try {
168
+ // dynamically, safely bring in prettier.
169
+ // ini the event that it fails, we will return the
170
+ // original output, unformatted, since prettier
171
+ // is technically not a real dependency of dream,
172
+ // though psychic and dream apps are provisioned
173
+ // with prettier by default, so this should usually work
174
+ const prettier = (await import('prettier')).default;
175
+ const results = await prettier.format(output, {
176
+ parser: 'typescript',
177
+ semi: false,
178
+ singleQuote: true,
179
+ tabWidth: 2,
180
+ lineWidth: 80,
181
+ });
182
+ return typeof results === 'string' ? results : output;
183
+ }
184
+ catch {
185
+ // intentional noop, we don't want to raise if prettier
186
+ // fails, since it is possible for the end user to not
187
+ // want to use prettier, and it is not a required peer
188
+ // dependency of dream
189
+ return output;
190
+ }
191
+ }
192
+ /**
193
+ * @internal
194
+ *
195
+ * given a type node, it will send back the first found generic
196
+ * provided to that type.
197
+ */
198
+ getFirstGenericType(node) {
199
+ if (ts.isTypeReferenceNode(node)) {
200
+ if (node.typeArguments && node.typeArguments.length > 0) {
201
+ return node.typeArguments[0];
202
+ }
203
+ }
204
+ else if (ts.isCallExpression(node)) {
205
+ if (node.typeArguments && node.typeArguments.length > 0) {
206
+ return node.typeArguments[0];
207
+ }
208
+ }
209
+ return null;
210
+ }
211
+ /**
212
+ * @internal
213
+ *
214
+ * returns the DateTime and CalendarDate imports. This is fairly
215
+ * tricky, since it considers whether or not we are in the dream
216
+ * internals (i.e. when testing dream). If we are, it will return
217
+ * valid internal import paths to those files. Otherwise, it will
218
+ * import them both from @rvoh/dream.
219
+ */
220
+ dateAndDateTimeImports() {
221
+ if (EnvInternal.boolean('DREAM_CORE_DEVELOPMENT')) {
222
+ const calendarImport = ts.factory.createImportClause(true, f.createIdentifier('CalendarDate'), undefined);
223
+ const calendarImportDeclaration = ts.factory.createImportDeclaration(undefined, calendarImport, ts.factory.createStringLiteral('../../src/helpers/CalendarDate.js'));
224
+ const dateTimeNamedImports = ts.factory.createNamedImports([
225
+ f.createImportSpecifier(true, undefined, ts.factory.createIdentifier('DateTime')),
226
+ ]);
227
+ const dateTimeImportClause = ts.factory.createImportClause(false, // isTypeOnly: false for the clause itself if not all imports are type only
228
+ undefined, // name: undefined for default import
229
+ dateTimeNamedImports // namedBindings
230
+ );
231
+ const dateTimeImportDeclaration = ts.factory.createImportDeclaration(undefined, dateTimeImportClause, ts.factory.createStringLiteral('../../src/helpers/DateTime.js'));
232
+ return [calendarImportDeclaration, dateTimeImportDeclaration];
233
+ }
234
+ else {
235
+ const namedImports = ts.factory.createNamedImports(['CalendarDate', 'DateTime'].map(importName => f.createImportSpecifier(true, undefined, ts.factory.createIdentifier(importName))));
236
+ const importClause = ts.factory.createImportClause(false, // isTypeOnly: false for the clause itself if not all imports are type only
237
+ undefined, // name: undefined for default import
238
+ namedImports // namedBindings
239
+ );
240
+ const importDeclaration = ts.factory.createImportDeclaration(undefined, // modifiers: e.g., 'export' or 'declare'
241
+ importClause, ts.factory.createStringLiteral('@rvoh/dream'));
242
+ return [importDeclaration];
243
+ }
244
+ }
245
+ /**
246
+ * @internal
247
+ *
248
+ * for a given table name (i.e. balloon_lines), it will return the exported
249
+ * `BalloonLines` interface within the dbSourceFile
250
+ */
251
+ getTableInterfaceDeclaration(dbSourceFile, tableName) {
252
+ const DB = this.findDbExport(dbSourceFile, 'DB');
253
+ let targetProperty = null;
254
+ for (const member of DB.members) {
255
+ if (ts.isPropertySignature(member) && member.name.getText(dbSourceFile) === tableName) {
256
+ targetProperty = member;
257
+ break;
258
+ }
259
+ }
260
+ const tableInterfaceName = (targetProperty?.type)
261
+ .typeName?.escapedText;
262
+ if (!tableInterfaceName)
263
+ throw new Error(`failed to find table interface for table: ${tableName}`);
264
+ const tableInterface = this.findDbExport(dbSourceFile, tableInterfaceName);
265
+ return tableInterface;
266
+ }
267
+ /**
268
+ * @internal
269
+ *
270
+ * returns an array of global names for all serializers in the app
271
+ */
272
+ globalSerializerNames() {
273
+ const dreamApp = DreamApp.getOrFail();
274
+ const serializers = dreamApp.serializers;
275
+ return Object.keys(serializers);
276
+ }
277
+ }
@@ -0,0 +1,284 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import * as path from 'node:path';
3
+ import ts from 'typescript';
4
+ import dbTypesFilenameForConnection from '../../db/helpers/dbTypesFilenameForConnection.js';
5
+ import dreamSchemaTypesFilenameForConnection from '../../db/helpers/dreamSchemaTypesFilenameForConnection.js';
6
+ import DreamApp from '../../dream-app/index.js';
7
+ import Query from '../../dream/Query.js';
8
+ import { ExplicitForeignKeyRequired, InvalidComputedForeignKey, } from '../../errors/associations/InvalidComputedForeignKey.js';
9
+ import FailedToIdentifyAssociation from '../../errors/schema-builder/FailedToIdentifyAssociation.js';
10
+ import intersection from '../intersection.js';
11
+ import sortBy from '../sortBy.js';
12
+ import uniq from '../uniq.js';
13
+ import ASTBuilder from './ASTBuilder.js';
14
+ /**
15
+ * @internal
16
+ *
17
+ * This is a base class, which is inherited by the ASTSchemaBuilder and
18
+ * the ASTKyselyCodegenEnhancer, both of which is responsible for building
19
+ * up the output of the various type files consumed by dream internally.
20
+ *
21
+ * This base class is just a container for common methods used by both
22
+ * classes. It requires a connectionName to be provided, unlike the underlying
23
+ * ASTBuilder class, and provides methods which leverage the connectionName
24
+ *
25
+ */
26
+ export default class ASTConnectionBuilder extends ASTBuilder {
27
+ connectionName;
28
+ hasForeignKeyError = false;
29
+ constructor(connectionName) {
30
+ super();
31
+ this.connectionName = connectionName;
32
+ }
33
+ /**
34
+ * @internal
35
+ *
36
+ * returns the path from project root to the dream.ts file
37
+ * for the particular connection. If the connectionName is anything
38
+ * other than default, the path will represent that by injecting
39
+ * the connectionName into the file name, i.e. dream.alternate.ts
40
+ */
41
+ schemaPath() {
42
+ const dreamApp = DreamApp.getOrFail();
43
+ return path.join(dreamApp.projectRoot, dreamApp.paths.types, dreamSchemaTypesFilenameForConnection(this.connectionName));
44
+ }
45
+ /**
46
+ * @internal
47
+ *
48
+ * returns the path from project root to the db.ts file
49
+ * for the particular connection. If the connectionName is anything
50
+ * other than default, the path will represent that by injecting
51
+ * the connectionName into the file name, i.e. db.alternate.ts
52
+ */
53
+ dbPath() {
54
+ const dreamApp = DreamApp.getOrFail();
55
+ return path.join(dreamApp.projectRoot, dreamApp.paths.types, dbTypesFilenameForConnection(this.connectionName));
56
+ }
57
+ /**
58
+ * @internal
59
+ *
60
+ * returns the db source file for the given connectionName, injecting
61
+ * the source file with the actual file contents, so that AST nodes
62
+ * can be built through ingesting.
63
+ */
64
+ async getDbSourceFile() {
65
+ const fileContent = await this.loadDbSyncFile();
66
+ return ts.createSourceFile('./db.js', fileContent, ts.ScriptTarget.Latest, true);
67
+ }
68
+ /**
69
+ * @internal
70
+ *
71
+ * reads the db source file for the given connection, returning the contents
72
+ * as a raw string
73
+ */
74
+ async loadDbSyncFile() {
75
+ return (await fs.readFile(this.dbPath())).toString();
76
+ }
77
+ /**
78
+ * @internal
79
+ *
80
+ * builds up the schema data for every table into an object, which
81
+ * can be read and injected into AST nodes.
82
+ */
83
+ async getSchemaData() {
84
+ const tables = await this.getTables();
85
+ const schemaData = {};
86
+ for (const table of tables) {
87
+ schemaData[table] = await this.tableData(table);
88
+ }
89
+ return schemaData;
90
+ }
91
+ /**
92
+ * @internal
93
+ *
94
+ * used by getSchemaData to build up all table data
95
+ */
96
+ async getTables() {
97
+ const fileContents = await this.loadDbSyncFile();
98
+ const tableLines = /export interface DB {([^}]*)}/.exec(fileContents)[1];
99
+ if (tableLines === undefined)
100
+ return [];
101
+ const tables = tableLines
102
+ .split('\n')
103
+ .map(line => {
104
+ const stingArray = line.split(':');
105
+ const substring = stingArray[0];
106
+ if (substring === undefined)
107
+ return '';
108
+ return substring.replace(/\s*/, '');
109
+ })
110
+ .filter(line => !!line);
111
+ return tables;
112
+ }
113
+ /**
114
+ * @internal
115
+ *
116
+ * finds all enums used by the app, and returns information
117
+ * about those enums that can be used for type generating purpposes
118
+ */
119
+ async getAllEnumValueNames() {
120
+ const schemaData = await this.getSchemaData();
121
+ const enumValueNames = Object.values(schemaData)
122
+ .map(tableData => Object.keys(tableData.columns)
123
+ .filter(columnName => !!tableData.columns[columnName]?.enumValues)
124
+ .map(columnName => ({
125
+ enumValues: tableData.columns[columnName].enumValues,
126
+ enumType: tableData.columns[columnName].enumType,
127
+ })))
128
+ .flat();
129
+ return enumValueNames;
130
+ }
131
+ /**
132
+ * @internal
133
+ *
134
+ * returns a tuple, where the first value is the global name, and the second value
135
+ * is the table that that global name points to. Used to build up our global
136
+ * model name exports within type files.
137
+ */
138
+ globalModelNames() {
139
+ const dreamApp = DreamApp.getOrFail();
140
+ const models = dreamApp.models;
141
+ return Object.keys(models)
142
+ .filter(key => models[key]?.prototype?.connectionName === this.connectionName)
143
+ .map(key => [key, models[key].prototype.table]);
144
+ }
145
+ /**
146
+ * @internal
147
+ *
148
+ * retrieves useful association data for a given association and table, which
149
+ * can be used to build up types
150
+ */
151
+ getAssociationData(tableName, targetAssociationType) {
152
+ const dreamApp = DreamApp.getOrFail();
153
+ const models = sortBy(Object.values(dreamApp.models), m => m.table);
154
+ const tableAssociationData = {};
155
+ for (const model of models.filter(model => model.table === tableName)) {
156
+ for (const associationName of model.associationNames) {
157
+ const associationMetaData = model['associationMetadataMap']()[associationName];
158
+ if (associationMetaData === undefined)
159
+ continue;
160
+ if (targetAssociationType && associationMetaData.type !== targetAssociationType)
161
+ continue;
162
+ const dreamClassOrClasses = associationMetaData.modelCB();
163
+ if (!dreamClassOrClasses)
164
+ throw new FailedToIdentifyAssociation(model, associationMetaData.type, associationName, associationMetaData.globalAssociationNameOrNames);
165
+ const optional = associationMetaData.type === 'BelongsTo' ? associationMetaData.optional === true : null;
166
+ const where = associationMetaData.type === 'HasMany' || associationMetaData.type === 'HasOne'
167
+ ? associationMetaData.and || null
168
+ : null;
169
+ // NOTE
170
+ // this try-catch is here because the ASTSchemaBuilder currently needs to be run twice to generate foreignKey
171
+ // correctly. The first time will raise, since calling Dream.columns is dependant on the schema const to
172
+ // introspect columns during a foreign key check. This will be repaired once kysely types have been successfully
173
+ // split off into a separate file from the types we diliver in types/dream.ts
174
+ let foreignKey = null;
175
+ try {
176
+ const isThroughAssociation = associationMetaData.through;
177
+ if (!isThroughAssociation) {
178
+ const _foreignKey = associationMetaData.foreignKey();
179
+ foreignKey = _foreignKey;
180
+ }
181
+ }
182
+ catch (err) {
183
+ this.hasForeignKeyError = true;
184
+ }
185
+ try {
186
+ tableAssociationData[associationName] ||= {
187
+ tables: [],
188
+ type: associationMetaData.type,
189
+ polymorphic: associationMetaData.polymorphic,
190
+ foreignKey,
191
+ foreignKeyTypeColumn: associationMetaData.polymorphic
192
+ ? associationMetaData?.foreignKeyTypeField?.() || null
193
+ : null,
194
+ optional,
195
+ and: where,
196
+ };
197
+ if (foreignKey)
198
+ tableAssociationData[associationName]['foreignKey'] = foreignKey;
199
+ if (Array.isArray(dreamClassOrClasses)) {
200
+ const tables = dreamClassOrClasses.map(dreamClass => dreamClass.table);
201
+ tableAssociationData[associationName].tables = [
202
+ ...tableAssociationData[associationName].tables,
203
+ ...tables,
204
+ ];
205
+ }
206
+ else {
207
+ tableAssociationData[associationName].tables.push(dreamClassOrClasses.table);
208
+ }
209
+ // guarantee unique
210
+ tableAssociationData[associationName].tables = [
211
+ ...new Set(tableAssociationData[associationName].tables),
212
+ ];
213
+ }
214
+ catch (error) {
215
+ if (!(error instanceof ExplicitForeignKeyRequired || error instanceof InvalidComputedForeignKey))
216
+ throw error;
217
+ }
218
+ }
219
+ }
220
+ return Object.keys(tableAssociationData)
221
+ .sort()
222
+ .reduce((acc, key) => {
223
+ if (tableAssociationData[key] === undefined)
224
+ return acc;
225
+ acc[key] = tableAssociationData[key];
226
+ return acc;
227
+ }, {});
228
+ }
229
+ /**
230
+ * @internal
231
+ *
232
+ * retrieves the table data for an individual table.
233
+ * Can be used to build up types
234
+ */
235
+ async tableData(tableName) {
236
+ const dreamApp = DreamApp.getOrFail();
237
+ const models = Object.values(dreamApp.models).filter(model => model.table === tableName);
238
+ const maybeModel = models[0];
239
+ if (!maybeModel)
240
+ throw new Error(`
241
+ Could not find a Dream model with table "${tableName}".
242
+
243
+ If you recently changed the name of a table in a migration, you
244
+ may need to update the table getter in the corresponding Dream.
245
+ `);
246
+ const baseModel = maybeModel['stiBaseClassOrOwnClass'];
247
+ const associationData = this.getAssociationData(tableName);
248
+ const allStiChildren = models.filter(model => model['isSTIChild']);
249
+ const modelsToCheck = allStiChildren.length ? allStiChildren : [baseModel];
250
+ // If a table is STI, then we look only at the serializers attached to
251
+ // all STI children (not the STI base model because the base model may not have any serializers)
252
+ const eachModelSerializerKeys = modelsToCheck.map(model => {
253
+ let serializers = {};
254
+ try {
255
+ serializers = model?.prototype?.['serializers'] || {};
256
+ }
257
+ catch {
258
+ // no-op
259
+ }
260
+ return Object.keys(serializers);
261
+ });
262
+ const serializerKeys = intersection(...eachModelSerializerKeys).sort();
263
+ return {
264
+ scopes: {
265
+ default: uniq(models.flatMap(model => model['scopes'].default.map(scopeStatement => scopeStatement.method))),
266
+ named: uniq(models.flatMap(model => model['scopes'].named.map(scopeStatement => scopeStatement.method))),
267
+ },
268
+ columns: await this.getColumnData(tableName, associationData),
269
+ virtualColumns: uniq(models.flatMap(model => model['virtualAttributes'].map(prop => prop.property) || [])),
270
+ associations: associationData,
271
+ serializerKeys,
272
+ };
273
+ }
274
+ /**
275
+ * @internal
276
+ *
277
+ * retrieves the column data for an individual table and association.
278
+ * Can be used to build up types
279
+ */
280
+ async getColumnData(tableName, allTableAssociationData) {
281
+ const dbDriverClass = Query.dbDriverClass(this.connectionName);
282
+ return await dbDriverClass.getColumnData(this.connectionName, tableName, allTableAssociationData);
283
+ }
284
+ }
@@ -0,0 +1,57 @@
1
+ import ts from 'typescript';
2
+ import { CliFileWriter } from '../../cli/CliFileWriter.js';
3
+ import ASTBuilder from './ASTBuilder.js';
4
+ import autogeneratedFileDisclaimer from './autoGeneratedFileDisclaimer.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 ASTGlobalSchemaBuilder extends ASTBuilder {
15
+ async build() {
16
+ // build a new, blank source file to populate with our output
17
+ const sourceFile = ts.createSourceFile('', '', ts.ScriptTarget.Latest, false, ts.ScriptKind.TS);
18
+ const output = await this.prettier(this.printStatements([this.buildGlobalTypeConfigConst()], sourceFile));
19
+ await CliFileWriter.write(this.globalSchemaPath(), output);
20
+ }
21
+ /**
22
+ * @internal
23
+ *
24
+ * builds up the `export const globalTypeConfig = ...` statement within the dream.globals.ts
25
+ * file. It does this by leveraging low-level AST utils built into typescript
26
+ * to manually build up an object literal, cast it as a const, and write it to
27
+ * an exported variable.
28
+ */
29
+ buildGlobalTypeConfigConst() {
30
+ const globalTypeConfigObjectLiteral = f.createObjectLiteralExpression([
31
+ f.createPropertyAssignment(f.createIdentifier('serializers'), f.createArrayLiteralExpression(this.globalSerializerNames()
32
+ .sort()
33
+ .map(key => f.createStringLiteral(key)))),
34
+ ], true // multiline
35
+ );
36
+ // add "as const" to the end of the schema object we
37
+ // have built before returning it
38
+ const constAssertion = f.createAsExpression(globalTypeConfigObjectLiteral, f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword));
39
+ const globalTypeConfigObjectLiteralConst = f.createVariableStatement([f.createModifier(ts.SyntaxKind.ExportKeyword)], f.createVariableDeclarationList([
40
+ f.createVariableDeclaration(f.createIdentifier('globalTypeConfig'), undefined, undefined, constAssertion),
41
+ ], ts.NodeFlags.Const));
42
+ return globalTypeConfigObjectLiteralConst;
43
+ }
44
+ /**
45
+ * @internal
46
+ *
47
+ * writes the compiled statements to string.
48
+ *
49
+ */
50
+ printStatements(statements, sourceFile) {
51
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed, omitTrailingSemicolon: true });
52
+ const result = printer.printList(ts.ListFormat.SourceFileStatements, f.createNodeArray(statements), sourceFile);
53
+ return `\
54
+ ${autogeneratedFileDisclaimer()}
55
+ ${result}`;
56
+ }
57
+ }