@rvoh/dream 2.8.0 → 2.9.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (228) hide show
  1. package/dist/cjs/src/bin/index.js +17 -2
  2. package/dist/cjs/src/cli/index.js +88 -12
  3. package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +17 -12
  4. package/dist/cjs/src/decorators/class/ReplicaSafe.js +3 -4
  5. package/dist/cjs/src/decorators/class/STI.js +1 -2
  6. package/dist/cjs/src/decorators/class/SoftDelete.js +4 -5
  7. package/dist/cjs/src/dream/QueryDriver/Kysely.js +21 -10
  8. package/dist/cjs/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +19 -6
  9. package/dist/cjs/src/encrypt/index.js +80 -11
  10. package/dist/cjs/src/errors/SspawnRequiresDevelopmentOrTest.js +26 -0
  11. package/dist/cjs/src/errors/encrypt/DecryptionError.js +5 -0
  12. package/dist/cjs/src/errors/encrypt/DecryptionParseError.js +5 -0
  13. package/dist/cjs/src/errors/encrypt/DecryptionWithRotationError.js +12 -0
  14. package/dist/cjs/src/ops/index.js +28 -1
  15. package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +29 -10
  16. package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +20 -4
  17. package/dist/esm/src/bin/index.js +17 -2
  18. package/dist/esm/src/cli/index.js +88 -12
  19. package/dist/esm/src/db/helpers/syncDbTypesFiles.js +17 -12
  20. package/dist/esm/src/decorators/class/ReplicaSafe.js +3 -4
  21. package/dist/esm/src/decorators/class/STI.js +1 -2
  22. package/dist/esm/src/decorators/class/SoftDelete.js +4 -5
  23. package/dist/esm/src/dream/QueryDriver/Kysely.js +21 -10
  24. package/dist/esm/src/encrypt/algorithms/aes-gcm/decryptAESGCM.js +19 -6
  25. package/dist/esm/src/encrypt/index.js +80 -11
  26. package/dist/esm/src/errors/SspawnRequiresDevelopmentOrTest.js +26 -0
  27. package/dist/esm/src/errors/encrypt/DecryptionError.js +5 -0
  28. package/dist/esm/src/errors/encrypt/DecryptionParseError.js +5 -0
  29. package/dist/esm/src/errors/encrypt/DecryptionWithRotationError.js +12 -0
  30. package/dist/esm/src/ops/index.js +28 -1
  31. package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +29 -10
  32. package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +20 -4
  33. package/dist/types/src/cli/index.d.ts +57 -4
  34. package/dist/types/src/decorators/class/ReplicaSafe.d.ts +2 -1
  35. package/dist/types/src/decorators/class/STI.d.ts +1 -1
  36. package/dist/types/src/decorators/class/SoftDelete.d.ts +2 -1
  37. package/dist/types/src/dream/QueryDriver/Kysely.d.ts +17 -0
  38. package/dist/types/src/dream-app/index.d.ts +25 -1
  39. package/dist/types/src/encrypt/algorithms/aes-gcm/decryptAESGCM.d.ts +1 -1
  40. package/dist/types/src/encrypt/index.d.ts +59 -0
  41. package/dist/types/src/errors/SspawnRequiresDevelopmentOrTest.d.ts +5 -0
  42. package/dist/types/src/errors/encrypt/DecryptionError.d.ts +3 -0
  43. package/dist/types/src/errors/encrypt/DecryptionParseError.d.ts +3 -0
  44. package/dist/types/src/errors/encrypt/DecryptionWithRotationError.d.ts +7 -0
  45. package/dist/types/src/ops/index.d.ts +28 -1
  46. package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +40 -12
  47. package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +23 -5
  48. package/docs/classes/db.DreamMigrationHelpers.html +9 -9
  49. package/docs/classes/db.KyselyQueryDriver.html +32 -32
  50. package/docs/classes/db.PostgresQueryDriver.html +33 -33
  51. package/docs/classes/db.QueryDriverBase.html +31 -31
  52. package/docs/classes/errors.CheckConstraintViolation.html +3 -3
  53. package/docs/classes/errors.ColumnOverflow.html +3 -3
  54. package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
  55. package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
  56. package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
  57. package/docs/classes/errors.GlobalNameNotSet.html +3 -3
  58. package/docs/classes/errors.InvalidCalendarDate.html +2 -2
  59. package/docs/classes/errors.InvalidClockTime.html +2 -2
  60. package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
  61. package/docs/classes/errors.InvalidDateTime.html +2 -2
  62. package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
  63. package/docs/classes/errors.NonLoadedAssociation.html +3 -3
  64. package/docs/classes/errors.NotNullViolation.html +3 -3
  65. package/docs/classes/errors.RecordNotFound.html +3 -3
  66. package/docs/classes/errors.ValidationError.html +3 -3
  67. package/docs/classes/index.CalendarDate.html +33 -33
  68. package/docs/classes/index.ClockTime.html +32 -32
  69. package/docs/classes/index.ClockTimeTz.html +35 -35
  70. package/docs/classes/index.DateTime.html +86 -86
  71. package/docs/classes/index.Decorators.html +19 -19
  72. package/docs/classes/index.Dream.html +118 -118
  73. package/docs/classes/index.DreamApp.html +5 -5
  74. package/docs/classes/index.DreamTransaction.html +2 -2
  75. package/docs/classes/index.Env.html +2 -2
  76. package/docs/classes/index.Query.html +56 -56
  77. package/docs/classes/system.CliFileWriter.html +4 -4
  78. package/docs/classes/system.DreamBin.html +2 -2
  79. package/docs/classes/system.DreamCLI.html +41 -6
  80. package/docs/classes/system.DreamImporter.html +2 -2
  81. package/docs/classes/system.DreamLogos.html +2 -2
  82. package/docs/classes/system.DreamSerializerBuilder.html +45 -23
  83. package/docs/classes/system.ObjectSerializerBuilder.html +32 -13
  84. package/docs/classes/system.PathHelpers.html +3 -3
  85. package/docs/classes/utils.Encrypt.html +51 -2
  86. package/docs/classes/utils.Range.html +2 -2
  87. package/docs/functions/db.closeAllDbConnections.html +1 -1
  88. package/docs/functions/db.dreamDbConnections.html +1 -1
  89. package/docs/functions/db.untypedDb.html +1 -1
  90. package/docs/functions/db.validateColumn.html +1 -1
  91. package/docs/functions/db.validateTable.html +1 -1
  92. package/docs/functions/errors.pgErrorType.html +1 -1
  93. package/docs/functions/index.DreamSerializer.html +1 -1
  94. package/docs/functions/index.ObjectSerializer.html +1 -1
  95. package/docs/functions/index.ReplicaSafe.html +1 -1
  96. package/docs/functions/index.STI.html +1 -1
  97. package/docs/functions/index.SoftDelete.html +2 -2
  98. package/docs/functions/utils.camelize.html +1 -1
  99. package/docs/functions/utils.capitalize.html +1 -1
  100. package/docs/functions/utils.cloneDeepSafe.html +1 -1
  101. package/docs/functions/utils.compact.html +1 -1
  102. package/docs/functions/utils.groupBy.html +1 -1
  103. package/docs/functions/utils.hyphenize.html +1 -1
  104. package/docs/functions/utils.intersection.html +1 -1
  105. package/docs/functions/utils.isEmpty.html +1 -1
  106. package/docs/functions/utils.normalizeUnicode.html +1 -1
  107. package/docs/functions/utils.pascalize.html +1 -1
  108. package/docs/functions/utils.percent.html +1 -1
  109. package/docs/functions/utils.range.html +1 -1
  110. package/docs/functions/utils.round.html +1 -1
  111. package/docs/functions/utils.sanitizeString.html +1 -1
  112. package/docs/functions/utils.snakeify.html +1 -1
  113. package/docs/functions/utils.sort.html +1 -1
  114. package/docs/functions/utils.sortBy.html +1 -1
  115. package/docs/functions/utils.sortObjectByKey.html +1 -1
  116. package/docs/functions/utils.sortObjectByValue.html +1 -1
  117. package/docs/functions/utils.uncapitalize.html +1 -1
  118. package/docs/functions/utils.uniq.html +1 -1
  119. package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
  120. package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
  121. package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
  122. package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
  123. package/docs/interfaces/types.BelongsToStatement.html +2 -2
  124. package/docs/interfaces/types.DecoratorContext.html +2 -2
  125. package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
  126. package/docs/interfaces/types.DreamAppOpts.html +2 -2
  127. package/docs/interfaces/types.DurationObject.html +2 -2
  128. package/docs/interfaces/types.EncryptOptions.html +2 -2
  129. package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
  130. package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
  131. package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
  132. package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
  133. package/docs/types/openapi.OpenapiAllTypes.html +1 -1
  134. package/docs/types/openapi.OpenapiFormats.html +1 -1
  135. package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
  136. package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
  137. package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
  138. package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
  139. package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
  140. package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
  141. package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
  142. package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
  143. package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
  144. package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
  145. package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
  146. package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
  147. package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
  148. package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
  149. package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
  150. package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
  151. package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
  152. package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
  153. package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
  154. package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
  155. package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
  156. package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
  157. package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
  158. package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
  159. package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
  160. package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
  161. package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
  162. package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
  163. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
  164. package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
  165. package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
  166. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
  167. package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
  168. package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
  169. package/docs/types/openapi.OpenapiSchemaString.html +1 -1
  170. package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
  171. package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
  172. package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
  173. package/docs/types/openapi.OpenapiTypeField.html +1 -1
  174. package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
  175. package/docs/types/types.CalendarDateDurationUnit.html +1 -1
  176. package/docs/types/types.CalendarDateObject.html +1 -1
  177. package/docs/types/types.Camelized.html +1 -1
  178. package/docs/types/types.ClockTimeObject.html +1 -1
  179. package/docs/types/types.DbConnectionType.html +1 -1
  180. package/docs/types/types.DbTypes.html +1 -1
  181. package/docs/types/types.DreamAssociationMetadata.html +1 -1
  182. package/docs/types/types.DreamAttributes.html +1 -1
  183. package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
  184. package/docs/types/types.DreamClassColumn.html +1 -1
  185. package/docs/types/types.DreamColumn.html +1 -1
  186. package/docs/types/types.DreamColumnNames.html +1 -1
  187. package/docs/types/types.DreamLogLevel.html +1 -1
  188. package/docs/types/types.DreamLogger.html +2 -2
  189. package/docs/types/types.DreamModelSerializerType.html +1 -1
  190. package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
  191. package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
  192. package/docs/types/types.DreamParamSafeAttributes.html +1 -1
  193. package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
  194. package/docs/types/types.DreamSerializable.html +1 -1
  195. package/docs/types/types.DreamSerializableArray.html +1 -1
  196. package/docs/types/types.DreamSerializerKey.html +1 -1
  197. package/docs/types/types.DreamSerializers.html +1 -1
  198. package/docs/types/types.DreamVirtualColumns.html +1 -1
  199. package/docs/types/types.DurationUnit.html +1 -1
  200. package/docs/types/types.EncryptAlgorithm.html +1 -1
  201. package/docs/types/types.HasManyStatement.html +1 -1
  202. package/docs/types/types.HasOneStatement.html +1 -1
  203. package/docs/types/types.Hyphenized.html +1 -1
  204. package/docs/types/types.Pascalized.html +1 -1
  205. package/docs/types/types.PrimaryKeyType.html +1 -1
  206. package/docs/types/types.RoundingPrecision.html +1 -1
  207. package/docs/types/types.SerializerCasing.html +1 -1
  208. package/docs/types/types.SimpleObjectSerializerType.html +1 -1
  209. package/docs/types/types.Snakeified.html +1 -1
  210. package/docs/types/types.StrictInterface.html +1 -1
  211. package/docs/types/types.UpdateableAssociationProperties.html +1 -1
  212. package/docs/types/types.UpdateableProperties.html +1 -1
  213. package/docs/types/types.ValidationType.html +1 -1
  214. package/docs/types/types.ViewModel.html +2 -2
  215. package/docs/types/types.ViewModelClass.html +1 -1
  216. package/docs/types/types.WeekdayName.html +1 -1
  217. package/docs/types/types.WhereStatementForDream.html +1 -1
  218. package/docs/types/types.WhereStatementForDreamClass.html +1 -1
  219. package/docs/variables/index.DreamConst.html +1 -1
  220. package/docs/variables/index.ops.html +26 -4
  221. package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
  222. package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
  223. package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
  224. package/docs/variables/system.primaryKeyTypes.html +1 -1
  225. package/package.json +1 -1
  226. package/dist/cjs/src/helpers/sspawn.js +0 -44
  227. package/dist/esm/src/helpers/sspawn.js +0 -44
  228. package/dist/types/src/helpers/sspawn.d.ts +0 -7
