@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,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
+ }
@@ -0,0 +1,236 @@
1
+ import ts from 'typescript';
2
+ import { CliFileWriter } from '../../cli/CliFileWriter.js';
3
+ import camelize from '../camelize.js';
4
+ import ASTConnectionBuilder from './ASTConnectionBuilder.js';
5
+ import autogeneratedFileDisclaimer from './autoGeneratedFileDisclaimer.js';
6
+ const f = ts.factory;
7
+ /**
8
+ * Responsible for enhancing the kysely-codegen output, which can be found at
9
+ * types/db.ts. If you are leveraging multiple db connections,
10
+ * then this file will also be responsible for building all variants,
11
+ * i.e. types/db.alternateConnection.ts, etc...
12
+ *
13
+ * This class leverages internal AST building mechanisms built into
14
+ * typescript to manually build up object literals and interfaces
15
+ * for our app to consume.
16
+ */
17
+ export default class ASTKyselyCodegenEnhancer extends ASTConnectionBuilder {
18
+ /**
19
+ * enhances the kysely codegen output for the given connection name
20
+ * by reading the relevant db.ts file into AST nodes, then enhancing them
21
+ * with a variety of transformations necessary to lock this file in with
22
+ * our other schema files, allowing types to flow correctly throughout the app.
23
+ */
24
+ async enhance() {
25
+ let dbSourceFile = await this.getDbSourceFile();
26
+ dbSourceFile = this.camelizeKeys(dbSourceFile);
27
+ dbSourceFile = this.replaceTimestampExport(dbSourceFile);
28
+ dbSourceFile = this.addMissingImports(dbSourceFile);
29
+ dbSourceFile = this.replaceInt8Export(dbSourceFile);
30
+ dbSourceFile = this.sortExportedInterfacesTransformer(dbSourceFile);
31
+ dbSourceFile = await this.addEnumValueExports(dbSourceFile);
32
+ dbSourceFile = this.addDeprecatedDbClassExportForBackwardsCompatibility(dbSourceFile);
33
+ const output = this.printOutput(dbSourceFile);
34
+ const finalOutput = await this.prettier(`\
35
+ ${autogeneratedFileDisclaimer()}
36
+ ${output}`);
37
+ await CliFileWriter.write(this.dbPath(), finalOutput);
38
+ }
39
+ addDeprecatedDbClassExportForBackwardsCompatibility(dbSourceFile) {
40
+ const transformer = () => {
41
+ return sourceFile => {
42
+ const dbInterface = this.findDbExport(sourceFile, 'DB');
43
+ if (!dbInterface)
44
+ return sourceFile;
45
+ const classDec = ts.factory.createClassDeclaration([ts.factory.createModifier(ts.SyntaxKind.ExportKeyword)], ts.factory.createIdentifier('DBClass'), undefined, [], dbInterface.members.filter(ts.isPropertySignature).map(propertySignature => {
46
+ return f.createPropertyDeclaration([], propertySignature.name, undefined, propertySignature.type, undefined);
47
+ }));
48
+ return ts.factory.updateSourceFile(sourceFile, [...sourceFile.statements, classDec], sourceFile.isDeclarationFile, sourceFile.referencedFiles, sourceFile.typeReferenceDirectives, undefined);
49
+ };
50
+ };
51
+ const result = ts.transform(dbSourceFile, [transformer]);
52
+ return result.transformed[0];
53
+ }
54
+ /**
55
+ * @internal
56
+ *
57
+ * returns the output of the provided dbSourceFile, ensuring that all exports
58
+ * have a new line above them.
59
+ */
60
+ printOutput(dbSourceFile) {
61
+ const printer = ts.createPrinter({ newLine: ts.NewLineKind.LineFeed });
62
+ const output = printer.printNode(ts.EmitHint.SourceFile, dbSourceFile, dbSourceFile);
63
+ return output.replace(/export /g, '\nexport ');
64
+ }
65
+ /**
66
+ * @internal
67
+ *
68
+ * ensures that the `Timestamp` exported from db.ts points correctly to either
69
+ * the DateTime or CalendarDate class, depending on the db type.
70
+ */
71
+ replaceTimestampExport(dbSourceFile) {
72
+ const transformer = context => {
73
+ return rootNode => {
74
+ const visit = (node) => {
75
+ const typeAlias = this.exportedTypeAliasOrNull(node, 'Timestamp');
76
+ if (typeAlias) {
77
+ const updatedNode = f.updateTypeAliasDeclaration(typeAlias, typeAlias.modifiers, typeAlias.name, typeAlias.typeParameters, f.createTypeReferenceNode('ColumnType', [
78
+ f.createUnionTypeNode([
79
+ f.createTypeReferenceNode(f.createIdentifier('DateTime')),
80
+ f.createTypeReferenceNode(f.createIdentifier('CalendarDate')),
81
+ ]),
82
+ ]));
83
+ return updatedNode;
84
+ }
85
+ return ts.visitEachChild(node, visit, context);
86
+ };
87
+ return ts.visitNode(rootNode, visit);
88
+ };
89
+ };
90
+ const result = ts.transform(dbSourceFile, [transformer]);
91
+ return result.transformed[0];
92
+ }
93
+ /**
94
+ * @internal
95
+ *
96
+ * massages the `Int8` exported from db.ts to make it more flexible
97
+ */
98
+ replaceInt8Export(dbSourceFile) {
99
+ const transformer = context => {
100
+ return rootNode => {
101
+ const visit = (node) => {
102
+ const typeAlias = this.exportedTypeAliasOrNull(node, 'Int8');
103
+ if (typeAlias) {
104
+ return f.updateTypeAliasDeclaration(typeAlias, typeAlias.modifiers, typeAlias.name, typeAlias.typeParameters, f.createTypeReferenceNode('ColumnType', [
105
+ f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
106
+ f.createUnionTypeNode([
107
+ f.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword),
108
+ f.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
109
+ f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
110
+ ]),
111
+ f.createUnionTypeNode([
112
+ f.createKeywordTypeNode(ts.SyntaxKind.BigIntKeyword),
113
+ f.createKeywordTypeNode(ts.SyntaxKind.NumberKeyword),
114
+ f.createKeywordTypeNode(ts.SyntaxKind.StringKeyword),
115
+ ]),
116
+ ]));
117
+ }
118
+ return ts.visitEachChild(node, visit, context);
119
+ };
120
+ return ts.visitNode(rootNode, visit);
121
+ };
122
+ };
123
+ const result = ts.transform(dbSourceFile, [transformer]);
124
+ return result.transformed[0];
125
+ }
126
+ /**
127
+ * @internal
128
+ *
129
+ * injects missing CalendarDate and DateTime imports from dream
130
+ */
131
+ addMissingImports(dbSourceFile) {
132
+ const transformer = () => {
133
+ return (sourceFile) => f.updateSourceFile(sourceFile, [...this.dateAndDateTimeImports(), ...sourceFile.statements]);
134
+ };
135
+ const result = ts.transform(dbSourceFile, [transformer]);
136
+ return result.transformed[0];
137
+ }
138
+ /**
139
+ * @internal
140
+ *
141
+ * sorts all exported interfaces and puts them above the DB interface
142
+ */
143
+ sortExportedInterfacesTransformer(dbSourceFile) {
144
+ const transformer = () => {
145
+ return (sourceFile) => {
146
+ const sortedInterfaceStatements = [];
147
+ const finalInterfaceStatements = [];
148
+ const otherStatements = [];
149
+ sourceFile.statements.forEach(statement => {
150
+ if (ts.isInterfaceDeclaration(statement) &&
151
+ statement.modifiers?.some(m => m.kind === ts.SyntaxKind.ExportKeyword)) {
152
+ if (statement.name.text !== 'DB') {
153
+ sortedInterfaceStatements.push(statement);
154
+ }
155
+ else {
156
+ finalInterfaceStatements.push(statement);
157
+ }
158
+ }
159
+ else {
160
+ otherStatements.push(statement);
161
+ }
162
+ });
163
+ sortedInterfaceStatements.sort((a, b) => a.name.text.localeCompare(b.name.text));
164
+ return f.updateSourceFile(sourceFile, [...otherStatements, ...sortedInterfaceStatements, ...finalInterfaceStatements], sourceFile.isDeclarationFile, sourceFile.referencedFiles);
165
+ };
166
+ };
167
+ const result = ts.transform(dbSourceFile, [transformer]);
168
+ return result.transformed[0];
169
+ }
170
+ /**
171
+ * @internal
172
+ *
173
+ * camelizes the keys on all interfaces except the DB interface
174
+ * (since the DB interface is indexed by table name, which must not be camelized)
175
+ */
176
+ camelizeKeys(dbSourceFile) {
177
+ // @ts-expect-error cannot lock this type down, though implementation is correct
178
+ const transformer = context => {
179
+ const visit = node => {
180
+ const interfaceNode = this.exportedInterfaceOrNull(node);
181
+ if (interfaceNode &&
182
+ !['DB'].includes(interfaceNode.name.text) // Check exclusion list
183
+ ) {
184
+ const updatedMembers = interfaceNode.members.map(member => {
185
+ if (ts.isPropertySignature(member) && ts.isIdentifier(member.name)) {
186
+ const camelizedKey = camelize(member.name.text);
187
+ if (member.name.text !== camelizedKey) {
188
+ return f.updatePropertySignature(member, member.modifiers, f.createIdentifier(camelizedKey), member.questionToken, member.type);
189
+ }
190
+ }
191
+ return member;
192
+ });
193
+ return f.updateInterfaceDeclaration(interfaceNode, interfaceNode.modifiers, interfaceNode.name, interfaceNode.typeParameters, interfaceNode.heritageClauses, updatedMembers);
194
+ }
195
+ return ts.visitEachChild(node, visit, context);
196
+ };
197
+ return node => ts.visitNode(node, visit);
198
+ };
199
+ const result = ts.transform(dbSourceFile, [transformer]);
200
+ return result.transformed[0];
201
+ }
202
+ /**
203
+ * @internal
204
+ *
205
+ * for each found enum in the app (i.e. LocalizedTextsEnum), this will also
206
+ * inject below it a `LocalizedTextsEnumValues` const, which can be used
207
+ * at runtime to grab the actual enum values.
208
+ */
209
+ async addEnumValueExports(dbSourceFile) {
210
+ const enums = await this.getAllEnumValueNames();
211
+ const enumTypes = enums.map(e => e.enumType);
212
+ // @ts-expect-error cannot lock this type down, though implementation is correct
213
+ const transformer = context => {
214
+ const visit = (node) => {
215
+ const typeAlias = this.exportedTypeAliasOrNull(node);
216
+ const isEnum = typeAlias ? enumTypes.includes(typeAlias.name.text) : false;
217
+ if (typeAlias && isEnum) {
218
+ const literals = this.extractStringLiteralTypeNodesFromTypeOrUnion(typeAlias).map(node => node.literal.text);
219
+ if (literals.length > 0) {
220
+ return [
221
+ typeAlias,
222
+ f.createVariableStatement([f.createModifier(ts.SyntaxKind.ExportKeyword)], f.createVariableDeclarationList([
223
+ f.createVariableDeclaration(f.createIdentifier(`${typeAlias.name.text}Values`), undefined, undefined, f.createAsExpression(f.createArrayLiteralExpression(literals.map(literal => f.createStringLiteral(literal)), true // multiLine
224
+ ), f.createKeywordTypeNode(ts.SyntaxKind.ConstKeyword))),
225
+ ], ts.NodeFlags.Const)),
226
+ ];
227
+ }
228
+ }
229
+ return ts.visitEachChild(node, visit, context);
230
+ };
231
+ return node => ts.visitNode(node, visit);
232
+ };
233
+ const result = ts.transform(dbSourceFile, [transformer]);
234
+ return result.transformed[0];
235
+ }
236
+ }