@rvoh/dream 2.5.2 → 2.5.4

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 (191) hide show
  1. package/dist/cjs/src/cli/index.js +185 -48
  2. package/dist/cjs/src/cli/logger/DreamCliLogger.js +107 -6
  3. package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +2 -2
  4. package/dist/cjs/src/dream/QueryDriver/Postgres.js +2 -2
  5. package/dist/cjs/src/dream/QueryDriver/helpers/kysely/runMigration.js +2 -2
  6. package/dist/cjs/src/dream/QueryDriver/helpers/pg/dropDb.js +2 -2
  7. package/dist/esm/src/cli/index.js +185 -48
  8. package/dist/esm/src/cli/logger/DreamCliLogger.js +107 -6
  9. package/dist/esm/src/db/helpers/syncDbTypesFiles.js +2 -2
  10. package/dist/esm/src/dream/QueryDriver/Postgres.js +2 -2
  11. package/dist/esm/src/dream/QueryDriver/helpers/kysely/runMigration.js +2 -2
  12. package/dist/esm/src/dream/QueryDriver/helpers/pg/dropDb.js +2 -2
  13. package/dist/types/src/cli/logger/DreamCliLogger.d.ts +6 -1
  14. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  15. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  16. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  17. package/docs/classes/db.QueryDriverBase.html +31 -31
  18. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  19. package/docs/classes/errors.ColumnOverflow.html +3 -3
  20. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  21. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  22. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  23. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  24. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  25. package/docs/classes/errors.InvalidClockTime.html +2 -2
  26. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  27. package/docs/classes/errors.InvalidDateTime.html +2 -2
  28. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  29. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  30. package/docs/classes/errors.NotNullViolation.html +3 -3
  31. package/docs/classes/errors.RecordNotFound.html +3 -3
  32. package/docs/classes/errors.ValidationError.html +3 -3
  33. package/docs/classes/index.CalendarDate.html +33 -33
  34. package/docs/classes/index.ClockTime.html +32 -32
  35. package/docs/classes/index.ClockTimeTz.html +35 -35
  36. package/docs/classes/index.DateTime.html +86 -86
  37. package/docs/classes/index.Decorators.html +19 -19
  38. package/docs/classes/index.Dream.html +118 -118
  39. package/docs/classes/index.DreamApp.html +5 -5
  40. package/docs/classes/index.DreamTransaction.html +2 -2
  41. package/docs/classes/index.Env.html +2 -2
  42. package/docs/classes/index.Query.html +56 -56
  43. package/docs/classes/system.CliFileWriter.html +4 -4
  44. package/docs/classes/system.DreamBin.html +2 -2
  45. package/docs/classes/system.DreamCLI.html +6 -6
  46. package/docs/classes/system.DreamImporter.html +2 -2
  47. package/docs/classes/system.DreamLogos.html +2 -2
  48. package/docs/classes/system.DreamSerializerBuilder.html +11 -11
  49. package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
  50. package/docs/classes/system.PathHelpers.html +3 -3
  51. package/docs/classes/utils.Encrypt.html +2 -2
  52. package/docs/classes/utils.Range.html +2 -2
  53. package/docs/functions/db.closeAllDbConnections.html +1 -1
  54. package/docs/functions/db.dreamDbConnections.html +1 -1
  55. package/docs/functions/db.untypedDb.html +1 -1
  56. package/docs/functions/db.validateColumn.html +1 -1
  57. package/docs/functions/db.validateTable.html +1 -1
  58. package/docs/functions/errors.pgErrorType.html +1 -1
  59. package/docs/functions/index.DreamSerializer.html +1 -1
  60. package/docs/functions/index.ObjectSerializer.html +1 -1
  61. package/docs/functions/index.ReplicaSafe.html +1 -1
  62. package/docs/functions/index.STI.html +1 -1
  63. package/docs/functions/index.SoftDelete.html +1 -1
  64. package/docs/functions/utils.camelize.html +1 -1
  65. package/docs/functions/utils.capitalize.html +1 -1
  66. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  67. package/docs/functions/utils.compact.html +1 -1
  68. package/docs/functions/utils.groupBy.html +1 -1
  69. package/docs/functions/utils.hyphenize.html +1 -1
  70. package/docs/functions/utils.intersection.html +1 -1
  71. package/docs/functions/utils.isEmpty.html +1 -1
  72. package/docs/functions/utils.normalizeUnicode.html +1 -1
  73. package/docs/functions/utils.pascalize.html +1 -1
  74. package/docs/functions/utils.percent.html +1 -1
  75. package/docs/functions/utils.range.html +1 -1
  76. package/docs/functions/utils.round.html +1 -1
  77. package/docs/functions/utils.sanitizeString.html +1 -1
  78. package/docs/functions/utils.snakeify.html +1 -1
  79. package/docs/functions/utils.sort.html +1 -1
  80. package/docs/functions/utils.sortBy.html +1 -1
  81. package/docs/functions/utils.sortObjectByKey.html +1 -1
  82. package/docs/functions/utils.sortObjectByValue.html +1 -1
  83. package/docs/functions/utils.uncapitalize.html +1 -1
  84. package/docs/functions/utils.uniq.html +1 -1
  85. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  86. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  87. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  88. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  89. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  90. package/docs/interfaces/types.DecoratorContext.html +2 -2
  91. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  92. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  93. package/docs/interfaces/types.DurationObject.html +2 -2
  94. package/docs/interfaces/types.EncryptOptions.html +2 -2
  95. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  96. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  97. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  98. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  99. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  100. package/docs/types/openapi.OpenapiFormats.html +1 -1
  101. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  102. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  103. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  104. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  105. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  106. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  107. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  108. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  109. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  110. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  111. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  112. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  113. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  114. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  115. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  116. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  117. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  118. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  119. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  120. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  121. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  122. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  123. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  124. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  125. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  126. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  127. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  128. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  129. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  130. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  131. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  132. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  133. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  134. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  135. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  136. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  137. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  138. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  139. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  140. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  141. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  142. package/docs/types/types.CalendarDateObject.html +1 -1
  143. package/docs/types/types.Camelized.html +1 -1
  144. package/docs/types/types.ClockTimeObject.html +1 -1
  145. package/docs/types/types.DbConnectionType.html +1 -1
  146. package/docs/types/types.DbTypes.html +1 -1
  147. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  148. package/docs/types/types.DreamAttributes.html +1 -1
  149. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  150. package/docs/types/types.DreamClassColumn.html +1 -1
  151. package/docs/types/types.DreamColumn.html +1 -1
  152. package/docs/types/types.DreamColumnNames.html +1 -1
  153. package/docs/types/types.DreamLogLevel.html +1 -1
  154. package/docs/types/types.DreamLogger.html +2 -2
  155. package/docs/types/types.DreamModelSerializerType.html +1 -1
  156. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  157. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  158. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  159. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  160. package/docs/types/types.DreamSerializable.html +1 -1
  161. package/docs/types/types.DreamSerializableArray.html +1 -1
  162. package/docs/types/types.DreamSerializerKey.html +1 -1
  163. package/docs/types/types.DreamSerializers.html +1 -1
  164. package/docs/types/types.DreamVirtualColumns.html +1 -1
  165. package/docs/types/types.DurationUnit.html +1 -1
  166. package/docs/types/types.EncryptAlgorithm.html +1 -1
  167. package/docs/types/types.HasManyStatement.html +1 -1
  168. package/docs/types/types.HasOneStatement.html +1 -1
  169. package/docs/types/types.Hyphenized.html +1 -1
  170. package/docs/types/types.Pascalized.html +1 -1
  171. package/docs/types/types.PrimaryKeyType.html +1 -1
  172. package/docs/types/types.RoundingPrecision.html +1 -1
  173. package/docs/types/types.SerializerCasing.html +1 -1
  174. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  175. package/docs/types/types.Snakeified.html +1 -1
  176. package/docs/types/types.StrictInterface.html +1 -1
  177. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  178. package/docs/types/types.UpdateableProperties.html +1 -1
  179. package/docs/types/types.ValidationType.html +1 -1
  180. package/docs/types/types.ViewModel.html +2 -2
  181. package/docs/types/types.ViewModelClass.html +1 -1
  182. package/docs/types/types.WeekdayName.html +1 -1
  183. package/docs/types/types.WhereStatementForDream.html +1 -1
  184. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  185. package/docs/variables/index.DreamConst.html +1 -1
  186. package/docs/variables/index.ops.html +1 -1
  187. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  188. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  189. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  190. package/docs/variables/system.primaryKeyTypes.html +1 -1
  191. package/package.json +6 -8
