@rvoh/dream 2.3.0 → 2.3.2

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 (231) hide show
  1. package/dist/cjs/src/bin/index.js +9 -2
  2. package/dist/cjs/src/cli/index.js +11 -2
  3. package/dist/cjs/src/dream/Query.js +9 -1
  4. package/dist/cjs/src/dream/QueryDriver/Kysely.js +11 -10
  5. package/dist/cjs/src/dream-app/index.js +2 -0
  6. package/dist/cjs/src/helpers/cli/generateDream.js +16 -21
  7. package/dist/cjs/src/helpers/cli/generateDreamContent.js +2 -2
  8. package/dist/cjs/src/helpers/cli/generateFactory.js +8 -22
  9. package/dist/cjs/src/helpers/cli/generateFactoryContent.js +1 -2
  10. package/dist/cjs/src/helpers/cli/generateMigration.js +18 -26
  11. package/dist/cjs/src/helpers/cli/generateMigrationContent.js +27 -2
  12. package/dist/cjs/src/helpers/cli/generateSerializer.js +10 -23
  13. package/dist/cjs/src/helpers/cli/generateSerializerContent.js +5 -7
  14. package/dist/cjs/src/helpers/cli/generateUnitSpec.js +7 -21
  15. package/dist/cjs/src/helpers/cli/modelClassNameFrom.js +15 -0
  16. package/dist/cjs/src/helpers/cli/writeGeneratedFile.js +26 -0
  17. package/dist/esm/src/bin/index.js +9 -2
  18. package/dist/esm/src/cli/index.js +11 -2
  19. package/dist/esm/src/dream/Query.js +9 -1
  20. package/dist/esm/src/dream/QueryDriver/Kysely.js +11 -10
  21. package/dist/esm/src/dream-app/index.js +2 -0
  22. package/dist/esm/src/helpers/cli/generateDream.js +16 -21
  23. package/dist/esm/src/helpers/cli/generateDreamContent.js +2 -2
  24. package/dist/esm/src/helpers/cli/generateFactory.js +8 -22
  25. package/dist/esm/src/helpers/cli/generateFactoryContent.js +1 -2
  26. package/dist/esm/src/helpers/cli/generateMigration.js +18 -26
  27. package/dist/esm/src/helpers/cli/generateMigrationContent.js +27 -2
  28. package/dist/esm/src/helpers/cli/generateSerializer.js +10 -23
  29. package/dist/esm/src/helpers/cli/generateSerializerContent.js +5 -7
  30. package/dist/esm/src/helpers/cli/generateUnitSpec.js +7 -21
  31. package/dist/esm/src/helpers/cli/modelClassNameFrom.js +15 -0
  32. package/dist/esm/src/helpers/cli/writeGeneratedFile.js +26 -0
  33. package/dist/types/src/bin/index.d.ts +5 -0
  34. package/dist/types/src/cli/index.d.ts +11 -0
  35. package/dist/types/src/dream-app/index.d.ts +2 -0
  36. package/dist/types/src/helpers/cli/generateDream.d.ts +9 -6
  37. package/dist/types/src/helpers/cli/generateDreamContent.d.ts +3 -0
  38. package/dist/types/src/helpers/cli/generateFactory.d.ts +2 -1
  39. package/dist/types/src/helpers/cli/generateFactoryContent.d.ts +3 -1
  40. package/dist/types/src/helpers/cli/generateMigration.d.ts +8 -1
  41. package/dist/types/src/helpers/cli/generateSerializer.d.ts +2 -1
  42. package/dist/types/src/helpers/cli/generateSerializerContent.d.ts +3 -1
  43. package/dist/types/src/helpers/cli/modelClassNameFrom.d.ts +9 -0
  44. package/dist/types/src/helpers/cli/writeGeneratedFile.d.ts +18 -0
  45. package/dist/types/src/helpers/compact.d.ts +2 -2
  46. package/docs/assets/search.js +1 -1
  47. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  48. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  49. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  50. package/docs/classes/db.QueryDriverBase.html +31 -31
  51. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  52. package/docs/classes/errors.ColumnOverflow.html +3 -3
  53. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  54. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  55. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  56. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  57. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  58. package/docs/classes/errors.InvalidClockTime.html +2 -2
  59. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  60. package/docs/classes/errors.InvalidDateTime.html +2 -2
  61. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  62. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  63. package/docs/classes/errors.NotNullViolation.html +3 -3
  64. package/docs/classes/errors.RecordNotFound.html +3 -3
  65. package/docs/classes/errors.ValidationError.html +3 -3
  66. package/docs/classes/index.CalendarDate.html +33 -33
  67. package/docs/classes/index.ClockTime.html +32 -32
  68. package/docs/classes/index.ClockTimeTz.html +35 -35
  69. package/docs/classes/index.DateTime.html +71 -71
  70. package/docs/classes/index.Decorators.html +19 -19
  71. package/docs/classes/index.Dream.html +116 -116
  72. package/docs/classes/index.DreamApp.html +10 -5
  73. package/docs/classes/index.DreamTransaction.html +2 -2
  74. package/docs/classes/index.Env.html +2 -2
  75. package/docs/classes/index.Query.html +56 -56
  76. package/docs/classes/system.CliFileWriter.html +2 -2
  77. package/docs/classes/system.DreamBin.html +2 -2
  78. package/docs/classes/system.DreamCLI.html +9 -5
  79. package/docs/classes/system.DreamImporter.html +2 -2
  80. package/docs/classes/system.DreamLogos.html +2 -2
  81. package/docs/classes/system.DreamSerializerBuilder.html +8 -8
  82. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  83. package/docs/classes/system.PathHelpers.html +3 -3
  84. package/docs/classes/utils.Encrypt.html +2 -2
  85. package/docs/classes/utils.Range.html +2 -2
  86. package/docs/functions/db.closeAllDbConnections.html +1 -1
  87. package/docs/functions/db.dreamDbConnections.html +1 -1
  88. package/docs/functions/db.untypedDb.html +1 -1
  89. package/docs/functions/db.validateColumn.html +1 -1
  90. package/docs/functions/db.validateTable.html +1 -1
  91. package/docs/functions/errors.pgErrorType.html +1 -1
  92. package/docs/functions/index.DreamSerializer.html +1 -1
  93. package/docs/functions/index.ObjectSerializer.html +1 -1
  94. package/docs/functions/index.ReplicaSafe.html +1 -1
  95. package/docs/functions/index.STI.html +1 -1
  96. package/docs/functions/index.SoftDelete.html +1 -1
  97. package/docs/functions/utils.camelize.html +1 -1
  98. package/docs/functions/utils.capitalize.html +1 -1
  99. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  100. package/docs/functions/utils.compact.html +2 -2
  101. package/docs/functions/utils.groupBy.html +1 -1
  102. package/docs/functions/utils.hyphenize.html +1 -1
  103. package/docs/functions/utils.intersection.html +1 -1
  104. package/docs/functions/utils.isEmpty.html +1 -1
  105. package/docs/functions/utils.normalizeUnicode.html +1 -1
  106. package/docs/functions/utils.pascalize.html +1 -1
  107. package/docs/functions/utils.percent.html +1 -1
  108. package/docs/functions/utils.range-1.html +1 -1
  109. package/docs/functions/utils.round.html +1 -1
  110. package/docs/functions/utils.sanitizeString.html +1 -1
  111. package/docs/functions/utils.snakeify.html +1 -1
  112. package/docs/functions/utils.sort.html +1 -1
  113. package/docs/functions/utils.sortBy.html +1 -1
  114. package/docs/functions/utils.sortObjectByKey.html +1 -1
  115. package/docs/functions/utils.sortObjectByValue.html +1 -1
  116. package/docs/functions/utils.uncapitalize.html +1 -1
  117. package/docs/functions/utils.uniq.html +1 -1
  118. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  119. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  120. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  121. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  122. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  123. package/docs/interfaces/types.DecoratorContext.html +2 -2
  124. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  125. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  126. package/docs/interfaces/types.DurationObject.html +2 -2
  127. package/docs/interfaces/types.EncryptOptions.html +2 -2
  128. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  129. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  130. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  131. package/docs/modules/db.html +1 -1
  132. package/docs/modules/errors.html +1 -1
  133. package/docs/modules/index.html +1 -1
  134. package/docs/modules/openapi.html +1 -1
  135. package/docs/modules/system.html +1 -1
  136. package/docs/modules/types.html +1 -1
  137. package/docs/modules/utils.html +1 -1
  138. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  139. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  140. package/docs/types/openapi.OpenapiFormats.html +1 -1
  141. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  142. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  143. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  145. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  146. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  147. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  148. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  149. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  150. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +1 -1
  151. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaNull.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  164. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  165. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  166. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  167. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  168. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  169. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +1 -1
  170. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
  171. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +1 -1
  172. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
  173. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
  174. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  175. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  176. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  177. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  178. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  179. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  180. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  181. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  182. package/docs/types/types.CalendarDateObject.html +1 -1
  183. package/docs/types/types.Camelized.html +1 -1
  184. package/docs/types/types.ClockTimeObject.html +1 -1
  185. package/docs/types/types.DbConnectionType.html +1 -1
  186. package/docs/types/types.DbTypes.html +1 -1
  187. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  188. package/docs/types/types.DreamAttributes.html +1 -1
  189. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  190. package/docs/types/types.DreamClassColumn.html +1 -1
  191. package/docs/types/types.DreamColumn.html +1 -1
  192. package/docs/types/types.DreamColumnNames.html +1 -1
  193. package/docs/types/types.DreamLogLevel.html +1 -1
  194. package/docs/types/types.DreamLogger.html +1 -1
  195. package/docs/types/types.DreamModelSerializerType.html +1 -1
  196. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  197. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  198. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  199. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  200. package/docs/types/types.DreamSerializable.html +1 -1
  201. package/docs/types/types.DreamSerializableArray.html +1 -1
  202. package/docs/types/types.DreamSerializerKey.html +1 -1
  203. package/docs/types/types.DreamSerializers.html +1 -1
  204. package/docs/types/types.DreamVirtualColumns.html +1 -1
  205. package/docs/types/types.DurationUnit.html +1 -1
  206. package/docs/types/types.EncryptAlgorithm.html +1 -1
  207. package/docs/types/types.HasManyStatement.html +1 -1
  208. package/docs/types/types.HasOneStatement.html +1 -1
  209. package/docs/types/types.Hyphenized.html +1 -1
  210. package/docs/types/types.Pascalized.html +1 -1
  211. package/docs/types/types.PrimaryKeyType.html +1 -1
  212. package/docs/types/types.RoundingPrecision.html +1 -1
  213. package/docs/types/types.SerializerCasing.html +1 -1
  214. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  215. package/docs/types/types.Snakeified.html +1 -1
  216. package/docs/types/types.StrictInterface.html +1 -1
  217. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  218. package/docs/types/types.UpdateableProperties.html +1 -1
  219. package/docs/types/types.ValidationType.html +1 -1
  220. package/docs/types/types.ViewModel.html +1 -1
  221. package/docs/types/types.ViewModelClass.html +1 -1
  222. package/docs/types/types.WeekdayName.html +1 -1
  223. package/docs/types/types.WhereStatementForDream.html +1 -1
  224. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  225. package/docs/variables/index.DreamConst.html +1 -1
  226. package/docs/variables/index.ops.html +1 -1
  227. package/docs/variables/openapi.openapiPrimitiveTypes-1.html +1 -1
  228. package/docs/variables/openapi.openapiShorthandPrimitiveTypes-1.html +1 -1
  229. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  230. package/docs/variables/system.primaryKeyTypes.html +1 -1
  231. package/package.json +4 -2
