@rvoh/dream 2.9.2 → 2.11.1
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.
- package/dist/cjs/src/cli/index.js +16 -3
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +7 -8
- package/dist/cjs/src/dream/QueryDriver/helpers/kysely/checkForNeedToBeRunMigrations.js +1 -1
- package/dist/cjs/src/dream/QueryDriver/helpers/kysely/runMigration.js +1 -1
- package/dist/cjs/src/dream-app/index.js +14 -6
- package/dist/cjs/src/encrypt/index.js +5 -5
- package/dist/cjs/src/errors/dream-app/MissingDbSslDirective.js +29 -0
- package/dist/cjs/src/errors/encrypt/{DecryptionWithRotationError.js → DecryptionRotationError.js} +1 -1
- package/dist/cjs/src/helpers/cli/generateDreamContent.js +43 -17
- package/dist/cjs/src/helpers/cli/generateFactoryContent.js +34 -7
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +30 -4
- package/dist/cjs/src/helpers/cli/parseAttribute.js +61 -0
- package/dist/cjs/src/package-exports/errors.js +3 -0
- package/dist/esm/src/cli/index.js +16 -3
- package/dist/esm/src/dream/QueryDriver/Kysely.js +7 -8
- package/dist/esm/src/dream/QueryDriver/helpers/kysely/checkForNeedToBeRunMigrations.js +1 -1
- package/dist/esm/src/dream/QueryDriver/helpers/kysely/runMigration.js +1 -1
- package/dist/esm/src/dream-app/index.js +14 -6
- package/dist/esm/src/encrypt/index.js +5 -5
- package/dist/esm/src/errors/dream-app/MissingDbSslDirective.js +29 -0
- package/dist/esm/src/errors/encrypt/{DecryptionWithRotationError.js → DecryptionRotationError.js} +1 -1
- package/dist/esm/src/helpers/cli/generateDreamContent.js +43 -17
- package/dist/esm/src/helpers/cli/generateFactoryContent.js +34 -7
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +30 -4
- package/dist/esm/src/helpers/cli/parseAttribute.js +61 -0
- package/dist/esm/src/package-exports/errors.js +3 -0
- package/dist/types/src/cli/index.d.ts +1 -1
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +8 -9
- package/dist/types/src/dream-app/index.d.ts +23 -15
- package/dist/types/src/encrypt/index.d.ts +3 -3
- package/dist/types/src/errors/dream-app/MissingDbSslDirective.d.ts +6 -0
- package/dist/types/src/errors/encrypt/{DecryptionWithRotationError.d.ts → DecryptionRotationError.d.ts} +1 -1
- package/dist/types/src/helpers/cli/generateDreamContent.d.ts +6 -2
- package/dist/types/src/helpers/cli/parseAttribute.d.ts +64 -0
- package/dist/types/src/package-exports/errors.d.ts +3 -0
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +9 -9
- package/docs/classes/db.KyselyQueryDriver.html +32 -32
- package/docs/classes/db.PostgresQueryDriver.html +33 -33
- package/docs/classes/db.QueryDriverBase.html +31 -31
- package/docs/classes/errors.CheckConstraintViolation.html +3 -3
- package/docs/classes/errors.ColumnOverflow.html +3 -3
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +3 -3
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/errors.DecryptionError.html +33 -0
- package/docs/classes/errors.DecryptionParseError.html +33 -0
- package/docs/classes/errors.DecryptionRotationError.html +35 -0
- package/docs/classes/errors.GlobalNameNotSet.html +3 -3
- package/docs/classes/errors.InvalidCalendarDate.html +2 -2
- package/docs/classes/errors.InvalidClockTime.html +2 -2
- package/docs/classes/errors.InvalidClockTimeTz.html +2 -2
- package/docs/classes/errors.InvalidDateTime.html +2 -2
- package/docs/classes/errors.MissingSerializersDefinition.html +3 -3
- package/docs/classes/errors.NonLoadedAssociation.html +3 -3
- package/docs/classes/errors.NotNullViolation.html +3 -3
- package/docs/classes/errors.RecordNotFound.html +3 -3
- package/docs/classes/errors.ValidationError.html +3 -3
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +19 -19
- package/docs/classes/index.Dream.html +118 -118
- package/docs/classes/index.DreamApp.html +5 -5
- package/docs/classes/index.DreamTransaction.html +2 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +56 -56
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +7 -7
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +6 -6
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +1 -1
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/modules/errors.html +1 -1
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +5 -5
|
@@ -6,6 +6,7 @@ import Encrypt from '../encrypt/index.js';
|
|
|
6
6
|
import DreamAppInitMissingCallToLoadModels from '../errors/dream-app/DreamAppInitMissingCallToLoadModels.js';
|
|
7
7
|
import DreamAppInitMissingMissingProjectRoot from '../errors/dream-app/DreamAppInitMissingMissingProjectRoot.js';
|
|
8
8
|
import DreamAppInitMissingPackageManager from '../errors/dream-app/DreamAppInitMissingPackageManager.js';
|
|
9
|
+
import MissingDbSslDirective from '../errors/dream-app/MissingDbSslDirective.js';
|
|
9
10
|
import autogeneratedFileDisclaimer from '../helpers/cli/autoGeneratedFileDisclaimer.js';
|
|
10
11
|
import modelClassNameFrom from '../helpers/cli/modelClassNameFrom.js';
|
|
11
12
|
import EnvInternal from '../helpers/EnvInternal.js';
|
|
@@ -345,14 +346,16 @@ A new key can also be generated from the CLI:
|
|
|
345
346
|
case 'bypassDeprecationChecks':
|
|
346
347
|
this._bypassDeprecationChecks = options;
|
|
347
348
|
break;
|
|
348
|
-
case 'db':
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
349
|
+
case 'db': {
|
|
350
|
+
const connectionName = typeof options === 'string' ? options : 'default';
|
|
351
|
+
const credentialOptions = (typeof options === 'string' ? secondaryOptions : options);
|
|
352
|
+
assertDbCredentialTlsDirective(credentialOptions.primary, connectionName, 'primary');
|
|
353
|
+
if (credentialOptions.replica) {
|
|
354
|
+
assertDbCredentialTlsDirective(credentialOptions.replica, connectionName, 'replica');
|
|
354
355
|
}
|
|
356
|
+
this._dbCredentials[connectionName] = credentialOptions;
|
|
355
357
|
break;
|
|
358
|
+
}
|
|
356
359
|
case 'encryption':
|
|
357
360
|
this._encryption = options;
|
|
358
361
|
break;
|
|
@@ -412,6 +415,11 @@ A new key can also be generated from the CLI:
|
|
|
412
415
|
}
|
|
413
416
|
}
|
|
414
417
|
}
|
|
418
|
+
function assertDbCredentialTlsDirective(credential, connectionName, credentialKey) {
|
|
419
|
+
if (credential.ssl === undefined && !credential.useSsl) {
|
|
420
|
+
throw new MissingDbSslDirective(connectionName, credentialKey);
|
|
421
|
+
}
|
|
422
|
+
}
|
|
415
423
|
function loggerArgToString(arg) {
|
|
416
424
|
if (typeof arg === 'string')
|
|
417
425
|
return arg;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import DecryptionError from '../errors/encrypt/DecryptionError.js';
|
|
2
|
-
import
|
|
2
|
+
import DecryptionRotationError from '../errors/encrypt/DecryptionRotationError.js';
|
|
3
3
|
import MissingEncryptionKey from '../errors/encrypt/MissingEncryptionKey.js';
|
|
4
4
|
import decryptAESGCM from './algorithms/aes-gcm/decryptAESGCM.js';
|
|
5
5
|
import encryptAESGCM from './algorithms/aes-gcm/encryptAESGCM.js';
|
|
@@ -34,7 +34,7 @@ export default class Encrypt {
|
|
|
34
34
|
*
|
|
35
35
|
* **Three-arg form** (rotation): tries the current key first; on
|
|
36
36
|
* `DecryptionError` falls back to the legacy key. If both fail, throws
|
|
37
|
-
* `
|
|
37
|
+
* `DecryptionRotationError` carrying both per-key errors. A
|
|
38
38
|
* `DecryptionParseError` from the current key is **not** retried — the
|
|
39
39
|
* cipher already matched, so a parse failure means the encrypted format
|
|
40
40
|
* is wrong (an app bug), not a wrong key.
|
|
@@ -44,7 +44,7 @@ export default class Encrypt {
|
|
|
44
44
|
* @throws MissingEncryptionKey
|
|
45
45
|
* @throws DecryptionError
|
|
46
46
|
* @throws DecryptionParseError
|
|
47
|
-
* @throws
|
|
47
|
+
* @throws DecryptionRotationError
|
|
48
48
|
*/
|
|
49
49
|
static decrypt(encrypted, { algorithm, key }, legacyOpts) {
|
|
50
50
|
if (legacyOpts)
|
|
@@ -81,7 +81,7 @@ export default class Encrypt {
|
|
|
81
81
|
catch (err) {
|
|
82
82
|
if (!(err instanceof DecryptionError))
|
|
83
83
|
throw err;
|
|
84
|
-
throw new
|
|
84
|
+
throw new DecryptionRotationError(currentKeyError, err);
|
|
85
85
|
}
|
|
86
86
|
}
|
|
87
87
|
/**
|
|
@@ -117,7 +117,7 @@ export default class Encrypt {
|
|
|
117
117
|
* not forced to re-authenticate.
|
|
118
118
|
* - For `@Encrypted` columns: until every existing row has been
|
|
119
119
|
* re-encrypted under the new key. Dropping `legacy` early will cause
|
|
120
|
-
* `
|
|
120
|
+
* `DecryptionRotationError` on any not-yet-rewritten row.
|
|
121
121
|
*/
|
|
122
122
|
static generateKey(algorithm) {
|
|
123
123
|
switch (algorithm) {
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
export default class MissingDbSslDirective extends Error {
|
|
2
|
+
connectionName;
|
|
3
|
+
credentialKey;
|
|
4
|
+
constructor(connectionName, credentialKey) {
|
|
5
|
+
super();
|
|
6
|
+
this.connectionName = connectionName;
|
|
7
|
+
this.credentialKey = credentialKey;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
return `
|
|
11
|
+
DreamApp refused to register a db credential without an explicit TLS
|
|
12
|
+
directive. Every \`SingleDbCredential\` passed to \`app.set('db', ...)\`
|
|
13
|
+
must set one of:
|
|
14
|
+
|
|
15
|
+
ssl: { rejectUnauthorized: true } // verified TLS (system CA)
|
|
16
|
+
ssl: { rejectUnauthorized: true, ca: <pem> } // verified TLS (private CA)
|
|
17
|
+
ssl: { rejectUnauthorized: false } // unverified TLS
|
|
18
|
+
ssl: false // TLS disabled
|
|
19
|
+
useSsl: true // legacy, deprecated
|
|
20
|
+
|
|
21
|
+
Omitting the directive used to silently disable TLS. Throwing here
|
|
22
|
+
turns the safety question into a deliberate decision at the call
|
|
23
|
+
site, so a credential cannot reach production with TLS off by accident.
|
|
24
|
+
|
|
25
|
+
connection: ${this.connectionName}
|
|
26
|
+
credential: ${this.credentialKey}
|
|
27
|
+
`;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -7,6 +7,7 @@ import absoluteDreamPath from '../path/absoluteDreamPath.js';
|
|
|
7
7
|
import snakeify from '../snakeify.js';
|
|
8
8
|
import standardizeFullyQualifiedModelName from '../standardizeFullyQualifiedModelName.js';
|
|
9
9
|
import uniq from '../uniq.js';
|
|
10
|
+
import parseAttribute from './parseAttribute.js';
|
|
10
11
|
/**
|
|
11
12
|
* Column names that are automatically emitted by the model generator (and
|
|
12
13
|
* the migration generator). When the user passes any of these explicitly,
|
|
@@ -35,8 +36,17 @@ export default function generateDreamContent(options) {
|
|
|
35
36
|
const columnsForAttributes = config.isSTI
|
|
36
37
|
? options.columnsWithTypes
|
|
37
38
|
: filterAutoGeneratedTimestampColumns(options.columnsWithTypes);
|
|
38
|
-
const baseImports = createImportConfig(config, options, { includeSoftDelete });
|
|
39
39
|
const attributesResult = processAttributes(columnsForAttributes, config.modelClassName);
|
|
40
|
+
// Decorators is only referenced via `@deco.*` annotations on fields (e.g.,
|
|
41
|
+
// BelongsTo, Encrypted). If the generated body emits none of those, the
|
|
42
|
+
// import + declaration would trigger lint errors for unused identifiers,
|
|
43
|
+
// so emit them commented out instead — keeps the boilerplate present for
|
|
44
|
+
// the developer to uncomment when they add their first decorator.
|
|
45
|
+
const decoratorsInUse = attributesResult.formattedDecorators.length > 0;
|
|
46
|
+
const baseImports = createImportConfig(config, options, {
|
|
47
|
+
includeSoftDelete,
|
|
48
|
+
includeDecorators: decoratorsInUse,
|
|
49
|
+
});
|
|
40
50
|
const allImports = {
|
|
41
51
|
...baseImports,
|
|
42
52
|
modelImportStatements: [...baseImports.modelImportStatements, ...attributesResult.imports],
|
|
@@ -48,9 +58,14 @@ export default function generateDreamContent(options) {
|
|
|
48
58
|
const fieldsSection = buildFieldsSection(config, attributesResult, {
|
|
49
59
|
includeDeletedAt: includeSoftDelete || hasExplicitDeletedAt,
|
|
50
60
|
});
|
|
61
|
+
const decoBlock = decoratorsInUse
|
|
62
|
+
? `const deco = new Decorators<typeof ${config.modelClassName}>()`
|
|
63
|
+
: `// Uncomment when adding decorators (@deco.BelongsTo, @deco.Validates, etc.):
|
|
64
|
+
// import { Decorators } from '@rvoh/dream'
|
|
65
|
+
// const deco = new Decorators<typeof ${config.modelClassName}>()`;
|
|
51
66
|
return `${importSection}
|
|
52
67
|
|
|
53
|
-
|
|
68
|
+
${decoBlock}
|
|
54
69
|
|
|
55
70
|
${classDeclaration}
|
|
56
71
|
${tableMethod}${serializersMethod}${fieldsSection}
|
|
@@ -78,9 +93,11 @@ export function createModelConfig(options) {
|
|
|
78
93
|
tableName,
|
|
79
94
|
};
|
|
80
95
|
}
|
|
81
|
-
export function createImportConfig(config, options, { includeSoftDelete }) {
|
|
96
|
+
export function createImportConfig(config, options, { includeSoftDelete, includeDecorators = true }) {
|
|
82
97
|
const dreamTypeImports = ['DreamColumn'];
|
|
83
|
-
const dreamImports = [
|
|
98
|
+
const dreamImports = [];
|
|
99
|
+
if (includeDecorators)
|
|
100
|
+
dreamImports.push('Decorators');
|
|
84
101
|
if (options.serializer) {
|
|
85
102
|
dreamTypeImports.push('DreamSerializers');
|
|
86
103
|
}
|
|
@@ -119,32 +136,41 @@ export function processAttributes(columnsWithTypes, modelClassName) {
|
|
|
119
136
|
};
|
|
120
137
|
}
|
|
121
138
|
export function processAttribute(attribute, modelClassName) {
|
|
122
|
-
const [
|
|
123
|
-
if (
|
|
139
|
+
const [rawName, rawType] = attribute.split(':');
|
|
140
|
+
if (rawName === undefined)
|
|
124
141
|
return { content: '', imports: [] };
|
|
125
|
-
if (!
|
|
126
|
-
throw new Error(`must pass a column type for ${
|
|
142
|
+
if (!rawType) {
|
|
143
|
+
throw new Error(`must pass a column type for ${rawName} (i.e. ${rawName}:string)`);
|
|
127
144
|
}
|
|
128
|
-
const
|
|
129
|
-
|
|
145
|
+
const parsed = parseAttribute(attribute);
|
|
146
|
+
// Malformed-but-parseable shapes (e.g., `Model@:belongs_to` with an empty
|
|
147
|
+
// alias) yield no content rather than throwing — the call site has already
|
|
148
|
+
// validated the basic name/type segments above.
|
|
149
|
+
if (!parsed)
|
|
150
|
+
return { content: '', imports: [] };
|
|
151
|
+
switch (parsed.normalizedAttributeType) {
|
|
130
152
|
case 'belongsto':
|
|
131
|
-
return createBelongsToAttribute(
|
|
153
|
+
return createBelongsToAttribute(parsed.rawAttributeName, modelClassName, {
|
|
154
|
+
aliasName: parsed.aliasName,
|
|
155
|
+
isOptional: parsed.isOptional,
|
|
156
|
+
});
|
|
132
157
|
case 'hasone':
|
|
133
158
|
case 'hasmany':
|
|
134
159
|
return { content: '', imports: [] };
|
|
135
160
|
case 'encrypted':
|
|
136
|
-
return createEncryptedAttribute(
|
|
161
|
+
return createEncryptedAttribute(parsed.rawAttributeName, attribute, modelClassName);
|
|
137
162
|
default:
|
|
138
|
-
return createRegularAttribute(
|
|
163
|
+
return createRegularAttribute(parsed.rawAttributeName, attribute, modelClassName);
|
|
139
164
|
}
|
|
140
165
|
}
|
|
141
|
-
export function createBelongsToAttribute(
|
|
142
|
-
const fullyQualifiedAssociatedModelName = standardizeFullyQualifiedModelName(
|
|
166
|
+
export function createBelongsToAttribute(fullyQualifiedModelInput, modelClassName, { aliasName, isOptional = false, } = {}) {
|
|
167
|
+
const fullyQualifiedAssociatedModelName = standardizeFullyQualifiedModelName(fullyQualifiedModelInput);
|
|
143
168
|
const associationModelName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedAssociatedModelName);
|
|
144
169
|
const associationImportStatement = importStatementForModel(fullyQualifiedAssociatedModelName);
|
|
145
|
-
const associationName =
|
|
170
|
+
const associationName = aliasName
|
|
171
|
+
? camelize(aliasName)
|
|
172
|
+
: camelize(fullyQualifiedAssociatedModelName.split('/').pop());
|
|
146
173
|
const associationForeignKey = `${associationName}Id`;
|
|
147
|
-
const isOptional = descriptors.includes('optional');
|
|
148
174
|
const content = `
|
|
149
175
|
@deco.BelongsTo('${fullyQualifiedAssociatedModelName}', { on: '${associationForeignKey}'${isOptional ? ', optional: true' : ''} })
|
|
150
176
|
public ${associationName}: ${associationModelName}${isOptional ? ' | null' : ''}
|
|
@@ -17,14 +17,23 @@ export default function generateFactoryContent({ fullyQualifiedModelName, column
|
|
|
17
17
|
const attributeDefaults = [];
|
|
18
18
|
let counterVariableIncremented = false;
|
|
19
19
|
for (const attribute of columnsWithTypes) {
|
|
20
|
-
const [
|
|
21
|
-
if (
|
|
20
|
+
const [rawSegmentOne, _attributeType, ...descriptors] = attribute.split(':');
|
|
21
|
+
if (rawSegmentOne === undefined)
|
|
22
22
|
continue;
|
|
23
23
|
if (_attributeType === undefined)
|
|
24
24
|
continue;
|
|
25
25
|
const optional = optionalFromDescriptors(descriptors);
|
|
26
26
|
if (optional)
|
|
27
27
|
continue;
|
|
28
|
+
// Extract optional `@alias` from segment-1 (Model@alias:belongs_to form).
|
|
29
|
+
// Non-association tokens never contain `@`, so this is a no-op for scalars.
|
|
30
|
+
const atIdx = rawSegmentOne.indexOf('@');
|
|
31
|
+
const aliasName = atIdx !== -1 ? rawSegmentOne.slice(atIdx + 1) : undefined;
|
|
32
|
+
const attributeName = atIdx !== -1 ? rawSegmentOne.slice(0, atIdx) : rawSegmentOne;
|
|
33
|
+
if (!attributeName)
|
|
34
|
+
continue;
|
|
35
|
+
if (atIdx !== -1 && !aliasName)
|
|
36
|
+
continue;
|
|
28
37
|
const attributeVariable = camelize(attributeName.replace(/\//g, ''));
|
|
29
38
|
if (/^type$/.test(attributeName))
|
|
30
39
|
continue;
|
|
@@ -36,7 +45,9 @@ export default function generateFactoryContent({ fullyQualifiedModelName, column
|
|
|
36
45
|
const safeAttributeType = camelize(attributeType)?.toLowerCase();
|
|
37
46
|
switch (safeAttributeType) {
|
|
38
47
|
case 'belongsto': {
|
|
39
|
-
|
|
48
|
+
// When `Model@alias:belongs_to`, the factory property uses the alias;
|
|
49
|
+
// otherwise it uses the model's last namespace segment (legacy form).
|
|
50
|
+
const attributeVariable = aliasName ? camelize(aliasName) : camelize(attributeName.split('/').pop());
|
|
40
51
|
const fullyQualifiedAssociatedModelName = standardizeFullyQualifiedModelName(attributeName);
|
|
41
52
|
const associationModelName = globalClassNameFromFullyQualifiedModelName(fullyQualifiedAssociatedModelName);
|
|
42
53
|
const associationFactoryImportStatement = `import create${associationModelName} from '${absoluteDreamPath('factories', fullyQualifiedAssociatedModelName)}'`;
|
|
@@ -66,11 +77,27 @@ export default function generateFactoryContent({ fullyQualifiedModelName, column
|
|
|
66
77
|
counterVariableIncremented = true;
|
|
67
78
|
break;
|
|
68
79
|
case 'enum':
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
80
|
+
case 'enum[]': {
|
|
81
|
+
// When the user passed `name:enum:enum_type_name` (reuse form, no
|
|
82
|
+
// inline values), descriptors has exactly one element — the enum
|
|
83
|
+
// type name. The factory cannot see the enum's values, so emit a
|
|
84
|
+
// TS-rejecting placeholder rather than the type name itself (the
|
|
85
|
+
// previous behavior emitted `'enum_type_name'` as the literal value,
|
|
86
|
+
// which compiles in the factory but fails at runtime).
|
|
87
|
+
const isReuseWithoutValues = descriptors.length === 1;
|
|
88
|
+
const isArrayEnum = safeAttributeType === 'enum[]';
|
|
89
|
+
if (isReuseWithoutValues) {
|
|
90
|
+
const enumTypeName = descriptors[0];
|
|
91
|
+
const placeholder = isArrayEnum ? `['TODO']` : `'TODO'`;
|
|
92
|
+
attributeDefaults.push(`// TODO: replace with a value from the \`${enumTypeName}\` enum\n ${attributeVariable}: ${placeholder},`);
|
|
93
|
+
}
|
|
94
|
+
else {
|
|
95
|
+
const firstValue = (descriptors.at(-1) || '<tbd>').split(',')[0];
|
|
96
|
+
const literal = isArrayEnum ? `['${firstValue}']` : `'${firstValue}'`;
|
|
97
|
+
attributeDefaults.push(`${attributeVariable}: ${literal},`);
|
|
98
|
+
}
|
|
73
99
|
break;
|
|
100
|
+
}
|
|
74
101
|
case 'integer':
|
|
75
102
|
attributeDefaults.push(`${attributeVariable}: 1,`);
|
|
76
103
|
break;
|
|
@@ -27,9 +27,19 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
27
27
|
const emitDeletedAtColumn = !altering && (softDelete || userExplicitlyPassedDeletedAt);
|
|
28
28
|
const { columnDefs, columnDrops, indexDefs, indexDrops } = processedColumnsWithTypes.reduce((acc, attributeDeclaration) => {
|
|
29
29
|
const { columnDefs, columnDrops, indexDefs, indexDrops } = acc;
|
|
30
|
-
const [
|
|
30
|
+
const [rawSegmentOne, _attributeType, ...descriptors] = attributeDeclaration.split(':');
|
|
31
|
+
if (!rawSegmentOne)
|
|
32
|
+
return acc;
|
|
33
|
+
// Extract optional `@alias` from segment-1 (Model@alias:belongs_to form).
|
|
34
|
+
// The model name (without alias) is what gets standardized / referenced
|
|
35
|
+
// by table lookup; the alias drives the column / index naming when present.
|
|
36
|
+
const atIdx = rawSegmentOne.indexOf('@');
|
|
37
|
+
const aliasName = atIdx !== -1 ? rawSegmentOne.slice(atIdx + 1) : undefined;
|
|
38
|
+
const nonStandardAttributeName = atIdx !== -1 ? rawSegmentOne.slice(0, atIdx) : rawSegmentOne;
|
|
31
39
|
if (!nonStandardAttributeName)
|
|
32
40
|
return acc;
|
|
41
|
+
if (atIdx !== -1 && !aliasName)
|
|
42
|
+
return acc;
|
|
33
43
|
/**
|
|
34
44
|
* Automatically set email columns to citext since different casings of
|
|
35
45
|
* email address are the same email address
|
|
@@ -64,8 +74,14 @@ export default function generateMigrationContent({ connectionName = 'default', t
|
|
|
64
74
|
primaryKeyType,
|
|
65
75
|
omitInlineNonNull,
|
|
66
76
|
originalAssociationName: nonStandardAttributeName,
|
|
77
|
+
aliasName,
|
|
67
78
|
}));
|
|
68
|
-
|
|
79
|
+
// Resolve the actual column name used for index + drop emission.
|
|
80
|
+
// When an alias is present (Model@alias:belongs_to), the column is
|
|
81
|
+
// `${alias}_id`; otherwise it's derived from the model's last segment.
|
|
82
|
+
attributeName = aliasName
|
|
83
|
+
? snakeify(aliasName)
|
|
84
|
+
: snakeify(nonStandardAttributeName.split('/').pop());
|
|
69
85
|
attributeName = associationNameToForeignKey(attributeName);
|
|
70
86
|
break;
|
|
71
87
|
case 'enum':
|
|
@@ -268,6 +284,7 @@ function generateColumnStr(attributeName, attributeType, descriptors, { omitInli
|
|
|
268
284
|
const isUnique = /(email|token|uuid)$/.test(attributeName);
|
|
269
285
|
const hasExtraValues = providedDefault || notNull || isUnique;
|
|
270
286
|
const isArray = /\[\]$/.test(attributeType);
|
|
287
|
+
const needsJsonDefault = notNull && !isArray && (attributeType === 'jsonb' || attributeType === 'json');
|
|
271
288
|
if (hasExtraValues)
|
|
272
289
|
returnStr += ', col => col';
|
|
273
290
|
if (notNull)
|
|
@@ -278,6 +295,12 @@ function generateColumnStr(attributeName, attributeType, descriptors, { omitInli
|
|
|
278
295
|
returnStr += `.defaultTo('${providedDefault}')`;
|
|
279
296
|
else if (isArray)
|
|
280
297
|
returnStr += `.defaultTo('{}')`;
|
|
298
|
+
// jsonb / json columns get an empty-object default so calling create() on a
|
|
299
|
+
// model without explicitly setting the column doesn't trip NOT NULL. Mirrors
|
|
300
|
+
// the existing boolean → false and array → '{}' auto-defaults. Optional
|
|
301
|
+
// jsonb columns skip the default since null is the intended initial state.
|
|
302
|
+
else if (needsJsonDefault)
|
|
303
|
+
returnStr += `.defaultTo(sql\`'{}'::${attributeType}\`)`;
|
|
281
304
|
returnStr = `${returnStr})`;
|
|
282
305
|
if (attributeName === STI_TYPE_COLUMN_NAME)
|
|
283
306
|
returnStr = `// CONSIDER: when using type for STI, always use an enum
|
|
@@ -306,11 +329,14 @@ function attributeTypeString(attributeType) {
|
|
|
306
329
|
}
|
|
307
330
|
}
|
|
308
331
|
}
|
|
309
|
-
function generateBelongsToStr(connectionName, associationName, { primaryKeyType, omitInlineNonNull: optional = false, originalAssociationName, }) {
|
|
332
|
+
function generateBelongsToStr(connectionName, associationName, { primaryKeyType, omitInlineNonNull: optional = false, originalAssociationName, aliasName, }) {
|
|
310
333
|
const dbDriverClass = Query.dbDriverClass(connectionName);
|
|
311
334
|
const dataType = dbDriverClass.foreignKeyTypeFromPrimaryKey(primaryKeyType);
|
|
312
335
|
const references = lookupReferencesTable(associationName, originalAssociationName);
|
|
313
|
-
|
|
336
|
+
// When the user passed `Model@alias:belongs_to`, the column name comes from
|
|
337
|
+
// the alias; otherwise it's the model's last segment (existing behavior).
|
|
338
|
+
const columnNameSource = aliasName ? snakeify(aliasName) : associationName.split('/').pop();
|
|
339
|
+
return `.addColumn('${associationNameToForeignKey(columnNameSource)}', '${dataType}', col => col.references('${references}.id').onDelete('restrict')${optional ? '' : '.notNull()'})`;
|
|
314
340
|
}
|
|
315
341
|
function generateIdStr({ primaryKeyType }) {
|
|
316
342
|
switch (primaryKeyType) {
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import camelize from '../camelize.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a single `columnsWithTypes` CLI token into its structural pieces.
|
|
4
|
+
*
|
|
5
|
+
* Centralizes the splitting + normalization logic shared across the model
|
|
6
|
+
* generator (`generateDreamContent`), the migration generator
|
|
7
|
+
* (`generateMigrationContent`), the factory generator
|
|
8
|
+
* (`generateFactoryContent`), and Psychic's resource/controller generators.
|
|
9
|
+
* Keeps the shared layer thin: consumers handle their own coercions (e.g.,
|
|
10
|
+
* migration's `email$ → citext`) and filters (e.g., Psychic's `_type`/`_id`
|
|
11
|
+
* exclusion).
|
|
12
|
+
*
|
|
13
|
+
* Returns `null` for malformed tokens (missing name or type).
|
|
14
|
+
*
|
|
15
|
+
* Supported forms (segment-1 only — see `rawAttributeType` for the full
|
|
16
|
+
* vocabulary of types/associations recognized by individual generators):
|
|
17
|
+
*
|
|
18
|
+
* - `name:type` → standard column
|
|
19
|
+
* - `name:type:optional` → nullable column
|
|
20
|
+
* - `Model:belongs_to[:optional]` → association with model-derived alias
|
|
21
|
+
* - `Model@alias:belongs_to[:optional]` → association with explicit alias
|
|
22
|
+
*/
|
|
23
|
+
export default function parseAttribute(attribute) {
|
|
24
|
+
const segments = attribute.split(':');
|
|
25
|
+
const rawSegmentOne = segments[0];
|
|
26
|
+
const rawAttributeType = segments[1];
|
|
27
|
+
const descriptors = segments.slice(2);
|
|
28
|
+
if (!rawSegmentOne || !rawAttributeType)
|
|
29
|
+
return null;
|
|
30
|
+
// Split segment-1 on `@` to extract an optional alias. Empty alias after `@`
|
|
31
|
+
// (e.g., `Model@:belongs_to`) is treated as malformed.
|
|
32
|
+
let rawAttributeName = rawSegmentOne;
|
|
33
|
+
let aliasName;
|
|
34
|
+
const atIndex = rawSegmentOne.indexOf('@');
|
|
35
|
+
if (atIndex !== -1) {
|
|
36
|
+
rawAttributeName = rawSegmentOne.slice(0, atIndex);
|
|
37
|
+
const rawAlias = rawSegmentOne.slice(atIndex + 1);
|
|
38
|
+
if (!rawAttributeName || !rawAlias)
|
|
39
|
+
return null;
|
|
40
|
+
aliasName = rawAlias;
|
|
41
|
+
}
|
|
42
|
+
// Pop trailing `optional` keyword off the descriptors list. Mirrors
|
|
43
|
+
// `optionalFromDescriptors` in generateMigrationContent.ts so the keyword
|
|
44
|
+
// behaves identically regardless of which consumer parses the token.
|
|
45
|
+
let isOptional = false;
|
|
46
|
+
if (descriptors[descriptors.length - 1] === 'optional') {
|
|
47
|
+
descriptors.pop();
|
|
48
|
+
isOptional = true;
|
|
49
|
+
}
|
|
50
|
+
const normalizedAttributeType = camelize(rawAttributeType).toLowerCase();
|
|
51
|
+
const isArray = /\[\]$/.test(rawAttributeType);
|
|
52
|
+
return {
|
|
53
|
+
rawAttributeName,
|
|
54
|
+
aliasName,
|
|
55
|
+
rawAttributeType,
|
|
56
|
+
normalizedAttributeType,
|
|
57
|
+
descriptors,
|
|
58
|
+
isOptional,
|
|
59
|
+
isArray,
|
|
60
|
+
};
|
|
61
|
+
}
|
|
@@ -7,6 +7,9 @@ export { default as DataIncompatibleWithDatabaseField } from '../errors/db/DataI
|
|
|
7
7
|
export { default as DataTypeColumnTypeMismatch } from '../errors/db/DataTypeColumnTypeMismatch.js';
|
|
8
8
|
export { default as NotNullViolation } from '../errors/db/NotNullViolation.js';
|
|
9
9
|
export { default as GlobalNameNotSet } from '../errors/dream-app/GlobalNameNotSet.js';
|
|
10
|
+
export { default as DecryptionError } from '../errors/encrypt/DecryptionError.js';
|
|
11
|
+
export { default as DecryptionParseError } from '../errors/encrypt/DecryptionParseError.js';
|
|
12
|
+
export { default as DecryptionRotationError } from '../errors/encrypt/DecryptionRotationError.js';
|
|
10
13
|
export { default as RecordNotFound } from '../errors/RecordNotFound.js';
|
|
11
14
|
export { default as MissingSerializersDefinition } from '../errors/serializers/MissingSerializersDefinition.js';
|
|
12
15
|
export { default as ValidationError } from '../errors/ValidationError.js';
|
|
@@ -14,7 +14,7 @@ export type SpawnOptions = Omit<NodeSpawnOptions, 'shell'> & {
|
|
|
14
14
|
args?: string[];
|
|
15
15
|
};
|
|
16
16
|
export declare const CLI_INDENT = " ";
|
|
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";
|
|
17
|
+
export declare const baseColumnsWithTypesDescription = "space separated snake-case (except for belongs_to model name, which may take an @alias suffix to rename the FK) 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";
|
|
18
18
|
export default class DreamCLI {
|
|
19
19
|
/**
|
|
20
20
|
* Starts the Dream console
|
|
@@ -473,14 +473,13 @@ export default class KyselyQueryDriver<DreamInstance extends Dream> extends Quer
|
|
|
473
473
|
* Resolve the value passed to `pg.Pool`'s `ssl` field for a given credential.
|
|
474
474
|
*
|
|
475
475
|
* Precedence:
|
|
476
|
-
* 1. If `connectionConf.ssl` is set (
|
|
477
|
-
*
|
|
478
|
-
*
|
|
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
|
|
476
|
+
* 1. If `connectionConf.ssl` is set (object or explicit `false`), pass it
|
|
477
|
+
* straight through to `pg`.
|
|
478
|
+
* 2. Else if `connectionConf.useSsl` is `true` (deprecated), fall back to
|
|
482
479
|
* `{ rejectUnauthorized: false }` — encrypted but **not** authenticated.
|
|
483
|
-
*
|
|
484
|
-
*
|
|
480
|
+
*
|
|
481
|
+
* `assertDbCredentialTlsDirective` (in `dream-app`) throws at
|
|
482
|
+
* `app.set('db', ...)` time when both `ssl` and `useSsl` are unset, so this
|
|
483
|
+
* resolver never sees the "neither directive" state.
|
|
485
484
|
*/
|
|
486
|
-
export declare function resolvePostgresSsl(connectionConf: SingleDbCredential):
|
|
485
|
+
export declare function resolvePostgresSsl(connectionConf: SingleDbCredential): TlsConnectionOptions | false;
|
|
@@ -213,27 +213,35 @@ export interface SingleDbCredential {
|
|
|
213
213
|
/**
|
|
214
214
|
* @deprecated Use `ssl` instead.
|
|
215
215
|
*
|
|
216
|
-
*
|
|
216
|
+
* Legacy boolean opt-in for Postgres TLS. When `true` (and `ssl` is not
|
|
217
217
|
* set), Dream connects with `{ rejectUnauthorized: false }` — TLS is on but
|
|
218
|
-
* the server certificate is not verified.
|
|
219
|
-
*
|
|
220
|
-
*
|
|
221
|
-
* Node's defaults (`ssl: true`). This field is preserved for back-compat
|
|
222
|
-
* and will be removed in a future major version.
|
|
218
|
+
* the server certificate is not verified. Preserved for back-compat and
|
|
219
|
+
* will be removed in a future major version. New code should set `ssl`
|
|
220
|
+
* directly.
|
|
223
221
|
*/
|
|
224
222
|
useSsl?: boolean;
|
|
225
223
|
/**
|
|
226
|
-
*
|
|
227
|
-
*
|
|
224
|
+
* TLS configuration passed straight through to `pg.Pool`'s `ssl` field.
|
|
225
|
+
* Takes precedence over the deprecated `useSsl` when provided.
|
|
228
226
|
*
|
|
229
|
-
*
|
|
230
|
-
*
|
|
231
|
-
*
|
|
232
|
-
*
|
|
233
|
-
*
|
|
234
|
-
*
|
|
227
|
+
* Set `rejectUnauthorized: true` (Node's own default) for verified TLS
|
|
228
|
+
* against the system CA store — the right choice for managed providers that
|
|
229
|
+
* present a public-CA-signed certificate (Supabase, Neon, Render, Azure
|
|
230
|
+
* Database for PostgreSQL on Flexible Server, etc.).
|
|
231
|
+
*
|
|
232
|
+
* For providers that present a private-CA certificate (AWS RDS,
|
|
233
|
+
* GCP Cloud SQL), add a `ca` bundle:
|
|
234
|
+
* `ssl: { rejectUnauthorized: true, ca: readFileSync('rds-ca.pem') }`.
|
|
235
|
+
*
|
|
236
|
+
* For providers that present a self-signed certificate (Heroku Hobby,
|
|
237
|
+
* some local docker images), set `rejectUnauthorized: false` — encrypted
|
|
238
|
+
* but unauthenticated.
|
|
239
|
+
*
|
|
240
|
+
* Set `false` to disable TLS entirely. Omitting `ssl` (and `useSsl`) throws
|
|
241
|
+
* at `app.set('db', ...)` time so the safety question is a deliberate
|
|
242
|
+
* decision at the call site rather than a silent default.
|
|
235
243
|
*/
|
|
236
|
-
ssl?:
|
|
244
|
+
ssl?: TlsConnectionOptions | false;
|
|
237
245
|
}
|
|
238
246
|
export type DreamLogger = {
|
|
239
247
|
info: (...args: any[]) => void;
|
|
@@ -12,7 +12,7 @@ export default class Encrypt {
|
|
|
12
12
|
*
|
|
13
13
|
* **Three-arg form** (rotation): tries the current key first; on
|
|
14
14
|
* `DecryptionError` falls back to the legacy key. If both fail, throws
|
|
15
|
-
* `
|
|
15
|
+
* `DecryptionRotationError` carrying both per-key errors. A
|
|
16
16
|
* `DecryptionParseError` from the current key is **not** retried — the
|
|
17
17
|
* cipher already matched, so a parse failure means the encrypted format
|
|
18
18
|
* is wrong (an app bug), not a wrong key.
|
|
@@ -22,7 +22,7 @@ export default class Encrypt {
|
|
|
22
22
|
* @throws MissingEncryptionKey
|
|
23
23
|
* @throws DecryptionError
|
|
24
24
|
* @throws DecryptionParseError
|
|
25
|
-
* @throws
|
|
25
|
+
* @throws DecryptionRotationError
|
|
26
26
|
*/
|
|
27
27
|
static decrypt<RetType>(encrypted: string, { algorithm, key }: DecryptOptions, legacyOpts?: DecryptOptions): RetType | null;
|
|
28
28
|
private static attemptDecryptionWithLegacyKeys;
|
|
@@ -59,7 +59,7 @@ export default class Encrypt {
|
|
|
59
59
|
* not forced to re-authenticate.
|
|
60
60
|
* - For `@Encrypted` columns: until every existing row has been
|
|
61
61
|
* re-encrypted under the new key. Dropping `legacy` early will cause
|
|
62
|
-
* `
|
|
62
|
+
* `DecryptionRotationError` on any not-yet-rewritten row.
|
|
63
63
|
*/
|
|
64
64
|
static generateKey(algorithm: EncryptAlgorithm): string;
|
|
65
65
|
static validateKey(base64EncodedKey: string, algorithm: EncryptAlgorithm): boolean;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import DecryptionError from './DecryptionError.js';
|
|
2
|
-
export default class
|
|
2
|
+
export default class DecryptionRotationError extends Error {
|
|
3
3
|
readonly currentKeyError: DecryptionError;
|
|
4
4
|
readonly legacyKeyError: DecryptionError;
|
|
5
5
|
constructor(currentKeyError: DecryptionError, legacyKeyError: DecryptionError);
|
|
@@ -37,15 +37,19 @@ export interface AttributeProcessingResult {
|
|
|
37
37
|
}
|
|
38
38
|
export default function generateDreamContent(options: GenerateDreamContentOptions): string;
|
|
39
39
|
export declare function createModelConfig(options: GenerateDreamContentOptions): ModelConfig;
|
|
40
|
-
export declare function createImportConfig(config: ModelConfig, options: GenerateDreamContentOptions, { includeSoftDelete }: {
|
|
40
|
+
export declare function createImportConfig(config: ModelConfig, options: GenerateDreamContentOptions, { includeSoftDelete, includeDecorators }: {
|
|
41
41
|
includeSoftDelete: boolean;
|
|
42
|
+
includeDecorators?: boolean;
|
|
42
43
|
}): ImportConfig;
|
|
43
44
|
export declare function processAttributes(columnsWithTypes: string[], modelClassName: string): AttributeProcessingResult & {
|
|
44
45
|
formattedFields: string;
|
|
45
46
|
formattedDecorators: string;
|
|
46
47
|
};
|
|
47
48
|
export declare function processAttribute(attribute: string, modelClassName: string): AttributeProcessingResult;
|
|
48
|
-
export declare function createBelongsToAttribute(
|
|
49
|
+
export declare function createBelongsToAttribute(fullyQualifiedModelInput: string, modelClassName: string, { aliasName, isOptional, }?: {
|
|
50
|
+
aliasName?: string | undefined;
|
|
51
|
+
isOptional?: boolean;
|
|
52
|
+
}): AttributeProcessingResult;
|
|
49
53
|
export declare function createEncryptedAttribute(attributeName: string, attribute: string, modelClassName: string): AttributeProcessingResult;
|
|
50
54
|
export declare function createRegularAttribute(attributeName: string, attribute: string, modelClassName: string): AttributeProcessingResult;
|
|
51
55
|
export {};
|