@@ -63,17 +63,38 @@ const ops = {
63
63
  * Creates an `OpsStatement` using the SQL `LIKE` operator for case-sensitive pattern matching.
64
64
  * Use `%` as a wildcard in the pattern string.
65
65
  *
66
+ * **User-supplied patterns:** the value is bound as a parameter (no SQL
67
+ * injection), but `%`, `_`, and `\` in the bound value are still interpreted
68
+ * as wildcards by the SQL engine. Wrap user input with `ops.like.escape`
69
+ * if you need literal matching:
70
+ *
71
+ * User.where({ name: ops.like(`%${ops.like.escape(query)}%`) })
72
+ *
73
+ * `ops.like.escape` escapes the three metacharacters that PostgreSQL's
74
+ * `LIKE` / `ILIKE` operators treat as wildcards (`\`, `%`, `_`) so the
75
+ * returned string matches literally. The same helper applies to all four
76
+ * LIKE-family ops (`like`, `ilike`, `not.like`, `not.ilike`); it is exposed
77
+ * here as the canonical entry point. If a caller uses `ESCAPE '...'` with
78
+ * a non-default character this helper will not be appropriate.
79
+ *
66
80
  * @param like - The pattern string (e.g. `'%hello%'`).
67
81
  * @returns An `OpsStatement` using the `like` operator.
68
82
  *
69
83
  * @example
70
84
  * User.where({ name: ops.like('%alice%') })
71
85
  */
72
- like: (like) => new OpsStatement('like', like),
86
+ like: Object.assign((like) => new OpsStatement('like', like), {
87
+ escape: (input) => input.replace(/\\/g, '\\\\').replace(/%/g, '\\%').replace(/_/g, '\\_'),
88
+ }),
73
89
  /**
74
90
  * Creates an `OpsStatement` using the SQL `ILIKE` operator for case-insensitive pattern matching.
75
91
  * Use `%` as a wildcard in the pattern string.
76
92
  *
93
+ * **User-supplied patterns:** the value is bound as a parameter (no SQL
94
+ * injection), but `%`, `_`, and `\` in the bound value are still interpreted
95
+ * as wildcards by the SQL engine. Wrap user input with `ops.like.escape`
96
+ * if you need literal matching.
97
+ *
77
98
  * @param ilike - The pattern string (e.g. `'%hello%'`).
78
99
  * @returns An `OpsStatement` using the `ilike` operator.
79
100
  *
@@ -201,6 +222,9 @@ const ops = {
201
222
  /**
202
223
  * Creates an `OpsStatement` using the `NOT LIKE` operator for case-sensitive pattern exclusion.
203
224
  *
225
+ * **User-supplied patterns:** see the note on `ops.like`. Wrap user input
226
+ * with `ops.like.escape` for literal matching.
227
+ *
204
228
  * @param like - The pattern string (e.g. `'%spam%'`).
205
229
  * @returns An `OpsStatement` using the `not like` operator.
206
230
  *
@@ -211,6 +235,9 @@ const ops = {
211
235
  /**
212
236
  * Creates an `OpsStatement` using the `NOT ILIKE` operator for case-insensitive pattern exclusion.
213
237
  *
238
+ * **User-supplied patterns:** see the note on `ops.ilike`. Wrap user input
239
+ * with `ops.like.escape` for literal matching.
240
+ *
214
241
  * @param ilike - The pattern string (e.g. `'%spam%'`).
215
242
  * @returns An `OpsStatement` using the `not ilike` operator.
216
243
  *
@@ -20,14 +20,27 @@ export default class DreamSerializerBuilder {
20
20
  /**
21
21
  * Includes an attribute from a nested object in the serialized output.
22
22
  *
23
- * Accesses `targetName.name` on the data object. If the target object
24
- * or the delegated attribute is null/undefined, the `default` option value
25
- * will be used if provided.
23
+ * Accesses `targetName.name` on the data object. When the target object or the
24
+ * delegated attribute is null/undefined, the resolution order is:
25
+ * 1. `default` if provided renders the default value
26
+ * 2. `required: false` → omits the key entirely from the rendered output
27
+ * 3. otherwise → renders `null`
26
28
  *
27
29
  * When the target is a Dream model, OpenAPI types may be automatically inferred
28
30
  * for standard database columns. For json/jsonb columns or non-Dream targets,
29
31
  * the `openapi` option is required.
30
32
  *
33
+ * `optional` and `required` are not aliases — they encode different things and can
34
+ * be used together:
35
+ * - `optional: true` is an OpenAPI-only marker (no runtime effect). It declares
36
+ * that the rendered value may be `null` (e.g. when delegating through a HasOne
37
+ * that may be missing). Use this when you want the key to always be present
38
+ * but the value to be nullable in the schema.
39
+ * - `required: false` affects both runtime and OpenAPI. At runtime, when the
40
+ * resolved value is `undefined` (which includes the case of a missing delegated
41
+ * association with no `default`), the key is omitted from the rendered output.
42
+ * In OpenAPI, the field is marked as not required.
43
+ *
31
44
  * @param targetName - The property name containing the target object (e.g., an association name)
32
45
  * @param name - The attribute name within the target object
33
46
  * @param options - Configuration options:
@@ -35,16 +48,22 @@ export default class DreamSerializerBuilder {
35
48
  * (e.g., delegating `'user', 'email'` with `as: 'userEmail'` outputs the value
36
49
  * under `userEmail`)
37
50
  * - `default` - Value to use when the target object or its attribute is null/undefined
51
+ * (not available when delegating to a `'type'` STI discriminator column: substituting
52
+ * a discriminator string when the association is actually missing produces a response
53
+ * indistinguishable from "association present with that type," which is misleading)
38
54
  * - `openapi` - OpenAPI schema definition; required for non-Dream targets and json/jsonb
39
55
  * columns, optional for standard Dream columns (where types are inferred)
40
- * - `optional` - Set to `true` to indicate the value can be null in the OpenAPI schema
41
- * (wraps the type in `anyOf: [schema, { type: 'null' }]`). For Dream models, this is
42
- * auto-inferred from optional BelongsTo associations. Use this when delegating through
43
- * a HasOne or other nullable association.
56
+ * - `optional` - Set to `true` to mark the value as nullable in the OpenAPI schema
57
+ * (wraps the type in `anyOf: [schema, { type: 'null' }]`). OpenAPI-only the
58
+ * key is still rendered (as `null`). For Dream models, this is auto-inferred
59
+ * from optional BelongsTo associations. Use this when delegating through a
60
+ * HasOne or other nullable association.
44
61
  * - `precision` - Round decimal values to the specified number of decimal places (0–9)
45
- * during rendering; does not affect the OpenAPI shape
46
- * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
47
- * when omitted, attributes are required by default
62
+ * during rendering; does not affect the OpenAPI shape (not available when delegating
63
+ * to a `'type'` STI discriminator column, which is always a string enum)
64
+ * - `required` - Set to `false` to omit the key from the rendered output when the
65
+ * resolved value is `undefined` (including a missing delegated association with
66
+ * no `default`), and to mark the field as not required in the OpenAPI schema
48
67
  * @returns The serializer builder for method chaining
49
68
  *
50
69
  * @example
@@ -69,8 +69,20 @@ export default class ObjectSerializerBuilder {
69
69
  * Includes an attribute from a nested object in the serialized output.
70
70
  *
71
71
  * Accesses `targetName.name` on the data object. The `openapi` option is always
72
- * required. If the target object or the delegated attribute is null/undefined,
73
- * the `default` option value will be used if provided.
72
+ * required. When the target object or the delegated attribute is null/undefined,
73
+ * the resolution order is:
74
+ * 1. `default` if provided → renders the default value
75
+ * 2. `required: false` → omits the key entirely from the rendered output
76
+ * 3. otherwise → renders `null`
77
+ *
78
+ * `optional` and `required` are not aliases — they encode different things and can
79
+ * be used together:
80
+ * - `optional: true` is an OpenAPI-only marker (no runtime effect). It declares
81
+ * that the rendered value may be `null`. Use this when you want the key to always
82
+ * be present but the value to be nullable in the schema.
83
+ * - `required: false` affects both runtime and OpenAPI. At runtime, when the
84
+ * resolved value is `undefined`, the key is omitted from the rendered output.
85
+ * In OpenAPI, the field is marked as not required.
74
86
  *
75
87
  * @param targetName - The property name containing the target object
76
88
  * @param name - The attribute name within the target object
@@ -80,10 +92,14 @@ export default class ObjectSerializerBuilder {
80
92
  * (e.g., delegating `'profile', 'avatarUrl'` with `as: 'avatar'` outputs the value
81
93
  * under `avatar`)
82
94
  * - `default` - Value to use when the target object or its attribute is null/undefined
95
+ * - `optional` - Set to `true` to mark the value as nullable in the OpenAPI schema
96
+ * (wraps the type in `anyOf: [schema, { type: 'null' }]`). OpenAPI-only — the
97
+ * key is still rendered (as `null`)
83
98
  * - `precision` - Round decimal values to the specified number of decimal places (0–9)
84
99
  * during rendering; does not affect the OpenAPI shape
85
- * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
86
- * when omitted, attributes are required by default
100
+ * - `required` - Set to `false` to omit the key from the rendered output when the
101
+ * resolved value is `undefined`, and to mark the field as not required in the
102
+ * OpenAPI schema
87
103
  * @returns The serializer builder for method chaining
88
104
  *
89
105
  * @example
@@ -1,7 +1,18 @@
1
- import { SpawnOptions } from 'child_process';
1
+ import { SpawnOptions as NodeSpawnOptions } 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 type SpawnOptions = Omit<NodeSpawnOptions, 'shell'> & {
6
+ onStdout?: (str: string) => void;
7
+ /**
8
+ * Argv elements passed to the child as separate arguments. Defaults to `[]`.
9
+ * Each entry is passed literally — shell meta-characters (`$`, backticks,
10
+ * `&&`, spaces, etc.) inside any element are not interpreted by a shell.
11
+ * This is the right shape for any caller that interpolates a path or
12
+ * credential.
13
+ */
14
+ args?: string[];
15
+ };
5
16
  export declare const CLI_INDENT = " ";
6
17
  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 - encrypted:\n encrypted text (used in conjunction with the @deco.Encrypted decorator)\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 precision,scale 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";
7
18
  export default class DreamCLI {
@@ -56,9 +67,51 @@ export default class DreamCLI {
56
67
  seedDb: () => Promise<void> | void;
57
68
  onSync: () => Promise<void> | void;
58
69
  }): void;
59
- static spawn(command: string, opts?: SpawnOptions & {
60
- onStdout?: (str: string) => void;
61
- }): Promise<unknown>;
70
+ /**
71
+ * Run a developer-authored CLI command. Always runs in argv form (the
72
+ * underlying child_process `spawn` is called with `shell: false`):
73
+ * `command` is exec'd literally and `opts.args` are passed as separate
74
+ * argv elements. Shell-form invocation is intentionally not supported —
75
+ * there is no caller that needs `&&`-chaining, globs, or other shell
76
+ * features that can't be expressed as argv.
77
+ *
78
+ * For backward compatibility, `command` may contain implicit args
79
+ * separated by whitespace (e.g. `'pnpm psy sync'`); the leading token
80
+ * becomes the program and the rest are split out and prepended to any
81
+ * `opts.args` so the original argument order is preserved:
82
+ *
83
+ * DreamCLI.spawn('pnpm psy sync')
84
+ * → spawn('pnpm', ['psy', 'sync'])
85
+ *
86
+ * DreamCLI.spawn('pnpm psy', { args: ['sync', '--flag'] })
87
+ * → spawn('pnpm', ['psy', 'sync', '--flag'])
88
+ *
89
+ * ## Threat model (R-015)
90
+ *
91
+ * For dev-time CLI glue only (scaffolding, doc generation, type sync).
92
+ * **No runtime HTTP request input ever reaches this function.** Inputs
93
+ * are constant literals or composed from developer-supplied config
94
+ * (package.json scripts, CLI argv, scaffold templates) — never from
95
+ * runtime request input or any other untrusted external source.
96
+ *
97
+ * Argv-form is the safe choice for any caller that interpolates a
98
+ * config value, path, or credential: a database password containing
99
+ * `$` or backticks is passed literally to the child rather than
100
+ * interpreted by a shell.
101
+ *
102
+ * ## Layered defense
103
+ *
104
+ * Primary gate: every caller restricts spawn use to dev/test code paths
105
+ * (CLI commands, the dev watcher, scaffold-time code generators,
106
+ * generated `cli:sync` initializers wrapped in
107
+ * `if (AppEnv.isDevelopmentOrTest)`).
108
+ *
109
+ * Backstop: throws `SspawnRequiresDevelopmentOrTest` when `NODE_ENV` is
110
+ * anything other than `development` or `test`. Checking
111
+ * `!isDevelopmentOrTest` (rather than `isProduction`) means staging-style
112
+ * envs and any unforeseen NODE_ENV value also fail closed.
113
+ */
114
+ static spawn(command: string, opts?: SpawnOptions): Promise<void>;
62
115
  static get logger(): DreamCliLogger;
63
116
  private static _logger;
64
117
  }
@@ -1 +1,2 @@
1
- export default function ReplicaSafe(): ClassDecorator;
1
+ import Dream from '../../Dream.js';
2
+ export default function ReplicaSafe(): (target: typeof Dream) => void;
@@ -1,3 +1,3 @@
1
1
  import Dream from '../../Dream.js';
2
2
  export declare const STI_SCOPE_NAME = "dream:STI";
3
- export default function STI(dreamClass: typeof Dream): ClassDecorator;
3
+ export default function STI(dreamClass: typeof Dream): (stiChildClass: typeof Dream) => void;
@@ -1,3 +1,4 @@
1
+ import Dream from '../../Dream.js';
1
2
  export declare const SOFT_DELETE_SCOPE_NAME = "dream:SoftDelete";
2
3
  /**
3
4
  * Instructs the model to set a timestamp when deleting,
@@ -34,4 +35,4 @@ export declare const SOFT_DELETE_SCOPE_NAME = "dream:SoftDelete";
34
35
  * }
35
36
  * }
36
37
  */
37
- export default function SoftDelete(): ClassDecorator;
38
+ export default function SoftDelete(): (target: typeof Dream) => void;
@@ -1,5 +1,7 @@
1
+ import type { ConnectionOptions as TlsConnectionOptions } from 'node:tls';
1
2
  import { DeleteQueryBuilder, Kysely, Transaction as KyselyTransaction, OrderByItemBuilder, SelectQueryBuilder, UpdateQueryBuilder } from 'kysely';
2
3
  import { DialectProviderCb } from '../../db/DreamDbConnection.js';
4
+ import { SingleDbCredential } from '../../dream-app/index.js';
3
5
  import Dream from '../../Dream.js';
4
6
  import { SchemaBuilderInformationSchemaRow } from '../../helpers/cli/ASTBuilder.js';
5
7
  import { AssociationStatement } from '../../types/associations/shared.js';
@@ -467,3 +469,18 @@ export default class KyselyQueryDriver<DreamInstance extends Dream> extends Quer
467
469
  private applyOnePreload;
468
470
  private _applyOnePreload;
469
471
  }
472
+ /**
473
+ * Resolve the value passed to `pg.Pool`'s `ssl` field for a given credential.
474
+ *
475
+ * Precedence:
476
+ * 1. If `connectionConf.ssl` is set (boolean or `tls.ConnectionOptions`),
477
+ * pass it straight through to `pg`. This is the verified-TLS path —
478
+ * apps configure `{ rejectUnauthorized: true, ca: <bundle> }` for
479
+ * authenticated TLS against a private PKI, or `true` to use Node's
480
+ * default verification against the system CA store.
481
+ * 2. Else if `connectionConf.useSsl` is `true`, fall back to
482
+ * `{ rejectUnauthorized: false }` — encrypted but **not** authenticated.
483
+ * Preserved for back-compat; new code should set `ssl` explicitly.
484
+ * 3. Else disable TLS.
485
+ */
486
+ export declare function resolvePostgresSsl(connectionConf: SingleDbCredential): boolean | TlsConnectionOptions;
@@ -1,4 +1,5 @@
1
1
  import { CompiledQuery } from 'kysely';
2
+ import type { ConnectionOptions as TlsConnectionOptions } from 'node:tls';
2
3
  import { Context } from 'node:vm';
3
4
  import Dream from '../Dream.js';
4
5
  import QueryDriverBase from '../dream/QueryDriver/Base.js';
@@ -209,7 +210,30 @@ export interface SingleDbCredential {
209
210
  host: string;
210
211
  name: string;
211
212
  port: number;
212
- useSsl: boolean;
213
+ /**
214
+ * @deprecated Use `ssl` instead.
215
+ *
216
+ * The legacy boolean opt-in for Postgres TLS. When `true` (and `ssl` is not
217
+ * set), Dream connects with `{ rejectUnauthorized: false }` — TLS is on but
218
+ * the server certificate is not verified. The new `ssl` field accepts a
219
+ * full `tls.ConnectionOptions` object so callers can opt into verified TLS
220
+ * (`ssl: { rejectUnauthorized: true, ca: readFileSync('ca.pem') }`) or use
221
+ * Node's defaults (`ssl: true`). This field is preserved for back-compat
222
+ * and will be removed in a future major version.
223
+ */
224
+ useSsl?: boolean;
225
+ /**
226
+ * Optional explicit TLS configuration passed straight through to `pg.Pool`'s
227
+ * `ssl` field. Takes precedence over the deprecated `useSsl` when provided.
228
+ *
229
+ * - `true` lets `pg` use Node's defaults (verified TLS against the system CA).
230
+ * - `false` disables TLS.
231
+ * - An object is `tls.ConnectionOptions` — set `rejectUnauthorized: true` plus
232
+ * a `ca` bundle for verified TLS against a private PKI; set
233
+ * `rejectUnauthorized: false` only if you accept opportunistic-TLS-without-
234
+ * authentication (the historical default produced by `useSsl: true` alone).
235
+ */
236
+ ssl?: boolean | TlsConnectionOptions;
213
237
  }
214
238
  export type DreamLogger = {
215
239
  info: (...args: any[]) => void;
@@ -1,2 +1,2 @@
1
1
  import { EncryptAESAlgorithm } from '../../index.js';
2
- export default function decryptAESGCM<RetType>(algorithm: EncryptAESAlgorithm, encrypted: string, key: string): RetType | null;
2
+ export default function decryptAESGCM<RetType>(algorithm: EncryptAESAlgorithm, encrypted: string, key: string): RetType;
@@ -1,7 +1,66 @@
1
1
  export default class Encrypt {
2
2
  static encrypt(data: any, { algorithm, key }: EncryptOptions): string;
3
+ /**
4
+ * Decrypts a value previously produced by {@link Encrypt.encrypt}.
5
+ *
6
+ * Behavior depends on whether `legacyOpts` is provided:
7
+ *
8
+ * **Two-arg form** (no rotation):
9
+ * - `null`/`undefined` input returns `null`.
10
+ * - Cipher op / auth tag / payload-shape failure throws `DecryptionError`.
11
+ * - Successful decrypt with non-JSON plaintext throws `DecryptionParseError`.
12
+ *
13
+ * **Three-arg form** (rotation): tries the current key first; on
14
+ * `DecryptionError` falls back to the legacy key. If both fail, throws
15
+ * `DecryptionWithRotationError` carrying both per-key errors. A
16
+ * `DecryptionParseError` from the current key is **not** retried — the
17
+ * cipher already matched, so a parse failure means the encrypted format
18
+ * is wrong (an app bug), not a wrong key.
19
+ *
20
+ * `MissingEncryptionKey` propagates from either form when a key is missing.
21
+ *
22
+ * @throws MissingEncryptionKey
23
+ * @throws DecryptionError
24
+ * @throws DecryptionParseError
25
+ * @throws DecryptionWithRotationError
26
+ */
3
27
  static decrypt<RetType>(encrypted: string, { algorithm, key }: DecryptOptions, legacyOpts?: DecryptOptions): RetType | null;
4
28
  private static attemptDecryptionWithLegacyKeys;
29
+ /**
30
+ * Generates a base64-encoded random key suitable for the given algorithm.
31
+ *
32
+ * ## Rotation workflow
33
+ *
34
+ * 1. Generate a new key: `const newKey = Encrypt.generateKey('aes-256-gcm')`.
35
+ * 2. Configure rotation by setting both `current` and `legacy`. For
36
+ * encrypted columns: `dreamApp.set('encryption', { columns: { current:
37
+ * { algorithm: 'aes-256-gcm', key: newKey }, legacy: { algorithm:
38
+ * 'aes-256-gcm', key: oldKey } } })`. For cookies, use the equivalent
39
+ * shape under `psychicApp.set('encryption', { cookies: { current,
40
+ * legacy } })`.
41
+ * 3. Deploy. New encryptions use `current`; existing ciphertext continues
42
+ * to decrypt via `legacy` fallback.
43
+ * 4. For cookies, wait at least the cookie `maxAge` so all in-flight
44
+ * cookies have either expired or been re-issued under the new key. For
45
+ * `@Encrypted` columns, re-encrypt every existing row under the new
46
+ * key (read each row and write it back; the setter re-encrypts with
47
+ * `current`).
48
+ * 5. Drop `legacy` from config and deploy again.
49
+ *
50
+ * ## When to rotate
51
+ *
52
+ * - On a scheduled cadence (90–180 days is a reasonable policy default).
53
+ * - Incident response: leaked env file, departing employee with key
54
+ * access, or any suspected key compromise.
55
+ *
56
+ * ## How long to keep `legacy`
57
+ *
58
+ * - For cookies: at least the cookie `maxAge`, so in-flight sessions are
59
+ * not forced to re-authenticate.
60
+ * - For `@Encrypted` columns: until every existing row has been
61
+ * re-encrypted under the new key. Dropping `legacy` early will cause
62
+ * `DecryptionWithRotationError` on any not-yet-rewritten row.
63
+ */
5
64
  static generateKey(algorithm: EncryptAlgorithm): string;
6
65
  static validateKey(base64EncodedKey: string, algorithm: EncryptAlgorithm): boolean;
7
66
  }
@@ -0,0 +1,5 @@
1
+ export default class SspawnRequiresDevelopmentOrTest extends Error {
2
+ private command;
3
+ constructor(command: string);
4
+ get message(): string;
5
+ }
@@ -0,0 +1,3 @@
1
+ export default class DecryptionError extends Error {
2
+ get message(): string;
3
+ }
@@ -0,0 +1,3 @@
1
+ export default class DecryptionParseError extends Error {
2
+ get message(): string;
3
+ }
@@ -0,0 +1,7 @@
1
+ import DecryptionError from './DecryptionError.js';
2
+ export default class DecryptionWithRotationError extends Error {
3
+ readonly currentKeyError: DecryptionError;
4
+ readonly legacyKeyError: DecryptionError;
5
+ constructor(currentKeyError: DecryptionError, legacyKeyError: DecryptionError);
6
+ get message(): string;
7
+ }
@@ -56,17 +56,38 @@ declare const ops: {
56
56
  * Creates an `OpsStatement` using the SQL `LIKE` operator for case-sensitive pattern matching.
57
57
  * Use `%` as a wildcard in the pattern string.
58
58
  *
59
+ * **User-supplied patterns:** the value is bound as a parameter (no SQL
60
+ * injection), but `%`, `_`, and `\` in the bound value are still interpreted
61
+ * as wildcards by the SQL engine. Wrap user input with `ops.like.escape`
62
+ * if you need literal matching:
63
+ *
64
+ * User.where({ name: ops.like(`%${ops.like.escape(query)}%`) })
65
+ *
66
+ * `ops.like.escape` escapes the three metacharacters that PostgreSQL's
67
+ * `LIKE` / `ILIKE` operators treat as wildcards (`\`, `%`, `_`) so the
68
+ * returned string matches literally. The same helper applies to all four
69
+ * LIKE-family ops (`like`, `ilike`, `not.like`, `not.ilike`); it is exposed
70
+ * here as the canonical entry point. If a caller uses `ESCAPE '...'` with
71
+ * a non-default character this helper will not be appropriate.
72
+ *
59
73
  * @param like - The pattern string (e.g. `'%hello%'`).
60
74
  * @returns An `OpsStatement` using the `like` operator.
61
75
  *
62
76
  * @example
63
77
  * User.where({ name: ops.like('%alice%') })
64
78
  */
65
- like: (like: string) => OpsStatement<"like", string, undefined>;
79
+ like: ((like: string) => OpsStatement<"like", string, undefined>) & {
80
+ escape: (input: string) => string;
81
+ };
66
82
  /**
67
83
  * Creates an `OpsStatement` using the SQL `ILIKE` operator for case-insensitive pattern matching.
68
84
  * Use `%` as a wildcard in the pattern string.
69
85
  *
86
+ * **User-supplied patterns:** the value is bound as a parameter (no SQL
87
+ * injection), but `%`, `_`, and `\` in the bound value are still interpreted
88
+ * as wildcards by the SQL engine. Wrap user input with `ops.like.escape`
89
+ * if you need literal matching.
90
+ *
70
91
  * @param ilike - The pattern string (e.g. `'%hello%'`).
71
92
  * @returns An `OpsStatement` using the `ilike` operator.
72
93
  *
@@ -208,6 +229,9 @@ declare const ops: {
208
229
  /**
209
230
  * Creates an `OpsStatement` using the `NOT LIKE` operator for case-sensitive pattern exclusion.
210
231
  *
232
+ * **User-supplied patterns:** see the note on `ops.like`. Wrap user input
233
+ * with `ops.like.escape` for literal matching.
234
+ *
211
235
  * @param like - The pattern string (e.g. `'%spam%'`).
212
236
  * @returns An `OpsStatement` using the `not like` operator.
213
237
  *
@@ -218,6 +242,9 @@ declare const ops: {
218
242
  /**
219
243
  * Creates an `OpsStatement` using the `NOT ILIKE` operator for case-insensitive pattern exclusion.
220
244
  *
245
+ * **User-supplied patterns:** see the note on `ops.ilike`. Wrap user input
246
+ * with `ops.like.escape` for literal matching.
247
+ *
221
248
  * @param ilike - The pattern string (e.g. `'%spam%'`).
222
249
  * @returns An `OpsStatement` using the `not ilike` operator.
223
250
  *
@@ -59,14 +59,27 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
59
59
  /**
60
60
  * Includes an attribute from a nested object in the serialized output.
61
61
  *
62
- * Accesses `targetName.name` on the data object. If the target object
63
- * or the delegated attribute is null/undefined, the `default` option value
64
- * will be used if provided.
62
+ * Accesses `targetName.name` on the data object. When the target object or the
63
+ * delegated attribute is null/undefined, the resolution order is:
64
+ * 1. `default` if provided renders the default value
65
+ * 2. `required: false` → omits the key entirely from the rendered output
66
+ * 3. otherwise → renders `null`
65
67
  *
66
68
  * When the target is a Dream model, OpenAPI types may be automatically inferred
67
69
  * for standard database columns. For json/jsonb columns or non-Dream targets,
68
70
  * the `openapi` option is required.
69
71
  *
72
+ * `optional` and `required` are not aliases — they encode different things and can
73
+ * be used together:
74
+ * - `optional: true` is an OpenAPI-only marker (no runtime effect). It declares
75
+ * that the rendered value may be `null` (e.g. when delegating through a HasOne
76
+ * that may be missing). Use this when you want the key to always be present
77
+ * but the value to be nullable in the schema.
78
+ * - `required: false` affects both runtime and OpenAPI. At runtime, when the
79
+ * resolved value is `undefined` (which includes the case of a missing delegated
80
+ * association with no `default`), the key is omitted from the rendered output.
81
+ * In OpenAPI, the field is marked as not required.
82
+ *
70
83
  * @param targetName - The property name containing the target object (e.g., an association name)
71
84
  * @param name - The attribute name within the target object
72
85
  * @param options - Configuration options:
@@ -74,16 +87,22 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
74
87
  * (e.g., delegating `'user', 'email'` with `as: 'userEmail'` outputs the value
75
88
  * under `userEmail`)
76
89
  * - `default` - Value to use when the target object or its attribute is null/undefined
90
+ * (not available when delegating to a `'type'` STI discriminator column: substituting
91
+ * a discriminator string when the association is actually missing produces a response
92
+ * indistinguishable from "association present with that type," which is misleading)
77
93
  * - `openapi` - OpenAPI schema definition; required for non-Dream targets and json/jsonb
78
94
  * columns, optional for standard Dream columns (where types are inferred)
79
- * - `optional` - Set to `true` to indicate the value can be null in the OpenAPI schema
80
- * (wraps the type in `anyOf: [schema, { type: 'null' }]`). For Dream models, this is
81
- * auto-inferred from optional BelongsTo associations. Use this when delegating through
82
- * a HasOne or other nullable association.
95
+ * - `optional` - Set to `true` to mark the value as nullable in the OpenAPI schema
96
+ * (wraps the type in `anyOf: [schema, { type: 'null' }]`). OpenAPI-only the
97
+ * key is still rendered (as `null`). For Dream models, this is auto-inferred
98
+ * from optional BelongsTo associations. Use this when delegating through a
99
+ * HasOne or other nullable association.
83
100
  * - `precision` - Round decimal values to the specified number of decimal places (0–9)
84
- * during rendering; does not affect the OpenAPI shape
85
- * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
86
- * when omitted, attributes are required by default
101
+ * during rendering; does not affect the OpenAPI shape (not available when delegating
102
+ * to a `'type'` STI discriminator column, which is always a string enum)
103
+ * - `required` - Set to `false` to omit the key from the rendered output when the
104
+ * resolved value is `undefined` (including a missing delegated association with
105
+ * no `default`), and to mark the field as not required in the OpenAPI schema
87
106
  * @returns The serializer builder for method chaining
88
107
  *
89
108
  * @example
@@ -106,9 +125,18 @@ export default class DreamSerializerBuilder<DataTypeForOpenapi extends typeof Dr
106
125
  */
107
126
  delegatedAttribute<ProvidedModelType = undefined, ProvidedTargetName extends ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude> = ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude>, ActualDataType extends ProvidedModelType extends undefined ? InstanceType<DataTypeForOpenapi> : ProvidedModelType = ProvidedModelType extends undefined ? InstanceType<DataTypeForOpenapi> : ProvidedModelType, TargetName extends ProvidedTargetName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedTargetName & keyof ActualDataType = ProvidedTargetName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedTargetName & keyof ActualDataType, AssociatedModelType = Exclude<ActualDataType[TargetName], null>, TargetAttributeName extends AssociatedModelType extends object ? Exclude<keyof AssociatedModelType, DreamPropertiesToExclude> & string : never = AssociatedModelType extends object ? Exclude<keyof AssociatedModelType, DreamPropertiesToExclude> & string : never>(targetName: TargetName, name: TargetAttributeName, options?: AssociatedModelType extends Dream ? TargetAttributeName extends NonJsonDreamColumnNames<AssociatedModelType> & keyof AssociatedModelType & 'type' ? AutomaticSerializerAttributeOptionsForType & {
108
127
  optional?: boolean;
109
- } : TargetAttributeName extends DreamVirtualColumns<AssociatedModelType>[number] ? SerializerAttributeOptionsForVirtualColumn : TargetAttributeName extends NonJsonDreamColumnNames<AssociatedModelType> & keyof AssociatedModelType & string ? (AutomaticSerializerAttributeOptions & {
128
+ required?: false;
129
+ } : TargetAttributeName extends DreamVirtualColumns<AssociatedModelType>[number] ? SerializerAttributeOptionsForVirtualColumn & {
130
+ optional?: boolean;
131
+ } : TargetAttributeName extends NonJsonDreamColumnNames<AssociatedModelType> & keyof AssociatedModelType & string ? (AutomaticSerializerAttributeOptions & {
110
132
  optional?: boolean;
111
- }) | NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption : NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption : NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption): this;
133
+ }) | (NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption & {
134
+ optional?: boolean;
135
+ }) : NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption & {
136
+ optional?: boolean;
137
+ } : NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption & {
138
+ optional?: boolean;
139
+ }): this;
112
140
  /**
113
141
  * Includes a computed value in the serialized output.
114
142
  *
@@ -63,8 +63,20 @@ export default class ObjectSerializerBuilder<MaybeNullDataType extends object |
63
63
  * Includes an attribute from a nested object in the serialized output.
64
64
  *
65
65
  * Accesses `targetName.name` on the data object. The `openapi` option is always
66
- * required. If the target object or the delegated attribute is null/undefined,
67
- * the `default` option value will be used if provided.
66
+ * required. When the target object or the delegated attribute is null/undefined,
67
+ * the resolution order is:
68
+ * 1. `default` if provided → renders the default value
69
+ * 2. `required: false` → omits the key entirely from the rendered output
70
+ * 3. otherwise → renders `null`
71
+ *
72
+ * `optional` and `required` are not aliases — they encode different things and can
73
+ * be used together:
74
+ * - `optional: true` is an OpenAPI-only marker (no runtime effect). It declares
75
+ * that the rendered value may be `null`. Use this when you want the key to always
76
+ * be present but the value to be nullable in the schema.
77
+ * - `required: false` affects both runtime and OpenAPI. At runtime, when the
78
+ * resolved value is `undefined`, the key is omitted from the rendered output.
79
+ * In OpenAPI, the field is marked as not required.
68
80
  *
69
81
  * @param targetName - The property name containing the target object
70
82
  * @param name - The attribute name within the target object
@@ -74,10 +86,14 @@ export default class ObjectSerializerBuilder<MaybeNullDataType extends object |
74
86
  * (e.g., delegating `'profile', 'avatarUrl'` with `as: 'avatar'` outputs the value
75
87
  * under `avatar`)
76
88
  * - `default` - Value to use when the target object or its attribute is null/undefined
89
+ * - `optional` - Set to `true` to mark the value as nullable in the OpenAPI schema
90
+ * (wraps the type in `anyOf: [schema, { type: 'null' }]`). OpenAPI-only — the
91
+ * key is still rendered (as `null`)
77
92
  * - `precision` - Round decimal values to the specified number of decimal places (0–9)
78
93
  * during rendering; does not affect the OpenAPI shape
79
- * - `required` - Set to `false` to mark the attribute as optional in the OpenAPI schema;
80
- * when omitted, attributes are required by default
94
+ * - `required` - Set to `false` to omit the key from the rendered output when the
95
+ * resolved value is `undefined`, and to mark the field as not required in the
96
+ * OpenAPI schema
81
97
  * @returns The serializer builder for method chaining
82
98
  *
83
99
  * @example
@@ -100,7 +116,9 @@ export default class ObjectSerializerBuilder<MaybeNullDataType extends object |
100
116
  * })
101
117
  * ```
102
118
  */
103
- delegatedAttribute<ProvidedModelType = undefined, ProvidedAttributeName extends ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude> = ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude>, ActualDataType extends ProvidedModelType extends undefined ? DataType : ProvidedModelType = ProvidedModelType extends undefined ? DataType : ProvidedModelType, TargetName extends ProvidedAttributeName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedAttributeName & keyof ActualDataType = ProvidedAttributeName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedAttributeName & keyof ActualDataType, TargetObject extends ActualDataType[TargetName] = ActualDataType[TargetName], AttributeName extends TargetObject extends object ? Exclude<keyof TargetObject, DreamPropertiesToExclude> & string : never = TargetObject extends object ? Exclude<keyof TargetObject, DreamPropertiesToExclude> & string : never>(targetName: TargetName, name: AttributeName, options: NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption): this;
119
+ delegatedAttribute<ProvidedModelType = undefined, ProvidedAttributeName extends ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude> = ProvidedModelType extends undefined ? undefined : Exclude<keyof ProvidedModelType, DreamPropertiesToExclude>, ActualDataType extends ProvidedModelType extends undefined ? DataType : ProvidedModelType = ProvidedModelType extends undefined ? DataType : ProvidedModelType, TargetName extends ProvidedAttributeName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedAttributeName & keyof ActualDataType = ProvidedAttributeName extends undefined ? Exclude<keyof ActualDataType, DreamPropertiesToExclude> : ProvidedAttributeName & keyof ActualDataType, TargetObject extends ActualDataType[TargetName] = ActualDataType[TargetName], AttributeName extends TargetObject extends object ? Exclude<keyof TargetObject, DreamPropertiesToExclude> & string : never = TargetObject extends object ? Exclude<keyof TargetObject, DreamPropertiesToExclude> & string : never>(targetName: TargetName, name: AttributeName, options: NonAutomaticSerializerAttributeOptionsWithPossibleDecimalRenderOption & {
120
+ optional?: boolean;
121
+ }): this;
104
122
  /**
105
123
  * Includes a computed value in the serialized output.
106
124
  *