@@ -6,8 +6,9 @@ import EnvInternal from '../helpers/EnvInternal.js';
6
6
  import loadRepl from '../helpers/loadRepl.js';
7
7
  import sspawn from '../helpers/sspawn.js';
8
8
  import DreamCliLogger from './logger/DreamCliLogger.js';
9
- const INDENT = ' ';
10
- const baseColumnsWithTypesDescription = `space separated snake-case (except for belongs_to model name) properties like this:
9
+ export const CLI_INDENT = ' ';
10
+ const INDENT = CLI_INDENT;
11
+ export const baseColumnsWithTypesDescription = `space separated snake-case (except for belongs_to model name) properties like this:
11
12
  ${INDENT} title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal User:belongs_to
12
13
  ${INDENT}
13
14
  ${INDENT}all properties default to not nullable; null can be allowed by appending ':optional':
@@ -99,6 +100,9 @@ export default class DreamCLI {
99
100
  }
100
101
  /**
101
102
  * @internal
103
+ *
104
+ * Called by Psychic (and other consumers) to programmatically generate a
105
+ * Dream model with all associated files.
102
106
  */
103
107
  static async generateDream(opts) {
104
108
  await generateDream(opts);
@@ -128,6 +132,9 @@ export default class DreamCLI {
128
132
  .option('--no-serializer')
129
133
  .option('--connection-name <connectionName>', 'the db connection you want this attached to (defaults to the default db connection)', 'default')
130
134
  .option('--sti-base-serializer')
135
+ .option('--table-name <tableName>', 'explicit table name to use instead of the auto-generated one (useful when model namespaces produce long names)')
136
+ .option('--model-name <modelName>', 'explicit model class name to use instead of the auto-generated one (e.g. --model-name=Kitchen for Room/Kitchen)')
137
+ .option('--admin-serializers', 'generate admin serializer variants (AdminSerializer and AdminSummarySerializer) in addition to the default serializers')
131
138
  .argument('<modelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
132
139
  .argument('[columnsWithTypes...]', columnsWithTypesDescription)
133
140
  .action(async (modelName, columnsWithTypes, options) => {
@@ -141,6 +148,8 @@ export default class DreamCLI {
141
148
  .description('Generates a Dream model that extends another Dream model, leveraging STI (single table inheritance), with corresponding spec factory, serializer, and migration.')
142
149
  .option('--no-serializer')
143
150
  .option('--connection-name', 'the db connection you want this model attached to (defaults to the default connection)', 'default')
151
+ .option('--model-name <modelName>', 'explicit model class name to use instead of the auto-generated one (e.g. --model-name=Kitchen for Room/Kitchen)')
152
+ .option('--admin-serializers', 'generate admin serializer variants (AdminSerializer and AdminSummarySerializer) in addition to the default serializers')
144
153
  .argument('<childModelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
145
154
  .argument('<extends>', 'just the word "extends"')
146
155
  .argument('<parentModelName>', `fully qualified name of the parent model, e.g.:
@@ -20,6 +20,8 @@ import ops from '../ops/index.js';
20
20
  import computedPaginatePage from './internal/computedPaginatePage.js';
21
21
  import convertDreamClassAndAssociationNameTupleArrayToPreloadArgs from './internal/convertDreamClassAndAssociationNameTupleArrayToPreloadArgs.js';
22
22
  import extractNestedPaths from './internal/extractNestedPaths.js';
23
+ // Cache for extractNestedPaths results, keyed by "${sanitizedName}:${serializerKey}"
24
+ const extractedNestedPathsCache = new Map();
23
25
  export default class Query {
24
26
  /**
25
27
  * @internal
@@ -524,7 +526,13 @@ export default class Query {
524
526
  * @returns A Query with all serialization associations preloaded
525
527
  */
526
528
  preloadFor(serializerKey, modifierFn) {
527
- const preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
529
+ const cacheKey = `${this.dreamClass.sanitizedName}:${serializerKey ?? 'default'}`;
530
+ let preloadArgs = extractedNestedPathsCache.get(cacheKey);
531
+ if (!preloadArgs) {
532
+ // Cache miss - compute and store
533
+ preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
534
+ extractedNestedPathsCache.set(cacheKey, preloadArgs);
535
+ }
528
536
  let query = this;
529
537
  preloadArgs.forEach(dreamClassAndAssociationNameTupleArray => {
530
538
  query = query.preload(...convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn));
@@ -33,7 +33,6 @@ import ColumnOverflow from '../../errors/db/ColumnOverflow.js';
33
33
  import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
34
34
  import NotNullViolation from '../../errors/db/NotNullViolation.js';
35
35
  import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
36
- import camelize from '../../helpers/camelize.js';
37
36
  import ASTGlobalSchemaBuilder from '../../helpers/cli/ASTGlobalSchemaBuilder.js';
38
37
  import ASTSchemaBuilder from '../../helpers/cli/ASTSchemaBuilder.js';
39
38
  import generateMigration from '../../helpers/cli/generateMigration.js';
@@ -533,18 +532,20 @@ export default class KyselyQueryDriver extends QueryDriverBase {
533
532
  let kyselyQuery = new this.constructor(this.query).buildSelect({
534
533
  bypassSelectAll: true,
535
534
  });
536
- const aliases = [];
537
- fields.forEach((field) => {
538
- // field will already be namespaced in a join situation, but when the field to pluck is on the
539
- // base model, it will be underscored (to match the table name), but when the selected column
540
- // comes back from Kysely camelCased
541
- aliases.push(field.includes('_') ? camelize(field) : field);
535
+ // Use short aliases to avoid PostgreSQL's 63-byte identifier truncation.
536
+ // Previously, the full namespaced field (e.g. "longAlias.columnName") was used as the
537
+ // AS alias, which Kysely's CamelCasePlugin converts to a single snake_case identifier
538
+ // that can exceed 63 bytes. Short aliases like "pluck0" avoid this entirely.
539
+ const shortAliases = [];
540
+ fields.forEach((field, index) => {
541
+ const shortAlias = `pluck${index}`;
542
+ shortAliases.push(shortAlias);
542
543
  // namespace the selection so that when plucking the same column name from
543
- // multpile tables, they don't get saved as the same name (e.g. select results with two `id` columns,
544
+ // multiple tables, they don't get saved as the same name (e.g. select results with two `id` columns,
544
545
  // which the pg package then returns in an object with a single `id` key)
545
- kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(field)} as ${field}`);
546
+ kyselyQuery = kyselyQuery.select(`${this.namespaceColumn(field)} as ${shortAlias}`);
546
547
  });
547
- return (await executeDatabaseQuery(kyselyQuery, 'execute')).map(singleResult => aliases.map(alias => singleResult[alias]));
548
+ return (await executeDatabaseQuery(kyselyQuery, 'execute')).map(singleResult => shortAliases.map(alias => singleResult[alias]));
548
549
  }
549
550
  /**
550
551
  * Returns a new Kysely SelectQueryBuilder instance to be used
@@ -7,6 +7,7 @@ import DreamAppInitMissingCallToLoadModels from '../errors/dream-app/DreamAppIni
7
7
  import DreamAppInitMissingMissingProjectRoot from '../errors/dream-app/DreamAppInitMissingMissingProjectRoot.js';
8
8
  import DreamAppInitMissingPackageManager from '../errors/dream-app/DreamAppInitMissingPackageManager.js';
9
9
  import autogeneratedFileDisclaimer from '../helpers/cli/autoGeneratedFileDisclaimer.js';
10
+ import modelClassNameFrom from '../helpers/cli/modelClassNameFrom.js';
10
11
  import EnvInternal from '../helpers/EnvInternal.js';
11
12
  import globalClassNameFromFullyQualifiedModelName from '../helpers/globalClassNameFromFullyQualifiedModelName.js';
12
13
  import absoluteDreamPath from '../helpers/path/absoluteDreamPath.js';
@@ -87,6 +88,7 @@ export default class DreamApp {
87
88
  static get system() {
88
89
  return {
89
90
  globalClassNameFromFullyQualifiedModelName,
91
+ modelClassNameFrom,
90
92
  absoluteDreamPath,
91
93
  dreamPath,
92
94
  standardizeFullyQualifiedModelName,
@@ -1,39 +1,31 @@
1
- import * as fs from 'node:fs/promises';
2
- import DreamCLI from '../../cli/index.js';
3
- import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
4
- import dreamPath from '../path/dreamPath.js';
5
1
  import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
6
2
  import generateDreamContent from './generateDreamContent.js';
7
3
  import generateFactory from './generateFactory.js';
8
4
  import generateMigration from './generateMigration.js';
9
5
  import generateSerializer from './generateSerializer.js';
10
6
  import generateUnitSpec from './generateUnitSpec.js';
7
+ import modelClassNameFrom from './modelClassNameFrom.js';
8
+ import writeGeneratedFile from './writeGeneratedFile.js';
11
9
  export default async function generateDream({ fullyQualifiedModelName, columnsWithTypes, options, fullyQualifiedParentName, }) {
12
10
  fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
13
- const { relFilePath, absDirPath, absFilePath } = dreamFileAndDirPaths(dreamPath('models'), `${fullyQualifiedModelName}.ts`);
14
- try {
15
- DreamCLI.logger.log(`[dream] generating dream: ${relFilePath}`);
16
- await fs.mkdir(absDirPath, { recursive: true });
17
- await fs.writeFile(absFilePath, generateDreamContent({
11
+ const modelClassName = modelClassNameFrom(fullyQualifiedModelName, options.modelName);
12
+ await writeGeneratedFile({
13
+ dreamPathKey: 'models',
14
+ fileName: `${fullyQualifiedModelName}.ts`,
15
+ content: generateDreamContent({
18
16
  fullyQualifiedModelName,
19
17
  columnsWithTypes,
20
18
  fullyQualifiedParentName,
21
19
  serializer: options.serializer,
22
20
  includeAdminSerializers: options.includeAdminSerializers,
23
21
  connectionName: options.connectionName,
24
- }));
25
- }
26
- catch (error) {
27
- throw new Error(`
28
- Something happened while trying to create the Dream file:
29
- ${relFilePath}
30
-
31
- Does this file already exist? Here is the error that was raised:
32
- ${error.message}
33
- `);
34
- }
22
+ tableName: options.tableName,
23
+ modelClassName,
24
+ }),
25
+ logLabel: 'dream',
26
+ });
35
27
  await generateUnitSpec({ fullyQualifiedModelName });
36
- await generateFactory({ fullyQualifiedModelName, columnsWithTypes });
28
+ await generateFactory({ fullyQualifiedModelName, columnsWithTypes, modelClassName });
37
29
  if (options.serializer)
38
30
  await generateSerializer({
39
31
  fullyQualifiedModelName,
@@ -41,6 +33,7 @@ export default async function generateDream({ fullyQualifiedModelName, columnsWi
41
33
  fullyQualifiedParentName,
42
34
  stiBaseSerializer: options.stiBaseSerializer,
43
35
  includeAdminSerializers: options.includeAdminSerializers,
36
+ modelClassName,
44
37
  });
45
38
  const isSTI = !!fullyQualifiedParentName;
46
39
  if (columnsWithTypes.length || !isSTI) {
@@ -50,6 +43,8 @@ export default async function generateDream({ fullyQualifiedModelName, columnsWi
50
43
  columnsWithTypes,
51
44
  fullyQualifiedModelName,
52
45
  fullyQualifiedParentName,
46
+ tableName: options.tableName,
47
+ modelClassName,
53
48
  });
54
49
  }
55
50
  }
@@ -31,7 +31,7 @@ ${tableMethod}${serializersMethod}${fieldsSection}
31
31
  }
32
32
  export function createModelConfig(options) {
33
33
  const fullyQualifiedModelName = standardizeFullyQualifiedModelName(options.fullyQualifiedModelName);
34
- const modelClassName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedModelName);
34
+ const modelClassName = options.modelClassName;
35
35
  const isSTI = !!options.fullyQualifiedParentName;
36
36
  let parentModelClassName;
37
37
  if (isSTI) {
@@ -40,7 +40,7 @@ export function createModelConfig(options) {
40
40
  }
41
41
  const connectionName = options.connectionName || 'default';
42
42
  const applicationModelName = connectionName === 'default' ? 'ApplicationModel' : `${pascalize(connectionName)}ApplicationModel`;
43
- const tableName = snakeify(pluralize(fullyQualifiedModelName.replace(/\//g, '_')));
43
+ const tableName = options.tableName || snakeify(pluralize(fullyQualifiedModelName.replace(/\//g, '_')));
44
44
  return {
45
45
  fullyQualifiedModelName,
46
46
  modelClassName,
@@ -1,24 +1,10 @@
1
- import * as fs from 'node:fs/promises';
2
- import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
3
- import dreamPath from '../path/dreamPath.js';
4
- import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
5
1
  import generateFactoryContent from './generateFactoryContent.js';
6
- import DreamCLI from '../../cli/index.js';
7
- export default async function generateFactory({ fullyQualifiedModelName, columnsWithTypes, }) {
8
- fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
9
- const { relFilePath, absDirPath, absFilePath } = dreamFileAndDirPaths(dreamPath('factories'), `${fullyQualifiedModelName}Factory.ts`);
10
- try {
11
- DreamCLI.logger.log(`[dream] generating factory: ${relFilePath}`);
12
- await fs.mkdir(absDirPath, { recursive: true });
13
- await fs.writeFile(absFilePath, generateFactoryContent({ fullyQualifiedModelName, columnsWithTypes }));
14
- }
15
- catch (error) {
16
- throw new Error(`
17
- Something happened while trying to create the spec file:
18
- ${relFilePath}
19
-
20
- Does this file already exist? Here is the error that was raised:
21
- ${error.message}
22
- `);
23
- }
2
+ import writeGeneratedFile from './writeGeneratedFile.js';
3
+ export default async function generateFactory({ fullyQualifiedModelName, columnsWithTypes, modelClassName, }) {
4
+ await writeGeneratedFile({
5
+ dreamPathKey: 'factories',
6
+ fileName: `${fullyQualifiedModelName}Factory.ts`,
7
+ content: generateFactoryContent({ fullyQualifiedModelName, columnsWithTypes, modelClassName }),
8
+ logLabel: 'factory',
9
+ });
24
10
  }
@@ -4,7 +4,7 @@ import absoluteDreamPath from '../path/absoluteDreamPath.js';
4
4
  import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
5
5
  import uniq from '../uniq.js';
6
6
  import { optionalFromDescriptors } from './generateMigrationContent.js';
7
- export default function generateFactoryContent({ fullyQualifiedModelName, columnsWithTypes, }) {
7
+ export default function generateFactoryContent({ fullyQualifiedModelName, columnsWithTypes, modelClassName, }) {
8
8
  fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
9
9
  const dreamTypeImports = ['UpdateableProperties'];
10
10
  const dreamImports = [];
@@ -129,7 +129,6 @@ export default function generateFactoryContent({ fullyQualifiedModelName, column
129
129
  }
130
130
  }
131
131
  const relativePath = absoluteDreamPath('models', fullyQualifiedModelName);
132
- const modelClassName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedModelName);
133
132
  return `\
134
133
  ${nodeImports.length ? `import { ${uniq(nodeImports).join(', ')} } from 'node:crypto'\n` : ''}${dreamImports.length ? `import { ${uniq(dreamImports).join(', ')} } from '@rvoh/dream'\n` : ''}import { ${uniq(dreamTypeImports).join(', ')} } from '@rvoh/dream/types'
135
134
  import ${modelClassName} from '${relativePath}'${additionalImports.length ? '\n' + uniq(additionalImports).join('\n') : ''}
@@ -1,4 +1,3 @@
1
- import * as fs from 'node:fs/promises';
2
1
  import * as path from 'node:path';
3
2
  import pluralize from 'pluralize-esm';
4
3
  import generateMigrationContent from '../cli/generateMigrationContent.js';
@@ -6,52 +5,45 @@ import primaryKeyType from '../db/primaryKeyType.js';
6
5
  import hyphenize from '../hyphenize.js';
7
6
  import migrationVersion from '../migrationVersion.js';
8
7
  import pascalizePath from '../pascalizePath.js';
9
- import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
10
8
  import dreamPath from '../path/dreamPath.js';
11
9
  import snakeify from '../snakeify.js';
12
10
  import generateStiMigrationContent from './generateStiMigrationContent.js';
13
- import DreamCLI from '../../cli/index.js';
14
- export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, }) {
15
- const { relFilePath, absFilePath } = connectionName === 'default'
16
- ? dreamFileAndDirPaths(path.join(dreamPath('db'), 'migrations'), `${migrationVersion()}-${hyphenize(migrationName).replace(/\//g, '-')}.ts`)
17
- : dreamFileAndDirPaths(path.join(dreamPath('db'), 'migrations', connectionName), `${migrationVersion()}-${hyphenize(migrationName).replace(/\//g, '-')}.ts`);
11
+ import writeGeneratedFile from './writeGeneratedFile.js';
12
+ export default async function generateMigration({ migrationName, columnsWithTypes, connectionName, fullyQualifiedModelName, fullyQualifiedParentName, tableName: explicitTableName, modelClassName, }) {
13
+ const migrationsBasePath = connectionName === 'default'
14
+ ? path.join(dreamPath('db'), 'migrations')
15
+ : path.join(dreamPath('db'), 'migrations', connectionName);
16
+ const fileName = `${migrationVersion()}-${hyphenize(migrationName).replace(/\//g, '-')}.ts`;
18
17
  const isSTI = !!fullyQualifiedParentName;
19
- let finalContent = '';
18
+ let content = '';
20
19
  if (isSTI) {
21
- finalContent = generateStiMigrationContent({
20
+ content = generateStiMigrationContent({
22
21
  table: snakeify(pluralize(pascalizePath(fullyQualifiedParentName))),
23
22
  columnsWithTypes,
24
23
  primaryKeyType: primaryKeyType(connectionName),
25
- stiChildClassName: pascalizePath(fullyQualifiedModelName),
24
+ stiChildClassName: modelClassName || pascalizePath(fullyQualifiedModelName),
26
25
  });
27
26
  }
28
27
  else if (fullyQualifiedModelName) {
29
- finalContent = generateMigrationContent({
30
- table: snakeify(pluralize(pascalizePath(fullyQualifiedModelName))),
28
+ content = generateMigrationContent({
29
+ table: explicitTableName || snakeify(pluralize(pascalizePath(fullyQualifiedModelName))),
31
30
  columnsWithTypes,
32
31
  primaryKeyType: primaryKeyType(connectionName),
33
32
  });
34
33
  }
35
34
  else {
36
35
  const tableName = migrationName.match(/-to-(.+)$/)?.[1];
37
- finalContent = generateMigrationContent({
36
+ content = generateMigrationContent({
38
37
  table: tableName ? pluralize(snakeify(tableName)) : '<table-name>',
39
38
  columnsWithTypes,
40
39
  primaryKeyType: primaryKeyType(connectionName),
41
40
  createOrAlter: 'alter',
42
41
  });
43
42
  }
44
- try {
45
- DreamCLI.logger.log(`[dream] generating migration: ${relFilePath}`);
46
- await fs.writeFile(absFilePath, finalContent);
47
- }
48
- catch (error) {
49
- throw new Error(`
50
- Something happened while trying to create the migration file:
51
- ${relFilePath}
52
-
53
- Does this file already exist? Here is the error that was raised:
54
- ${error.message}
55
- `);
56
- }
43
+ await writeGeneratedFile({
44
+ basePath: migrationsBasePath,
45
+ fileName,
46
+ content,
47
+ logLabel: 'migration',
48
+ });
57
49
  }
@@ -1,9 +1,12 @@
1
1
  import pluralize from 'pluralize-esm';
2
+ import lookupModelByGlobalName from '../../dream-app/helpers/lookupModelByGlobalName.js';
2
3
  import Query from '../../dream/Query.js';
3
4
  import InvalidDecimalFieldPassedToGenerator from '../../errors/InvalidDecimalFieldPassedToGenerator.js';
4
5
  import camelize from '../camelize.js';
5
6
  import compact from '../compact.js';
7
+ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFullyQualifiedModelName.js';
6
8
  import snakeify from '../snakeify.js';
9
+ import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
7
10
  const STI_TYPE_COLUMN_NAME = 'type';
8
11
  const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
9
12
  export default function generateMigrationContent({ connectionName = 'default', table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, } = {}) {
@@ -43,6 +46,7 @@ export default function generateMigrationContent({ connectionName = 'default', t
43
46
  columnDefs.push(generateBelongsToStr(connectionName, attributeName, {
44
47
  primaryKeyType,
45
48
  omitInlineNonNull,
49
+ originalAssociationName: nonStandardAttributeName,
46
50
  }));
47
51
  attributeName = snakeify(nonStandardAttributeName.split('/').pop());
48
52
  attributeName = associationNameToForeignKey(attributeName);
@@ -279,10 +283,10 @@ function attributeTypeString(attributeType) {
279
283
  }
280
284
  }
281
285
  }
282
- function generateBelongsToStr(connectionName, associationName, { primaryKeyType, omitInlineNonNull: optional = false, }) {
286
+ function generateBelongsToStr(connectionName, associationName, { primaryKeyType, omitInlineNonNull: optional = false, originalAssociationName, }) {
283
287
  const dbDriverClass = Query.dbDriverClass(connectionName);
284
288
  const dataType = dbDriverClass.foreignKeyTypeFromPrimaryKey(primaryKeyType);
285
- const references = pluralize(associationName.replace(/\//g, '_').replace(/_id$/, ''));
289
+ const references = lookupReferencesTable(associationName, originalAssociationName);
286
290
  return `.addColumn('${associationNameToForeignKey(associationName.split('/').pop())}', '${dataType}', col => col.references('${references}.id').onDelete('restrict')${optional ? '' : '.notNull()'})`;
287
291
  }
288
292
  function generateIdStr({ primaryKeyType }) {
@@ -312,6 +316,27 @@ function generateIdStr({ primaryKeyType }) {
312
316
  return `.addColumn('id', '${primaryKeyType}', col => col.primaryKey())`;
313
317
  }
314
318
  }
319
+ /**
320
+ * Determines the referenced table name for a belongs_to association.
321
+ * First tries to look up the actual model to get its table name (handles
322
+ * custom table name overrides). Falls back to deriving the table name
323
+ * from the association string if the model isn't found.
324
+ */
325
+ function lookupReferencesTable(snakeAssociationName, originalAssociationName) {
326
+ if (originalAssociationName) {
327
+ try {
328
+ const globalName = globalClassNameFromFullyQualifiedModelName(standardizeFullyQualifiedModelName(originalAssociationName));
329
+ const model = lookupModelByGlobalName(globalName);
330
+ if (model?.table) {
331
+ return model.table;
332
+ }
333
+ }
334
+ catch {
335
+ // Model not found or DreamApp not initialized — fall through to string derivation
336
+ }
337
+ }
338
+ return pluralize(snakeAssociationName.replace(/\//g, '_').replace(/_id$/, ''));
339
+ }
315
340
  function associationNameToForeignKey(associationName) {
316
341
  return snakeify(associationName.replace(/\//g, '_').replace(/_id$/, '') + '_id');
317
342
  }
@@ -1,30 +1,17 @@
1
- import * as fs from 'node:fs/promises';
2
- import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
3
- import dreamPath from '../path/dreamPath.js';
4
- import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
5
1
  import generateSerializerContent from './generateSerializerContent.js';
6
- import DreamCLI from '../../cli/index.js';
7
- export default async function generateSerializer({ fullyQualifiedModelName, columnsWithTypes, fullyQualifiedParentName, stiBaseSerializer, includeAdminSerializers, }) {
8
- fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
9
- const { relFilePath, absDirPath, absFilePath } = dreamFileAndDirPaths(dreamPath('serializers'), `${fullyQualifiedModelName}Serializer.ts`);
10
- try {
11
- DreamCLI.logger.log(`[dream] generating serializer: ${relFilePath}`);
12
- await fs.mkdir(absDirPath, { recursive: true });
13
- await fs.writeFile(absFilePath, generateSerializerContent({
2
+ import writeGeneratedFile from './writeGeneratedFile.js';
3
+ export default async function generateSerializer({ fullyQualifiedModelName, columnsWithTypes, fullyQualifiedParentName, stiBaseSerializer, includeAdminSerializers, modelClassName, }) {
4
+ await writeGeneratedFile({
5
+ dreamPathKey: 'serializers',
6
+ fileName: `${fullyQualifiedModelName}Serializer.ts`,
7
+ content: generateSerializerContent({
14
8
  fullyQualifiedModelName,
15
9
  columnsWithTypes,
16
10
  fullyQualifiedParentName,
17
11
  stiBaseSerializer,
18
12
  includeAdminSerializers,
19
- }));
20
- }
21
- catch (error) {
22
- throw new Error(`
23
- Something happened while trying to create the serializer file:
24
- ${relFilePath}
25
-
26
- Does this file already exist? Here is the error that was raised:
27
- ${error.message}
28
- `);
29
- }
13
+ modelClassName,
14
+ }),
15
+ logLabel: 'serializer',
16
+ });
30
17
  }
@@ -4,7 +4,7 @@ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFu
4
4
  import absoluteDreamPath from '../path/absoluteDreamPath.js';
5
5
  import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
6
6
  import uniq from '../uniq.js';
7
- export default function generateSerializerContent({ fullyQualifiedModelName, columnsWithTypes = [], fullyQualifiedParentName, stiBaseSerializer, includeAdminSerializers, }) {
7
+ export default function generateSerializerContent({ fullyQualifiedModelName, columnsWithTypes = [], fullyQualifiedParentName, stiBaseSerializer, includeAdminSerializers, modelClassName, }) {
8
8
  fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
9
9
  const additionalImports = [];
10
10
  const dreamImports = [];
@@ -16,8 +16,7 @@ export default function generateSerializerContent({ fullyQualifiedModelName, col
16
16
  else {
17
17
  dreamImports.push('DreamSerializer');
18
18
  }
19
- const relatedModelImport = importStatementForModel(fullyQualifiedModelName);
20
- const modelClassName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedModelName);
19
+ const relatedModelImport = importStatementForModel(modelClassName, fullyQualifiedModelName);
21
20
  const modelInstanceName = camelize(modelClassName);
22
21
  const modelSerializerSignature = stiBaseSerializer
23
22
  ? `<T extends ${modelClassName}>(StiChildClass: typeof ${modelClassName}, ${modelInstanceName}: T)`
@@ -94,10 +93,9 @@ function importStatementForSerializer(originModelName, destinationModelName) {
94
93
  const importFrom = absoluteDreamPath('serializers', destinationModelName);
95
94
  return `import { ${defaultSerializer}, ${summarySerializer} } from '${importFrom}'\n`;
96
95
  }
97
- function importStatementForModel(originModelName, destinationModelName = originModelName) {
98
- const modelName = globalClassNameFromFullyQualifiedModelName(destinationModelName);
99
- const importFrom = absoluteDreamPath('models', destinationModelName);
100
- return `import ${modelName} from '${importFrom}'\n`;
96
+ function importStatementForModel(modelClassName, fullyQualifiedModelName) {
97
+ const importFrom = absoluteDreamPath('models', fullyQualifiedModelName);
98
+ return `import ${modelClassName} from '${importFrom}'\n`;
101
99
  }
102
100
  function fullyQualifiedModelNameToSerializerBaseName(fullyQualifiedModelName) {
103
101
  return fullyQualifiedModelName.replace(/\//g, '');
@@ -1,24 +1,10 @@
1
- import * as fs from 'node:fs/promises';
2
- import DreamCLI from '../../cli/index.js';
3
- import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
4
- import dreamPath from '../path/dreamPath.js';
5
- import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
6
1
  import generateUnitSpecContent from './generateUnitSpecContent.js';
2
+ import writeGeneratedFile from './writeGeneratedFile.js';
7
3
  export default async function generateUnitSpec({ fullyQualifiedModelName, }) {
8
- fullyQualifiedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelName);
9
- const { relFilePath, absDirPath, absFilePath } = dreamFileAndDirPaths(dreamPath('modelSpecs'), `${fullyQualifiedModelName}.spec.ts`);
10
- try {
11
- DreamCLI.logger.log(`[dream] generating spec: ${relFilePath}`);
12
- await fs.mkdir(absDirPath, { recursive: true });
13
- await fs.writeFile(absFilePath, generateUnitSpecContent({ fullyQualifiedModelName }));
14
- }
15
- catch (error) {
16
- throw new Error(`
17
- Something happened while trying to create the spec file:
18
- ${relFilePath}
19
-
20
- Does this file already exist? Here is the error that was raised:
21
- ${error.message}
22
- `);
23
- }
4
+ await writeGeneratedFile({
5
+ dreamPathKey: 'modelSpecs',
6
+ fileName: `${fullyQualifiedModelName}.spec.ts`,
7
+ content: generateUnitSpecContent({ fullyQualifiedModelName }),
8
+ logLabel: 'spec',
9
+ });
24
10
  }
@@ -0,0 +1,15 @@
1
+ import globalClassNameFromFullyQualifiedModelName from '../globalClassNameFromFullyQualifiedModelName.js';
2
+ import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
3
+ /**
4
+ * Returns the model class name, either from an explicit override or derived
5
+ * from the fully qualified model name.
6
+ *
7
+ * @param fullyQualifiedModelName - e.g. "Room/Kitchen"
8
+ * @param modelNameOverride - optional explicit class name, e.g. "Kitchen"
9
+ * @returns The model class name, e.g. "RoomKitchen" (derived) or "Kitchen" (override)
10
+ */
11
+ export default function modelClassNameFrom(fullyQualifiedModelName, modelNameOverride) {
12
+ if (modelNameOverride)
13
+ return modelNameOverride;
14
+ return globalClassNameFromFullyQualifiedModelName(standardizeFullyQualifiedModelName(fullyQualifiedModelName));
15
+ }
@@ -0,0 +1,26 @@
1
+ import * as fs from 'node:fs/promises';
2
+ import DreamCLI from '../../cli/index.js';
3
+ import dreamFileAndDirPaths from '../path/dreamFileAndDirPaths.js';
4
+ import dreamPath from '../path/dreamPath.js';
5
+ /**
6
+ * Common file-writing helper used by all Dream generators.
7
+ * Handles directory creation, file writing, logging, and error wrapping.
8
+ */
9
+ export default async function writeGeneratedFile({ dreamPathKey, basePath, fileName, content, logLabel, }) {
10
+ const resolvedBasePath = basePath ?? dreamPath(dreamPathKey);
11
+ const { relFilePath, absDirPath, absFilePath } = dreamFileAndDirPaths(resolvedBasePath, fileName);
12
+ try {
13
+ DreamCLI.logger.log(`[dream] generating ${logLabel}: ${relFilePath}`);
14
+ await fs.mkdir(absDirPath, { recursive: true });
15
+ await fs.writeFile(absFilePath, content);
16
+ }
17
+ catch (error) {
18
+ throw new Error(`
19
+ Something happened while trying to create the ${logLabel} file:
20
+ ${relFilePath}
21
+
22
+ Does this file already exist? Here is the error that was raised:
23
+ ${error.message}
24
+ `);
25
+ }
26
+ }
@@ -13,10 +13,15 @@ export default class DreamBin {
13
13
  serializer: boolean;
14
14
  stiBaseSerializer: boolean;
15
15
  connectionName: string;
16
+ tableName?: string;
17
+ adminSerializers?: boolean;
18
+ modelName?: string;
16
19
  }): Promise<void>;
17
20
  static generateStiChild(fullyQualifiedModelName: string, fullyQualifiedParentName: string, columnsWithTypes: string[], options: {
18
21
  serializer: boolean;
19
22
  connectionName: string;
23
+ adminSerializers?: boolean;
24
+ modelName?: string;
20
25
  }): Promise<void>;
21
26
  static generateMigration(migrationName: string, columnsWithTypes: string[], connectionName: string): Promise<void>;
22
27
  private static buildDocs;
@@ -2,6 +2,8 @@ import { SpawnOptions } from 'child_process';
2
2
  import { Command } from 'commander';
3
3
  import DreamApp, { DreamAppInitOptions } from '../dream-app/index.js';
4
4
  import DreamCliLogger from './logger/DreamCliLogger.js';
5
+ export declare const CLI_INDENT = " ";
6
+ export declare const baseColumnsWithTypesDescription = "space separated snake-case (except for belongs_to model name) properties like this:\n title:citext subtitle:string body_markdown:text style:enum:post_styles:formal,informal User:belongs_to\n \n all properties default to not nullable; null can be allowed by appending ':optional':\n subtitle:string:optional\n \n supported types:\n - uuid:\n - uuid[]:\n a column optimized for storing UUIDs\n \n - citext:\n - citext[]:\n case insensitive text (indexes and queries are automatically case insensitive)\n \n - string:\n - string[]:\n varchar; allowed length defaults to 255, but may be customized, e.g.: subtitle:string:128 or subtitle:string:128:optional\n \n - text\n - text[]\n - date\n - date[]\n - datetime\n - datetime[]\n - time\n - time[]\n - timetz\n - timetz[]\n - integer\n - integer[]\n \n - decimal:\n - decimal[]:\n scale,precision is required, e.g.: volume:decimal:3,2 or volume:decimal:3,2:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: volume:decimal[]:3,2\n \n - enum:\n - enum[]:\n include the enum name to automatically create the enum:\n type:enum:room_types:bathroom,kitchen,bedroom or type:enum:room_types:bathroom,kitchen,bedroom:optional\n \n omit the enum values to leverage an existing enum (omits the enum type creation):\n type:enum:room_types or type:enum:room_types:optional\n \n leveraging arrays, add the \"[]\" suffix, e.g.: type:enum[]:room_types:bathroom,kitchen,bedroom";
5
7
  export default class DreamCLI {
6
8
  /**
7
9
  * Starts the Dream console
@@ -17,6 +19,9 @@ export default class DreamCLI {
17
19
  }): void;
18
20
  /**
19
21
  * @internal
22
+ *
23
+ * Called by Psychic (and other consumers) to programmatically generate a
24
+ * Dream model with all associated files.
20
25
  */
21
26
  static generateDream(opts: {
22
27
  fullyQualifiedModelName: string;
@@ -26,6 +31,12 @@ export default class DreamCLI {
26
31
  serializer: boolean;
27
32
  stiBaseSerializer: boolean;
28
33
  includeAdminSerializers: boolean;
34
+ tableName?: string | undefined;
35
+ /**
36
+ * Optional model class name override. If provided, generators use it
37
+ * instead of deriving the class name from the fully qualified model name.
38
+ */
39
+ modelName?: string | undefined;
29
40
  };
30
41
  fullyQualifiedParentName?: string | undefined;
31
42
  }): Promise<void>;
@@ -4,6 +4,7 @@ import Dream from '../Dream.js';
4
4
  import QueryDriverBase from '../dream/QueryDriver/Base.js';
5
5
  import { EncryptOptions } from '../encrypt/index.js';
6
6
  import autogeneratedFileDisclaimer from '../helpers/cli/autoGeneratedFileDisclaimer.js';
7
+ import modelClassNameFrom from '../helpers/cli/modelClassNameFrom.js';
7
8
  import globalClassNameFromFullyQualifiedModelName from '../helpers/globalClassNameFromFullyQualifiedModelName.js';
8
9
  import absoluteDreamPath from '../helpers/path/absoluteDreamPath.js';
9
10
  import dreamPath from '../helpers/path/dreamPath.js';
@@ -56,6 +57,7 @@ export default class DreamApp {
56
57
  */
57
58
  static get system(): {
58
59
  globalClassNameFromFullyQualifiedModelName: typeof globalClassNameFromFullyQualifiedModelName;
60
+ modelClassNameFrom: typeof modelClassNameFrom;
59
61
  absoluteDreamPath: typeof absoluteDreamPath;
60
62
  dreamPath: typeof dreamPath;
61
63
  standardizeFullyQualifiedModelName: typeof standardizeFullyQualifiedModelName;