@@ -7,6 +7,7 @@ import EnvInternal from '../helpers/EnvInternal.js';
7
7
  import loadRepl from '../helpers/loadRepl.js';
8
8
  import sspawn from '../helpers/sspawn.js';
9
9
  import DreamCliLogger from './logger/DreamCliLogger.js';
10
+ import colorize from './logger/loggable/colorize.js';
10
11
  export const CLI_INDENT = ' ';
11
12
  const INDENT = CLI_INDENT;
12
13
  export const baseColumnsWithTypesDescription = `space separated snake-case (except for belongs_to model name) properties like this:
@@ -63,18 +64,24 @@ const columnsWithTypesDescription = baseColumnsWithTypesDescription +
63
64
  `
64
65
  ${INDENT}
65
66
  ${INDENT} - belongs_to:
66
- ${INDENT} not only adds a foreign key to the migration, but also adds a BelongsTo association to the generated model:
67
+ ${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column, adds a database index,
68
+ ${INDENT} AND generates the @deco.BelongsTo association and typed property on the model. A raw uuid column does none of this.
67
69
  ${INDENT}
68
- ${INDENT} include the fully qualified model name, e.g., if the Coach model is in src/app/models/Health/Coach:
69
- ${INDENT} Health/Coach:belongs_to`;
70
+ ${INDENT} use the fully qualified model name (matching its path under src/app/models/):
71
+ ${INDENT} User:belongs_to # creates user_id column + BelongsTo association
72
+ ${INDENT} Health/Coach:belongs_to # creates health_coach_id column + BelongsTo association
73
+ ${INDENT} User:belongs_to:optional # nullable foreign key (for optional associations)`;
70
74
  const columnsWithTypesDescriptionForMigration = baseColumnsWithTypesDescription +
71
75
  `
72
76
  ${INDENT}
73
77
  ${INDENT} - belongs_to:
74
- ${INDENT} adds a foreign key to migration
78
+ ${INDENT} ALWAYS use this instead of adding a raw uuid column for foreign keys. It creates the FK column with an index.
79
+ ${INDENT} Unlike in g:model/g:resource, this does NOT add a BelongsTo association (no model is generated).
75
80
  ${INDENT}
76
- ${INDENT} include the fully qualified model name, e.g., if the Coach model is in src/app/models/Health/Coach:
77
- ${INDENT} Health/Coach:belongs_to`;
81
+ ${INDENT} use the fully qualified model name (matching its path under src/app/models/):
82
+ ${INDENT} User:belongs_to # creates user_id column with index
83
+ ${INDENT} Health/Coach:belongs_to # creates health_coach_id column with index
84
+ ${INDENT} User:belongs_to:optional # nullable foreign key`;
78
85
  export default class DreamCLI {
79
86
  /**
80
87
  * Starts the Dream console
@@ -89,9 +96,9 @@ export default class DreamCLI {
89
96
  static provide(program, { initializeDreamApp, seedDb, }) {
90
97
  program
91
98
  .command('sync')
92
- .description('Generates types from the current state of the database.')
93
- .option('--schema-only')
94
- .action(async (options = {}) => {
99
+ .description('Regenerates TypeScript types (types/db.ts, types/dream.ts) from the current database schema. Run this after changing associations, serializers, or enum types.')
100
+ .option('--schema-only', 'only regenerate database schema types, skipping any custom sync actions', false)
101
+ .action(async (options) => {
95
102
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
96
103
  await DreamBin.sync(() => { }, options);
97
104
  process.exit();
@@ -118,9 +125,25 @@ export default class DreamCLI {
118
125
  program
119
126
  .command('generate:migration')
120
127
  .alias('g:migration')
121
- .description('Generates a new migration file (prefer g:resource or g:model if creating a table for a new model).')
122
- .argument('<migrationName>', 'end with -to-table-name or -from-table-name to prepopulate with an alterTable command')
123
- .option('--connection-name <connectionName>', 'the connection name you wish to use for your migration')
128
+ .description(`Generates a new Kysely migration file for schema changes. Use this for altering existing tables (adding/removing columns, indexes, constraints). Prefer g:resource or g:model when creating a new model, since they generate the migration along with the model, serializer, and spec files.
129
+ ${INDENT}
130
+ ${INDENT}Examples:
131
+ ${INDENT} # Add columns to an existing table (suffix with -to-<table_name> for auto alterTable scaffolding)
132
+ ${INDENT} pnpm psy g:migration add-timezone-to-users timezone:string
133
+ ${INDENT} pnpm psy g:migration add-bio-to-users bio:text:optional avatar_url:string:optional
134
+ ${INDENT}
135
+ ${INDENT} # Remove columns (suffix with -from-<table_name>)
136
+ ${INDENT} pnpm psy g:migration remove-legacy-fields-from-posts
137
+ ${INDENT}
138
+ ${INDENT} # General schema change (no table suffix — generates empty up/down methods)
139
+ ${INDENT} pnpm psy g:migration create-unique-index-on-invitations`)
140
+ .argument('<migrationName>', `Kebab-case name describing the change. End with -to-<table_name> or -from-<table_name> to auto-generate an alterTable scaffold for that table.
141
+ ${INDENT}
142
+ ${INDENT}Examples:
143
+ ${INDENT} add-phone-to-users # scaffolds alterTable('users', ...)
144
+ ${INDENT} remove-status-from-posts # scaffolds alterTable('posts', ...)
145
+ ${INDENT} create-join-table-host-places # empty migration (no table suffix match)`)
146
+ .option('--connection-name <connectionName>', 'the database connection to use for this migration. Only needed for multi-database setups; defaults to "default"')
124
147
  .argument('[columnsWithTypes...]', columnsWithTypesDescriptionForMigration)
125
148
  .action(async (migrationName, columnsWithTypes, options) => {
126
149
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
@@ -132,15 +155,47 @@ export default class DreamCLI {
132
155
  .alias('g:model')
133
156
  .alias('generate:dream')
134
157
  .alias('g:dream')
135
- .description('Generates a Dream model with corresponding spec factory, serializer, and migration.')
136
- .option('--no-serializer')
137
- .option('--connection-name <connectionName>', 'the db connection you want this attached to (defaults to the default db connection)', 'default')
138
- .option('--sti-base-serializer')
139
- .option('--table-name <tableName>', 'explicit table name to use instead of the auto-generated one (useful when model namespaces produce long names)')
140
- .option('--model-name <modelName>', 'explicit model class name to use instead of the auto-generated one (e.g. --model-name=Kitchen for Room/Kitchen)')
141
- .option('--admin-serializers', 'generate admin serializer variants (AdminSerializer and AdminSummarySerializer) in addition to the default serializers')
142
- .option('--internal-serializers', 'generate internal serializer variants (InternalSerializer and InternalSummarySerializer) in addition to the default serializers')
143
- .argument('<modelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
158
+ .description(`Generates a Dream model with corresponding spec factory, serializer, and migration. Use this when the model will NOT be accessible via HTTP requests (e.g., internal join tables, data models with no API). For HTTP-accessible models, prefer g:resource which also generates a controller and specs.
159
+ ${INDENT}
160
+ ${INDENT}Examples:
161
+ ${INDENT} # Simple model
162
+ ${INDENT} pnpm psy g:model Tag value:citext
163
+ ${INDENT}
164
+ ${INDENT} # Join table model
165
+ ${INDENT} pnpm psy g:model HostPlace Host:belongs_to Place:belongs_to
166
+ ${INDENT}
167
+ ${INDENT} # STI parent model (use with g:sti-child for children)
168
+ ${INDENT} pnpm psy g:model --sti-base-serializer Room Place:belongs_to type:enum:room_types:Bathroom,Bedroom deleted_at:datetime:optional`)
169
+ .option('--no-serializer', 'skip serializer generation. Useful for internal models that will never be serialized in an API response (e.g., join tables, audit logs)')
170
+ .option('--connection-name <connectionName>', 'the name of the database connection to use for the model. Only needed for multi-database setups; defaults to "default"', 'default')
171
+ .option('--sti-base-serializer', `Creates generically typed base serializers (default and summary) that accept a \`StiChildClass\` parameter and include the \`type\` attribute with a per-child enum constraint. This allows consuming applications to determine the response shape based on the STI type discriminator.
172
+ ${INDENT}
173
+ ${INDENT}Use this when generating the parent model of an STI hierarchy. After generating the parent, use g:sti-child for each child type.
174
+ ${INDENT}
175
+ ${INDENT}Example:
176
+ ${INDENT} # CRITICAL: the type enums must exactly match the class names of the STI children
177
+ ${INDENT} pnpm psy g:model --sti-base-serializer Rental type:enum:place_types:Apartment,House,Condo
178
+ ${INDENT} # STI children subsequently generated using the g:sti-child generator (note the use of \`--model-name\` to generate class names that match the \`type\` column, e.g., "Apartment" instead of the "RentalApartment" default):
179
+ ${INDENT} pnpm psy g:sti-child --model-name=Apartment Rental/Apartment extends Rental
180
+ ${INDENT} pnpm psy g:sti-child --model-name=House Rental/House extends Rental
181
+ ${INDENT} pnpm psy g:sti-child --model-name=Condo Rental/Condo extends Rental`, false)
182
+ .option('--table-name <tableName>', `Explicit table name to use instead of the auto-generated one. Useful when model namespaces produce long or awkward table names.
183
+ ${INDENT}
184
+ ${INDENT}Example:
185
+ ${INDENT} pnpm psy g:model --table-name=notif_prefs Settings/NotificationPreferences User:belongs_to`)
186
+ .option('--model-name <modelName>', `Explicit model class name to use instead of the one auto-derived from the model path. Useful when the path segments don't match the desired class name.
187
+ ${INDENT}
188
+ ${INDENT}Example:
189
+ ${INDENT} pnpm psy g:model --model-name=GroupDanceLesson Lesson/Dance/Group
190
+ ${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
191
+ .option('--admin-serializers', 'also generate AdminSerializer and AdminSummarySerializer variants for admin-facing API endpoints that may expose additional fields', false)
192
+ .option('--internal-serializers', 'also generate InternalSerializer and InternalSummarySerializer variants for internal API endpoints that may expose additional fields', false)
193
+ .argument('<modelName>', `The fully qualified model name, using / for namespacing. This determines the model class name (may be overridden with \`--model-name\`), table name, and file path under src/app/models/.
194
+ ${INDENT}
195
+ ${INDENT}Examples:
196
+ ${INDENT} Post # src/app/models/Post.ts, table: posts
197
+ ${INDENT} HostPlace # src/app/models/HostPlace.ts, table: host_places
198
+ ${INDENT} Settings/CommunicationPreferences # src/app/models/Settings/CommunicationPreferences.ts`)
144
199
  .argument('[columnsWithTypes...]', columnsWithTypesDescription)
145
200
  .action(async (modelName, columnsWithTypes, options) => {
146
201
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
@@ -150,17 +205,40 @@ export default class DreamCLI {
150
205
  program
151
206
  .command('generate:sti-child')
152
207
  .alias('g:sti-child')
153
- .description('Generates a Dream model that extends another Dream model, leveraging STI (single table inheritance), with corresponding spec factory, serializer, and migration.')
154
- .option('--no-serializer')
155
- .option('--connection-name', 'the db connection you want this model attached to (defaults to the default connection)', 'default')
156
- .option('--model-name <modelName>', 'explicit model class name to use instead of the auto-generated one (e.g. --model-name=Kitchen for Room/Kitchen)')
157
- .option('--admin-serializers', 'generate admin serializer variants (AdminSerializer and AdminSummarySerializer) in addition to the default serializers')
158
- .option('--internal-serializers', 'generate internal serializer variants (InternalSerializer and InternalSummarySerializer) in addition to the default serializers')
159
- .argument('<childModelName>', 'the name of the model to create, e.g. Post or Settings/CommunicationPreferences')
160
- .argument('<extends>', 'just the word "extends"')
161
- .argument('<parentModelName>', `fully qualified name of the parent model, e.g.:
162
- ${INDENT} to extend the Room model in src/app/models/Room: Room
163
- ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Coach`)
208
+ .description(`Generates an STI (Single Table Inheritance) child model that extends an existing parent model. The child shares the parent's database table (discriminated by the \`type\` column) and can add child-specific columns. Generates a child model decorated with @STI(Parent), child serializers extending the parent's base serializers, a migration that ALTERs the parent table (not a new table), check constraints, a factory, and spec skeleton.
209
+ ${INDENT}
210
+ ${INDENT}The parent must already exist (typically generated with g:model --sti-base-serializer or g:resource --sti-base-serializer).
211
+ ${INDENT}
212
+ ${INDENT}Examples:
213
+ ${INDENT} # Child with an enum column
214
+ ${INDENT} pnpm psy g:sti-child --model-name=Bathroom Room/Bathroom extends Room bath_or_shower_style:enum:bath_or_shower_styles:bath,shower,none
215
+ ${INDENT}
216
+ ${INDENT} # Child with an enum array column
217
+ ${INDENT} pnpm psy g:sti-child --model-name=Bedroom Room/Bedroom extends Room bed_types:enum[]:bed_types:twin,queen,king
218
+ ${INDENT}
219
+ ${INDENT} # Child with no additional columns
220
+ ${INDENT} pnpm psy g:sti-child --model-name=Kitchen Room/Kitchen extends Room`)
221
+ .option('--no-serializer', 'skip serializer generation. Useful if the child uses the parent serializer directly or serialization is handled elsewhere')
222
+ .option('--connection-name', 'the name of the database connection to use for the model. Only needed for multi-database setups; defaults to "default"', 'default')
223
+ .option('--model-name <modelName>', `Explicit model class name to use instead of the one auto-derived from the model path. Useful when the path segments don't match the desired class name.
224
+ ${INDENT}
225
+ ${INDENT}Example:
226
+ ${INDENT} pnpm psy g:sti-child --model-name=GroupDanceLesson Lesson/Dance/Group extends Lesson
227
+ ${INDENT} # model is named GroupDanceLesson instead of LessonDanceGroup`)
228
+ .option('--admin-serializers', 'also generate AdminSerializer and AdminSummarySerializer variants for admin-facing API endpoints that may expose additional fields', false)
229
+ .option('--internal-serializers', 'also generate InternalSerializer and InternalSummarySerializer variants for internal API endpoints that may expose additional fields', false)
230
+ .argument('<childModelName>', `The namespaced child model path. By convention, children are nested under the parent name.
231
+ ${INDENT}
232
+ ${INDENT}Examples:
233
+ ${INDENT} Room/Bathroom # src/app/models/Room/Bathroom.ts
234
+ ${INDENT} Room/Bedroom # src/app/models/Room/Bedroom.ts
235
+ ${INDENT} Vehicle/Truck # src/app/models/Vehicle/Truck.ts`)
236
+ .argument('<extends>', 'the literal word "extends" (required syntax)')
237
+ .argument('<parentModelName>', `Fully qualified name of the parent STI model to extend. Must match the parent's path under src/app/models/.
238
+ ${INDENT}
239
+ ${INDENT}Examples:
240
+ ${INDENT} Room # extends src/app/models/Room.ts
241
+ ${INDENT} Health/Coach # extends src/app/models/Health/Coach.ts`)
164
242
  .argument('[columnsWithTypes...]', columnsWithTypesDescription)
165
243
  .action(async (childModelName, extendsWord, parentModelName, columnsWithTypes, options) => {
166
244
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
@@ -172,8 +250,18 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
172
250
  program
173
251
  .command('generate:encryption-key')
174
252
  .alias('g:encryption-key')
175
- .description('Generates a new encryption key for any of the encryption use cases in Dream or Psychic (encrypted column, encrypting/decrypting cookies, generalized use of the Encrypt library).')
176
- .addOption(new Option('--algorithm <algorithm>', 'the encryption algorithm to generate a key for')
253
+ .description(`Generates a cryptographically secure encryption key and prints it to stdout. Use this to create keys for any Dream/Psychic encryption use case:
254
+ ${INDENT}
255
+ ${INDENT} - @deco.Encrypted() model columns (e.g., phone numbers, SSNs)
256
+ ${INDENT} - Cookie encryption/decryption in Psychic sessions
257
+ ${INDENT} - General-purpose use of the Dream Encrypt library
258
+ ${INDENT}
259
+ ${INDENT}Store the generated key in your environment variables (e.g., ENCRYPTION_KEY). Never commit keys to source control.
260
+ ${INDENT}
261
+ ${INDENT}Example:
262
+ ${INDENT} pnpm psy g:encryption-key # generates aes-256-gcm key (default)
263
+ ${INDENT} pnpm psy g:encryption-key --algorithm=aes-128-gcm`)
264
+ .addOption(new Option('--algorithm <algorithm>', 'the encryption algorithm to generate a key for. aes-256-gcm (default) is recommended for most use cases')
177
265
  .choices(['aes-256-gcm', 'aes-192-gcm', 'aes-128-gcm'])
178
266
  .default('aes-256-gcm'))
179
267
  .action((options) => {
@@ -183,7 +271,7 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
183
271
  });
184
272
  program
185
273
  .command('db:create')
186
- .description('Creates a new database.')
274
+ .description('Creates the database defined in your Dream configuration. Run this once when setting up a new development environment, or after db:drop. Safe to run if the database already exists.')
187
275
  .action(async () => {
188
276
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
189
277
  await DreamBin.dbCreate();
@@ -191,7 +279,7 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
191
279
  });
192
280
  program
193
281
  .command('db:integrity-check')
194
- .description('Fails if migrations need to be run.')
282
+ .description('Checks that all migrations have been run and exits with code 1 if any are pending. Useful as a CI check or deploy gate to ensure the database schema is up to date before starting the application.')
195
283
  .action(async () => {
196
284
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
197
285
  await DreamBin.dbEnsureAllMigrationsHaveBeenRun();
@@ -199,8 +287,13 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
199
287
  });
200
288
  program
201
289
  .command('db:migrate')
202
- .description('Runs any outstanding database migrations.')
203
- .option('--skip-sync', 'skips syncing local schema after running migrations')
290
+ .description(`Runs all pending database migrations in order, then automatically syncs types (in development/test). This is the primary command for applying schema changes after generating or editing a migration.
291
+ ${INDENT}
292
+ ${INDENT}Example workflow:
293
+ ${INDENT} pnpm psy g:migration add-phone-to-users phone:string:optional
294
+ ${INDENT} # edit the migration if needed (e.g., add unique constraints)
295
+ ${INDENT} pnpm psy db:migrate`)
296
+ .option('--skip-sync', 'skip the automatic sync after migrating. Useful when running migrations in production or when you plan to sync manually afterward', false)
204
297
  .action(async ({ skipSync }) => {
205
298
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
206
299
  await DreamBin.dbMigrate();
@@ -211,9 +304,13 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
211
304
  });
212
305
  program
213
306
  .command('db:rollback')
214
- .description('Rolls back the specified number of migration steps (defaults to 1)')
215
- .option('--steps <number>', 'number of steps back to travel', myParseInt, 1)
216
- .option('--skip-sync', 'skips syncing local schema after running migrations')
307
+ .description(`Rolls back the most recent migration(s), then automatically syncs types (in development/test). Use this to undo a migration so you can edit and re-run it.
308
+ ${INDENT}
309
+ ${INDENT}Examples:
310
+ ${INDENT} pnpm psy db:rollback # rolls back the last migration
311
+ ${INDENT} pnpm psy db:rollback --steps=3 # rolls back the last 3 migrations`)
312
+ .option('--steps <number>', 'number of migration steps to roll back (default: 1)', myParseInt, 1)
313
+ .option('--skip-sync', 'skip the automatic sync after rolling back. Useful when you plan to immediately re-migrate or sync manually', false)
217
314
  .action(async ({ steps, skipSync }) => {
218
315
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
219
316
  await DreamBin.dbRollback({ steps });
@@ -224,7 +321,7 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
224
321
  });
225
322
  program
226
323
  .command('db:drop')
227
- .description('Drops the database')
324
+ .description('Drops the database. This is a destructive operation — all data will be lost. Primarily used as part of db:reset or when you need a clean slate during development.')
228
325
  .action(async () => {
229
326
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
230
327
  await DreamBin.dbDrop();
@@ -232,20 +329,53 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
232
329
  });
233
330
  program
234
331
  .command('db:reset')
235
- .description('Runs db:drop (safely), db:create, db:migrate, and db:seed')
332
+ .description(`Completely resets the database by running db:drop, db:create, db:migrate, sync, and db:seed in sequence. Use this when:
333
+ ${INDENT}
334
+ ${INDENT} - Switching between branches with incompatible migrations ("corrupted migrations" error)
335
+ ${INDENT} - Starting fresh after a schema has diverged significantly
336
+ ${INDENT} - Setting up a clean development environment
337
+ ${INDENT}
338
+ ${INDENT}Warning: all existing data will be lost. The seed file (db/seed.ts) will be run to repopulate initial data.`)
236
339
  .action(async () => {
237
340
  await initializeDreamApp({ bypassDreamIntegrityChecks: true, bypassDbConnectionsDuringInit: true });
341
+ const arrows = colorize('⭣⭣⭣', { color: 'green' }) + '\n';
342
+ DreamCLI.logger.log(colorize('db:drop', { color: 'green' }), {
343
+ logPrefix: ' ',
344
+ logPrefixColor: 'green',
345
+ });
238
346
  await DreamBin.dbDrop();
347
+ DreamCLI.logger.log(arrows, { logPrefix: ' ' });
348
+ DreamCLI.logger.log(colorize('db:create', { color: 'green' }), {
349
+ logPrefix: ' ',
350
+ logPrefixColor: 'green',
351
+ });
239
352
  await DreamBin.dbCreate();
353
+ DreamCLI.logger.log(arrows, { logPrefix: ' ' });
240
354
  await initializeDreamApp({ bypassDreamIntegrityChecks: true });
355
+ DreamCLI.logger.log(colorize('db:migrate', { color: 'green' }), {
356
+ logPrefix: ' ',
357
+ logPrefixColor: 'green',
358
+ });
241
359
  await DreamBin.dbMigrate();
360
+ DreamCLI.logger.log(arrows, { logPrefix: ' ' });
361
+ DreamCLI.logger.log(colorize('sync', { color: 'green' }), {
362
+ logPrefix: ' ',
363
+ logPrefixColor: 'green',
364
+ });
242
365
  await DreamBin.sync(onSync);
243
- await seedDb();
366
+ DreamCLI.logger.log(arrows, { logPrefix: ' ' });
367
+ DreamCLI.logger.log(colorize('db:seed', { color: 'green' }), {
368
+ logPrefix: ' ',
369
+ logPrefixColor: 'green',
370
+ });
371
+ await DreamCLI.logger.logProgress('seeding db...', async () => {
372
+ await seedDb();
373
+ });
244
374
  process.exit();
245
375
  });
246
376
  program
247
377
  .command('db:seed')
248
- .description('Seeds the database using the file located in db/seed.ts.')
378
+ .description(`Seeds the database by running the seed function defined in db/seed.ts. Skipped automatically in test environments unless DREAM_SEED_DB_IN_TEST=1 is set. Runs automatically as the last step of db:reset.`)
249
379
  .action(async () => {
250
380
  if (process.env.NODE_ENV === 'test' && process.env.DREAM_SEED_DB_IN_TEST !== '1') {
251
381
  DreamApp.log('skipping db seed for test env. To really seed for test, add DREAM_SEED_DB_IN_TEST=1');
@@ -258,9 +388,16 @@ ${INDENT} to extend the Coach model in src/app/models/Health/Coach: Health/Co
258
388
  program
259
389
  .command('inspect:serialization')
260
390
  .alias('i:serialization')
261
- .description('Displays a serialization map to help understand the rendering logic for a particular model.')
262
- .argument('<globalName>', 'the global name of the model you want to look up')
263
- .argument('[serializerKey]', 'the serializer key you wish to use')
391
+ .description(`Displays a detailed serialization map for a model, showing all attributes, associations, custom attributes, and their types. Useful for debugging serializer output, understanding what a model's API response will look like, and verifying that associations are preloaded correctly.
392
+ ${INDENT}
393
+ ${INDENT}Examples:
394
+ ${INDENT} pnpm psy i:serialization Place # shows the default serializer for Place
395
+ ${INDENT} pnpm psy i:serialization Place summary # shows the summary serializer for Place
396
+ ${INDENT} pnpm psy i:serialization Room/Bedroom # shows serializer for an STI child`)
397
+ .argument('<globalName>', `The global name of the model as registered in Dream (typically matches the model class name or its fully qualified path).
398
+ ${INDENT}
399
+ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`)
400
+ .argument('[serializerKey]', 'the serializer variant to display (e.g., "summary", "admin", "internal"). Defaults to "default" if omitted')
264
401
  .action(async (globalName, serializerKey) => {
265
402
  await initializeDreamApp();
266
403
  const dreamApp = DreamApp.getOrFail();
@@ -1,25 +1,126 @@
1
+ import c from 'yoctocolors';
1
2
  import DreamCliLoggableText from './loggable/DreamCliLoggableText.js';
3
+ const SPINNER_FRAMES = ['✺', '✹', '✸', '✷', '✶', '✵', '✴', '✵', '✶', '✷', '✸', '✹'];
4
+ const EYE_FRAMES = ['─', '◡', '◯', '◉', '●', '◉', '◯', '◡'];
5
+ const SPINNER_INTERVAL_MS = 200;
6
+ function formatElapsed(ms) {
7
+ if (ms < 1000)
8
+ return `${ms}ms`;
9
+ return `${(ms / 1000).toFixed(1)}s`;
10
+ }
2
11
  export default class DreamCliLogger {
12
+ spinner = null;
3
13
  log(text, { logPrefix, logPrefixColor, logPrefixBgColor } = {}) {
4
14
  const loggable = new DreamCliLoggableText(text, {
5
15
  logPrefix,
6
- logPrefixColor: logPrefixColor || 'green',
16
+ logPrefixColor: logPrefixColor || 'greenBright',
7
17
  logPrefixBgColor,
8
18
  });
9
19
  loggable.render();
10
20
  }
11
21
  async logProgress(text, cb, { logPrefix = '✺ ┌', logPrefixColor, logPrefixBgColor } = {}) {
12
- this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
22
+ this.logStartProgress(text, { logPrefix, logPrefixColor, logPrefixBgColor });
13
23
  await cb();
14
24
  this.logEndProgress();
15
25
  }
16
- logStartProgress(text, { logPrefix = '✺ ┌', logPrefixColor, logPrefixBgColor } = {}) {
17
- this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
26
+ logStartProgress(text, { logPrefixColor, logPrefixBgColor } = {}) {
27
+ if (!process.stdout.isTTY) {
28
+ this.log(text, { logPrefix: '✺ ┌', logPrefixColor: logPrefixColor || 'greenBright', logPrefixBgColor });
29
+ return;
30
+ }
31
+ this.startTopSpinner(text);
32
+ }
33
+ startTopSpinner(text) {
34
+ const renderFrame = (frameIndex) => {
35
+ const star = SPINNER_FRAMES[frameIndex % SPINNER_FRAMES.length];
36
+ process.stdout.write(`${this.escapeSequence('clearLine')}${c.yellow(`${star} ┌`)} ${c.yellow(text)}`);
37
+ };
38
+ renderFrame(0);
39
+ const interval = setInterval(() => {
40
+ if (this.spinner && !this.spinner.committed) {
41
+ this.spinner.frameIndex++;
42
+ renderFrame(this.spinner.frameIndex);
43
+ }
44
+ }, SPINNER_INTERVAL_MS);
45
+ this.spinner = {
46
+ actionText: text,
47
+ startTime: Date.now(),
48
+ frameIndex: 0,
49
+ interval,
50
+ committed: false,
51
+ };
52
+ }
53
+ startBottomSpinner() {
54
+ if (!process.stdout.isTTY || !this.spinner)
55
+ return;
56
+ const renderFrame = (frameIndex) => {
57
+ const eye = EYE_FRAMES[frameIndex % EYE_FRAMES.length];
58
+ process.stdout.write(`${this.escapeSequence('clearLine')}${c.yellow(eye)} ${c.yellow(`└ ${this.spinner.actionText}`)}`);
59
+ };
60
+ this.spinner.frameIndex = 0;
61
+ renderFrame(0);
62
+ this.spinner.interval = setInterval(() => {
63
+ if (this.spinner) {
64
+ this.spinner.frameIndex++;
65
+ renderFrame(this.spinner.frameIndex);
66
+ }
67
+ }, SPINNER_INTERVAL_MS);
18
68
  }
19
69
  logContinueProgress(text, { logPrefix = ' ├', logPrefixColor, logPrefixBgColor } = {}) {
20
- this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
70
+ if (this.spinner && !this.spinner.committed) {
71
+ clearInterval(this.spinner.interval);
72
+ // Overwrite the yellow animated header with green past-tense text before marking
73
+ // the spinner as committed
74
+ process.stdout.write(`${this.escapeSequence('clearLine')}${c.greenBright('✺ ┌')} ${c.greenBright(this.spinner.actionText)}\n`);
75
+ this.spinner.committed = true;
76
+ this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
77
+ this.startBottomSpinner();
78
+ }
79
+ else if (this.spinner?.committed) {
80
+ // Clear the bottom running... spinner, log sub-line, then re-show it
81
+ clearInterval(this.spinner.interval);
82
+ this.clearLine();
83
+ this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
84
+ this.startBottomSpinner();
85
+ }
86
+ else {
87
+ this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
88
+ }
21
89
  }
22
90
  logEndProgress(text = 'complete', { logPrefix = ' └', logPrefixColor, logPrefixBgColor } = {}) {
23
- this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
91
+ if (!this.spinner) {
92
+ this.log(text, { logPrefix, logPrefixColor, logPrefixBgColor });
93
+ return;
94
+ }
95
+ const spinner = this.spinner;
96
+ this.spinner = null;
97
+ clearInterval(spinner.interval);
98
+ const elapsed = Date.now() - spinner.startTime;
99
+ const elapsedStr = formatElapsed(elapsed);
100
+ if (!spinner.committed) {
101
+ // No sub-lines: overwrite the spinner in-place with the green completed header
102
+ process.stdout.write(`${this.escapeSequence('clearLine')}${c.greenBright('✺ ┌')} ${c.greenBright(spinner.actionText)}\n`);
103
+ }
104
+ else {
105
+ // Has sub-lines: clear the bottom running... spinner line
106
+ if (process.stdout.isTTY)
107
+ this.clearLine();
108
+ }
109
+ this.log(c.greenBright(`done in ${elapsedStr}\n`), {
110
+ logPrefix,
111
+ logPrefixColor: logPrefixColor || 'greenBright',
112
+ logPrefixBgColor,
113
+ });
114
+ }
115
+ clearLine() {
116
+ process.stdout.write(this.escapeSequence('clearLine'));
117
+ }
118
+ escapeSequence(sequence) {
119
+ switch (sequence) {
120
+ case 'clearLine':
121
+ return `\r\x1b[2K`;
122
+ default:
123
+ throw new Error(`unexpected sequence: ${sequence}`);
124
+ }
24
125
  }
25
126
  }
@@ -28,8 +28,8 @@ export default async function syncDbTypesFiles(connectionName) {
28
28
  const kyselyCodegenCmd = `kysely-codegen ${dialect} ${url} ${includePattern} ${excludePattern} ${outfile}`;
29
29
  await sspawn(kyselyCodegenCmd, {
30
30
  onStdout: message => {
31
- DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'cyan' }) + ' ' + message, {
32
- logPrefixColor: 'cyan',
31
+ DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) + ' ' + message, {
32
+ logPrefixColor: 'greenBright',
33
33
  });
34
34
  },
35
35
  });
@@ -157,13 +157,13 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
157
157
  const client = await loadPgClient({ useSystemDb: true, connectionName });
158
158
  if (EnvInternal.boolean('DREAM_CORE_DEVELOPMENT')) {
159
159
  const replicaTestWorkerDatabaseName = `replica_test_${dbConf.name}`;
160
- DreamCLI.logger.logContinueProgress(`creating fake replica test database ${replicaTestWorkerDatabaseName}...`, { logPrefix: ' ├ [db]', logPrefixColor: 'cyan' });
160
+ DreamCLI.logger.logContinueProgress(`creating fake replica test database ${replicaTestWorkerDatabaseName}...`, { logPrefix: ' ├ [db]', logPrefixColor: 'greenBright' });
161
161
  await client.query(`DROP DATABASE IF EXISTS ${replicaTestWorkerDatabaseName};`);
162
162
  await client.query(`CREATE DATABASE ${replicaTestWorkerDatabaseName} TEMPLATE ${dbConf.name};`);
163
163
  }
164
164
  for (let i = 2; i <= parallelTests; i++) {
165
165
  const workerDatabaseName = `${dbConf.name}_${i}`;
166
- DreamCLI.logger.logContinueProgress(`creating duplicate test database ${workerDatabaseName} for concurrent tests...`, { logPrefix: ' ├ [db]', logPrefixColor: 'cyan' });
166
+ DreamCLI.logger.logContinueProgress(`creating duplicate test database ${workerDatabaseName} for concurrent tests...`, { logPrefix: ' ├ [db]', logPrefixColor: 'greenBright' });
167
167
  await client.query(`DROP DATABASE IF EXISTS ${workerDatabaseName};`);
168
168
  await client.query(`CREATE DATABASE ${workerDatabaseName} TEMPLATE ${dbConf.name};`);
169
169
  }
@@ -88,8 +88,8 @@ function migratedActionPastTense(mode) {
88
88
  function logResults(results, mode) {
89
89
  results?.forEach(it => {
90
90
  if (it.status === 'Success') {
91
- DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'cyan' }) +
92
- ` migration "${it.migrationName}" was ${migratedActionPastTense(mode)} successfully`, { logPrefixColor: 'cyan' });
91
+ DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) +
92
+ ` migration "${it.migrationName}" was ${migratedActionPastTense(mode)} successfully`, { logPrefixColor: 'greenBright' });
93
93
  }
94
94
  else if (it.status === 'Error') {
95
95
  DreamCLI.logger.logContinueProgress(JSON.stringify(it, null, 2));
@@ -22,14 +22,14 @@ async function maybeDropDuplicateDatabases(client, dbName) {
22
22
  return;
23
23
  if (EnvInternal.boolean('DREAM_CORE_DEVELOPMENT')) {
24
24
  const replicaTestWorkerDatabaseName = `replica_test_${dbName}`;
25
- DreamCLI.logger.logContinueProgress(`dropping fake replica test database ${replicaTestWorkerDatabaseName}`, { logPrefix: ' ├ [db]', logPrefixColor: 'cyan' });
25
+ DreamCLI.logger.logContinueProgress(`dropping fake replica test database ${replicaTestWorkerDatabaseName}`, { logPrefix: ' ├ [db]', logPrefixColor: 'greenBright' });
26
26
  await client.query(`DROP DATABASE IF EXISTS ${replicaTestWorkerDatabaseName};`);
27
27
  }
28
28
  for (let i = 2; i <= parallelTests; i++) {
29
29
  const workerDatabaseName = `${dbName}_${i}`;
30
30
  DreamCLI.logger.logContinueProgress(`dropping duplicate test database ${workerDatabaseName}`, {
31
31
  logPrefix: ' ├ [db]',
32
- logPrefixColor: 'cyan',
32
+ logPrefixColor: 'greenBright',
33
33
  });
34
34
  await client.query(`DROP DATABASE IF EXISTS ${workerDatabaseName};`);
35
35
  }
@@ -1,8 +1,13 @@
1
1
  import { DreamCliLoggerLogOpts } from '../../types/logger.js';
2
2
  export default class DreamCliLogger {
3
+ private spinner;
3
4
  log(text: string, { logPrefix, logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): void;
4
5
  logProgress(text: string, cb: () => void | Promise<void>, { logPrefix, logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): Promise<void>;
5
- logStartProgress(text: string, { logPrefix, logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): void;
6
+ logStartProgress(text: string, { logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): void;
7
+ private startTopSpinner;
8
+ private startBottomSpinner;
6
9
  logContinueProgress(text: string, { logPrefix, logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): void;
7
10
  logEndProgress(text?: string, { logPrefix, logPrefixColor, logPrefixBgColor }?: DreamCliLoggerLogOpts): void;
11
+ private clearLine;
12
+ private escapeSequence;
8
13
  }