@rvoh/dream 1.2.1 → 1.3.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/Dream.js +101 -33
- package/dist/cjs/src/db/errors.js +16 -6
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +33 -20
- package/dist/cjs/src/dream/internal/printSerializerHierarchyLevel.js +17 -0
- package/dist/cjs/src/dream-app/cache.js +12 -2
- package/dist/cjs/src/errors/db/CheckConstraintViolation.js +15 -0
- package/dist/cjs/src/errors/db/DataTypeColumnTypeMismatch.js +1 -5
- package/dist/cjs/src/errors/db/NotNullViolation.js +15 -0
- package/dist/cjs/src/helpers/cli/generateMigration.js +1 -0
- package/dist/cjs/src/helpers/cli/generateMigrationContent.js +20 -5
- package/dist/cjs/src/helpers/cli/generateStiMigrationContent.js +8 -2
- package/dist/cjs/src/index.js +6 -2
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
- package/dist/esm/src/Dream.js +102 -34
- package/dist/esm/src/db/errors.js +14 -4
- package/dist/esm/src/dream/QueryDriver/Kysely.js +34 -21
- package/dist/esm/src/dream/internal/printSerializerHierarchyLevel.js +14 -0
- package/dist/esm/src/dream-app/cache.js +12 -2
- package/dist/esm/src/errors/db/CheckConstraintViolation.js +12 -0
- package/dist/esm/src/errors/db/DataTypeColumnTypeMismatch.js +1 -5
- package/dist/esm/src/errors/db/NotNullViolation.js +12 -0
- package/dist/esm/src/helpers/cli/generateMigration.js +1 -0
- package/dist/esm/src/helpers/cli/generateMigrationContent.js +20 -5
- package/dist/esm/src/helpers/cli/generateStiMigrationContent.js +8 -2
- package/dist/esm/src/index.js +2 -0
- package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
- package/dist/types/src/Dream.d.ts +77 -52
- package/dist/types/src/db/errors.d.ts +14 -2
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
- package/dist/types/src/dream/internal/printSerializerHierarchyLevel.d.ts +7 -0
- package/dist/types/src/errors/db/CheckConstraintViolation.d.ts +10 -0
- package/dist/types/src/errors/db/NotNullViolation.d.ts +10 -0
- package/dist/types/src/helpers/cli/generateMigrationContent.d.ts +2 -1
- package/dist/types/src/helpers/cli/generateStiMigrationContent.d.ts +2 -1
- package/dist/types/src/index.d.ts +2 -0
- package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +1 -1
- package/dist/types/src/types/dream.d.ts +1 -1
- package/dist/types/src/types/dream.ts +2 -2
- package/docs/assets/navigation.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/Benchmark.html +2 -2
- package/docs/classes/CalendarDate.html +2 -2
- package/docs/classes/CheckConstraintViolation.html +14 -0
- package/docs/classes/CliFileWriter.html +2 -2
- package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/DataTypeColumnTypeMismatch.html +3 -3
- package/docs/classes/Decorators.html +19 -19
- package/docs/classes/Dream.html +209 -308
- package/docs/classes/DreamApp.html +4 -4
- package/docs/classes/DreamBin.html +2 -2
- package/docs/classes/DreamCLI.html +4 -4
- package/docs/classes/DreamImporter.html +2 -2
- package/docs/classes/DreamLogos.html +2 -2
- package/docs/classes/DreamMigrationHelpers.html +7 -7
- package/docs/classes/DreamSerializerBuilder.html +8 -8
- package/docs/classes/DreamTransaction.html +2 -2
- package/docs/classes/Encrypt.html +2 -2
- package/docs/classes/Env.html +2 -2
- package/docs/classes/GlobalNameNotSet.html +3 -3
- package/docs/classes/NonLoadedAssociation.html +3 -3
- package/docs/classes/NotNullViolation.html +14 -0
- package/docs/classes/ObjectSerializerBuilder.html +8 -8
- package/docs/classes/Query.html +60 -60
- package/docs/classes/Range.html +2 -2
- package/docs/classes/RecordNotFound.html +3 -3
- package/docs/classes/ValidationError.html +3 -3
- package/docs/functions/DreamSerializer.html +1 -1
- package/docs/functions/ObjectSerializer.html +1 -1
- package/docs/functions/ReplicaSafe.html +1 -1
- package/docs/functions/STI.html +1 -1
- package/docs/functions/SoftDelete.html +1 -1
- package/docs/functions/camelize.html +1 -1
- package/docs/functions/capitalize.html +1 -1
- package/docs/functions/cloneDeepSafe.html +1 -1
- package/docs/functions/closeAllDbConnections.html +1 -1
- package/docs/functions/compact.html +1 -1
- package/docs/functions/dreamDbConnections.html +1 -1
- package/docs/functions/dreamPath.html +1 -1
- package/docs/functions/expandStiClasses.html +1 -1
- package/docs/functions/generateDream.html +1 -1
- package/docs/functions/globalClassNameFromFullyQualifiedModelName.html +1 -1
- package/docs/functions/groupBy.html +1 -1
- package/docs/functions/hyphenize.html +1 -1
- package/docs/functions/inferSerializerFromDreamOrViewModel.html +1 -1
- package/docs/functions/inferSerializersFromDreamClassOrViewModelClass.html +1 -1
- package/docs/functions/intersection.html +1 -1
- package/docs/functions/isDreamSerializer.html +1 -1
- package/docs/functions/isEmpty.html +1 -1
- package/docs/functions/loadRepl.html +1 -1
- package/docs/functions/lookupClassByGlobalName.html +1 -1
- package/docs/functions/normalizeUnicode.html +1 -1
- package/docs/functions/pascalize.html +1 -1
- package/docs/functions/pgErrorType.html +1 -1
- package/docs/functions/range-1.html +1 -1
- package/docs/functions/relativeDreamPath.html +1 -1
- package/docs/functions/round.html +1 -1
- package/docs/functions/serializerNameFromFullyQualifiedModelName.html +1 -1
- package/docs/functions/sharedPathPrefix.html +1 -1
- package/docs/functions/snakeify.html +1 -1
- package/docs/functions/sort.html +1 -1
- package/docs/functions/sortBy.html +1 -1
- package/docs/functions/sortObjectByKey.html +1 -1
- package/docs/functions/sortObjectByValue.html +1 -1
- package/docs/functions/standardizeFullyQualifiedModelName.html +1 -1
- package/docs/functions/uncapitalize.html +1 -1
- package/docs/functions/uniq.html +1 -1
- package/docs/functions/untypedDb.html +1 -1
- package/docs/functions/validateColumn.html +1 -1
- package/docs/functions/validateTable.html +1 -1
- package/docs/interfaces/BelongsToStatement.html +2 -2
- package/docs/interfaces/DecoratorContext.html +2 -2
- package/docs/interfaces/DreamAppInitOptions.html +2 -2
- package/docs/interfaces/DreamAppOpts.html +2 -2
- package/docs/interfaces/EncryptOptions.html +2 -2
- package/docs/interfaces/InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/OpenapiDescription.html +2 -2
- package/docs/interfaces/OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/SerializerRendererOpts.html +2 -2
- package/docs/modules.html +2 -0
- package/docs/types/Camelized.html +1 -1
- package/docs/types/CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/DateTime.html +1 -1
- package/docs/types/DbConnectionType.html +1 -1
- package/docs/types/DbTypes.html +1 -1
- package/docs/types/DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/DreamAssociationMetadata.html +1 -1
- package/docs/types/DreamAttributes.html +1 -1
- package/docs/types/DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/DreamClassColumn.html +1 -1
- package/docs/types/DreamColumn.html +1 -1
- package/docs/types/DreamColumnNames.html +1 -1
- package/docs/types/DreamLogLevel.html +1 -1
- package/docs/types/DreamLogger.html +1 -1
- package/docs/types/DreamModelSerializerType.html +1 -1
- package/docs/types/DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/DreamParamSafeAttributes.html +1 -1
- package/docs/types/DreamParamSafeColumnNames.html +1 -1
- package/docs/types/DreamSerializable.html +1 -1
- package/docs/types/DreamSerializableArray.html +1 -1
- package/docs/types/DreamSerializerKey.html +1 -1
- package/docs/types/DreamSerializers.html +1 -1
- package/docs/types/DreamTableSchema.html +1 -1
- package/docs/types/DreamVirtualColumns.html +1 -1
- package/docs/types/EncryptAlgorithm.html +1 -1
- package/docs/types/HasManyStatement.html +1 -1
- package/docs/types/HasOneStatement.html +1 -1
- package/docs/types/Hyphenized.html +1 -1
- package/docs/types/IdType.html +1 -1
- package/docs/types/OpenapiAllTypes.html +1 -1
- package/docs/types/OpenapiFormats.html +1 -1
- package/docs/types/OpenapiNumberFormats.html +1 -1
- package/docs/types/OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/OpenapiSchemaArray.html +1 -1
- package/docs/types/OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/OpenapiSchemaBase.html +1 -1
- package/docs/types/OpenapiSchemaBody.html +1 -1
- package/docs/types/OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/OpenapiSchemaExpressionAllOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionOneOf.html +1 -1
- package/docs/types/OpenapiSchemaExpressionRef.html +1 -1
- package/docs/types/OpenapiSchemaExpressionRefSchemaShorthand.html +1 -1
- package/docs/types/OpenapiSchemaInteger.html +1 -1
- package/docs/types/OpenapiSchemaNull.html +1 -1
- package/docs/types/OpenapiSchemaNumber.html +1 -1
- package/docs/types/OpenapiSchemaObject.html +1 -1
- package/docs/types/OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionAllOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionAnyOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionOneOf.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionSerializableRef.html +1 -1
- package/docs/types/OpenapiSchemaShorthandExpressionSerializerRef.html +1 -1
- package/docs/types/OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/OpenapiSchemaString.html +1 -1
- package/docs/types/OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/OpenapiTypeField.html +1 -1
- package/docs/types/Pascalized.html +1 -1
- package/docs/types/PrimaryKeyType.html +1 -1
- package/docs/types/RoundingPrecision.html +1 -1
- package/docs/types/SerializerCasing.html +1 -1
- package/docs/types/SimpleObjectSerializerType.html +1 -1
- package/docs/types/Snakeified.html +1 -1
- package/docs/types/Timestamp.html +1 -1
- package/docs/types/UpdateableAssociationProperties.html +1 -1
- package/docs/types/UpdateableProperties.html +1 -1
- package/docs/types/ValidationType.html +1 -1
- package/docs/types/ViewModel.html +1 -1
- package/docs/types/ViewModelClass.html +1 -1
- package/docs/types/WhereStatementForDream.html +1 -1
- package/docs/types/WhereStatementForDreamClass.html +1 -1
- package/docs/variables/DateTime-1.html +1 -1
- package/docs/variables/DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/DreamConst.html +1 -1
- package/docs/variables/TRIGRAM_OPERATORS.html +1 -1
- package/docs/variables/openapiPrimitiveTypes-1.html +1 -1
- package/docs/variables/openapiShorthandPrimitiveTypes-1.html +1 -1
- package/docs/variables/ops.html +1 -1
- package/docs/variables/primaryKeyTypes.html +1 -1
- package/package.json +1 -1
package/dist/esm/src/Dream.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import yoctocolors from 'yoctocolors';
|
|
2
|
-
import { pgErrorType } from './db/errors.js';
|
|
2
|
+
import { pgErrorType, UNIQUE_VIOLATION } from './db/errors.js';
|
|
3
3
|
import db from './db/index.js';
|
|
4
4
|
import associationToGetterSetterProp from './decorators/field/association/associationToGetterSetterProp.js';
|
|
5
5
|
import { blankAssociationsFactory } from './decorators/field/association/shared.js';
|
|
@@ -19,6 +19,7 @@ import destroyDream from './dream/internal/destroyDream.js';
|
|
|
19
19
|
import { destroyOptions, reallyDestroyOptions, undestroyOptions, } from './dream/internal/destroyOptions.js';
|
|
20
20
|
import ensureSTITypeFieldIsSet from './dream/internal/ensureSTITypeFieldIsSet.js';
|
|
21
21
|
import findOrCreateBy from './dream/internal/findOrCreateBy.js';
|
|
22
|
+
import printSerializerHierarchyLevel from './dream/internal/printSerializerHierarchyLevel.js';
|
|
22
23
|
import reload from './dream/internal/reload.js';
|
|
23
24
|
import runValidations from './dream/internal/runValidations.js';
|
|
24
25
|
import saveDream from './dream/internal/saveDream.js';
|
|
@@ -47,10 +48,8 @@ import cloneDeepSafe from './helpers/cloneDeepSafe.js';
|
|
|
47
48
|
import { DateTime } from './helpers/DateTime.js';
|
|
48
49
|
import cachedTypeForAttribute from './helpers/db/cachedTypeForAttribute.js';
|
|
49
50
|
import isJsonColumn from './helpers/db/types/isJsonColumn.js';
|
|
50
|
-
import { indent } from './helpers/indent.js';
|
|
51
51
|
import notEqual from './helpers/notEqual.js';
|
|
52
52
|
import { inferSerializersFromDreamClassOrViewModelClass } from './serializer/helpers/inferSerializerFromDreamOrViewModel.js';
|
|
53
|
-
import { serializerForAssociatedClass } from './serializer/helpers/serializerForAssociatedClass.js';
|
|
54
53
|
export default class Dream {
|
|
55
54
|
DB;
|
|
56
55
|
/**
|
|
@@ -487,42 +486,65 @@ export default class Dream {
|
|
|
487
486
|
static recursiveSerializationMap(serializer, { forDisplay = false, forDisplayDepth = 0, } = {}) {
|
|
488
487
|
const serializerBuilder = serializer(undefined, undefined);
|
|
489
488
|
const serializerAssociations = serializerBuilder['attributes'].filter(attribute => ['rendersOne', 'rendersMany', 'delegatedAttribute'].includes(attribute.type));
|
|
490
|
-
return serializerAssociations.reduce((
|
|
489
|
+
return serializerAssociations.reduce((accumulator, serializerAssociation) => {
|
|
491
490
|
const serializerAssociationName = serializerAssociation.targetName ??
|
|
492
491
|
serializerAssociation.name;
|
|
493
492
|
const serializerAssociationType = serializerAssociation.type;
|
|
494
493
|
const association = this['getAssociationMetadata'](serializerAssociationName);
|
|
495
494
|
if (!association)
|
|
496
|
-
return
|
|
497
|
-
const
|
|
498
|
-
|
|
499
|
-
if (!associatedClass)
|
|
495
|
+
return accumulator;
|
|
496
|
+
const maybeAssociatedClasses = association.modelCB();
|
|
497
|
+
if (!maybeAssociatedClasses)
|
|
500
498
|
throw new Error(`No class defined on ${serializerAssociationName} association on ${this.sanitizedName}`);
|
|
501
|
-
const
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
499
|
+
const associatedClasses = Array.isArray(maybeAssociatedClasses)
|
|
500
|
+
? maybeAssociatedClasses
|
|
501
|
+
: [maybeAssociatedClasses];
|
|
502
|
+
/////////////////////////////////////////////////
|
|
503
|
+
// map associated classes to their serializers //
|
|
504
|
+
/////////////////////////////////////////////////
|
|
505
|
+
const associatedClassSerializerTuples = associatedClasses.flatMap(associatedClass => {
|
|
506
|
+
/**
|
|
507
|
+
* `serializers` is an array because `associatedClass` may be an STI
|
|
508
|
+
* base, with each of its STI children having its own serializer
|
|
509
|
+
*/
|
|
510
|
+
const serializers = serializerAssociation.options.serializer
|
|
511
|
+
? [serializerAssociation.options.serializer]
|
|
512
|
+
: inferSerializersFromDreamClassOrViewModelClass(associatedClass, serializerAssociation.options.serializerKey);
|
|
513
|
+
if (!serializers.length)
|
|
514
|
+
throw new Error(`No serializer found to render ${serializerAssociationName} on ${this.sanitizedName}`);
|
|
515
|
+
return serializers.map(serializer => [associatedClass, serializer]);
|
|
516
|
+
});
|
|
517
|
+
/////////////////////////////////////////////////////
|
|
518
|
+
// end:map associated classes to their serializers //
|
|
519
|
+
/////////////////////////////////////////////////////
|
|
520
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
521
|
+
// reduce over all associated serializers, recursively building out their associated serializers //
|
|
522
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
523
|
+
const innerAssociationSerializerInfo = associatedClassSerializerTuples.reduce((innerAccumulator, [associatedClass, associatedSerializer]) => {
|
|
524
|
+
if (forDisplay && serializerAssociationType !== 'delegatedAttribute') {
|
|
525
|
+
printSerializerHierarchyLevel({
|
|
526
|
+
serializerAssociationType,
|
|
527
|
+
serializerAssociationName,
|
|
528
|
+
associationSerializer: associatedSerializer,
|
|
529
|
+
forDisplayDepth,
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
return {
|
|
533
|
+
...innerAccumulator,
|
|
534
|
+
...associatedClass['recursiveSerializationMap'](associatedSerializer, {
|
|
521
535
|
forDisplay,
|
|
522
536
|
forDisplayDepth: forDisplayDepth + 1,
|
|
523
537
|
}),
|
|
538
|
+
};
|
|
539
|
+
}, {});
|
|
540
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
541
|
+
// end:reduce over all associated serializers, recursively building out their associated serializers //
|
|
542
|
+
///////////////////////////////////////////////////////////////////////////////////////////////////////
|
|
543
|
+
accumulator[association.as] = {
|
|
544
|
+
parentDreamClass: this,
|
|
545
|
+
nestedSerializerInfo: serializerAssociation.type === 'delegatedAttribute' ? {} : innerAssociationSerializerInfo,
|
|
524
546
|
};
|
|
525
|
-
return
|
|
547
|
+
return accumulator;
|
|
526
548
|
}, {});
|
|
527
549
|
}
|
|
528
550
|
/**
|
|
@@ -657,11 +679,57 @@ export default class Dream {
|
|
|
657
679
|
return map;
|
|
658
680
|
}
|
|
659
681
|
/**
|
|
660
|
-
*
|
|
682
|
+
* Checks whether the specified association has been defined on this Dream model.
|
|
683
|
+
*
|
|
684
|
+
* ```ts
|
|
685
|
+
* const user = User.new()
|
|
686
|
+
*
|
|
687
|
+
* user.hasAssociation('posts')
|
|
688
|
+
* // true (if User has a posts association defined)
|
|
689
|
+
*
|
|
690
|
+
* user.hasAssociation('nonExistentAssociation')
|
|
691
|
+
* // false
|
|
692
|
+
*
|
|
693
|
+
* // Useful for conditional association handling
|
|
694
|
+
* if (user.hasAssociation('posts')) {
|
|
695
|
+
* user = await user.load('posts').execute()
|
|
696
|
+
* }
|
|
697
|
+
* ```
|
|
698
|
+
*
|
|
699
|
+
* @param associationName - The name of the association to check for
|
|
700
|
+
* @returns `true` if the association exists on this model, `false` otherwise
|
|
701
|
+
*/
|
|
702
|
+
hasAssociation(associationName) {
|
|
703
|
+
return !!(this.associationMetadataByType.belongsTo.find(association => association.as === associationName) ||
|
|
704
|
+
this.associationMetadataByType.hasOne.find(association => association.as === associationName) ||
|
|
705
|
+
this.associationMetadataByType.hasMany.find(association => association.as === associationName));
|
|
706
|
+
}
|
|
707
|
+
/**
|
|
708
|
+
* Returns all of the association names for this Dream class.
|
|
709
|
+
* This includes all BelongsTo, HasOne, and HasMany associations
|
|
710
|
+
* defined on the model.
|
|
661
711
|
*
|
|
662
|
-
*
|
|
712
|
+
* This is useful for introspection, debugging, or dynamically working
|
|
713
|
+
* with all associations on a model.
|
|
714
|
+
*
|
|
715
|
+
* ```ts
|
|
716
|
+
* class User extends ApplicationModel {
|
|
717
|
+
* @deco.HasMany('Post')
|
|
718
|
+
* public posts: Post[]
|
|
719
|
+
*
|
|
720
|
+
* @deco.HasOne('Profile')
|
|
721
|
+
* public profile: Profile
|
|
722
|
+
*
|
|
723
|
+
* @deco.BelongsTo('Company')
|
|
724
|
+
* public company: Company
|
|
725
|
+
* }
|
|
726
|
+
*
|
|
727
|
+
* User.associationNames
|
|
728
|
+
* // ['posts', 'profile', 'company']
|
|
729
|
+
* })
|
|
730
|
+
* ```
|
|
663
731
|
*
|
|
664
|
-
* @returns
|
|
732
|
+
* @returns An array containing all association names defined on this Dream class
|
|
665
733
|
*/
|
|
666
734
|
static get associationNames() {
|
|
667
735
|
const allAssociations = [
|
|
@@ -853,7 +921,7 @@ export default class Dream {
|
|
|
853
921
|
return dreamModel;
|
|
854
922
|
}
|
|
855
923
|
catch (err) {
|
|
856
|
-
if (pgErrorType(err) ===
|
|
924
|
+
if (pgErrorType(err) === UNIQUE_VIOLATION) {
|
|
857
925
|
const dreamModel = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
|
|
858
926
|
if (!dreamModel)
|
|
859
927
|
throw new CreateOrFindByFailedToCreateAndFind(this);
|
|
@@ -907,7 +975,7 @@ export default class Dream {
|
|
|
907
975
|
}, skipHooks ? { skipHooks } : undefined);
|
|
908
976
|
}
|
|
909
977
|
catch (err) {
|
|
910
|
-
if (pgErrorType(err) ===
|
|
978
|
+
if (pgErrorType(err) === UNIQUE_VIOLATION) {
|
|
911
979
|
const existingRecord = await this.findBy(this.extractAttributesFromUpdateableProperties(attributes));
|
|
912
980
|
if (!existingRecord)
|
|
913
981
|
throw new CreateOrUpdateByFailedToCreateAndUpdate(this);
|
|
@@ -7,12 +7,22 @@
|
|
|
7
7
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
import pg from 'pg';
|
|
10
|
-
const
|
|
11
|
-
const
|
|
10
|
+
export const CHECK_VIOLATION = 'CHECK_VIOLATION';
|
|
11
|
+
export const FOREIGN_KEY_VIOLATION = 'FOREIGN_KEY_VIOLATION';
|
|
12
|
+
export const INTEGRITY_CONSTRAINT_VIOLATION = 'INTEGRITY_CONSTRAINT_VIOLATION';
|
|
13
|
+
export const INVALID_INPUT_SYNTAX = 'INVALID_INPUT_SYNTAX';
|
|
14
|
+
export const NOT_NULL_VIOLATION = 'NOT_NULL_VIOLATION';
|
|
15
|
+
export const RESTRICT_VIOLATION = 'RESTRICT_VIOLATION';
|
|
16
|
+
export const UNIQUE_VIOLATION = 'UNIQUE_VIOLATION';
|
|
12
17
|
export const PG_ERRORS = {
|
|
13
|
-
'23505':
|
|
14
|
-
'22P02': INVALID_INPUT_SYNTAX,
|
|
18
|
+
'23505': UNIQUE_VIOLATION,
|
|
15
19
|
'22007': INVALID_INPUT_SYNTAX,
|
|
20
|
+
'22P02': INVALID_INPUT_SYNTAX,
|
|
21
|
+
'23502': NOT_NULL_VIOLATION,
|
|
22
|
+
'23514': CHECK_VIOLATION,
|
|
23
|
+
'23000': INTEGRITY_CONSTRAINT_VIOLATION,
|
|
24
|
+
'23001': RESTRICT_VIOLATION,
|
|
25
|
+
'23503': FOREIGN_KEY_VIOLATION,
|
|
16
26
|
};
|
|
17
27
|
function pgErrorFromCode(code) {
|
|
18
28
|
return PG_ERRORS[code] || null;
|
|
@@ -3,7 +3,7 @@ import pluralize from 'pluralize-esm';
|
|
|
3
3
|
import writeSyncFile from '../../bin/helpers/sync.js';
|
|
4
4
|
import { CliFileWriter } from '../../cli/CliFileWriter.js';
|
|
5
5
|
import DreamCLI from '../../cli/index.js';
|
|
6
|
-
import { pgErrorType } from '../../db/errors.js';
|
|
6
|
+
import { CHECK_VIOLATION, INVALID_INPUT_SYNTAX, NOT_NULL_VIOLATION, pgErrorType } from '../../db/errors.js';
|
|
7
7
|
import _db from '../../db/index.js';
|
|
8
8
|
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
9
9
|
import PackageManager from '../../dream-app/helpers/PackageManager.js';
|
|
@@ -18,7 +18,9 @@ import MissingThroughAssociationSource from '../../errors/associations/MissingTh
|
|
|
18
18
|
import ThroughAssociationConditionsIncompatibleWithThroughAssociationSource from '../../errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js';
|
|
19
19
|
import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
|
|
20
20
|
import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
|
|
21
|
+
import CheckConstraintViolation from '../../errors/db/CheckConstraintViolation.js';
|
|
21
22
|
import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
|
|
23
|
+
import NotNullViolation from '../../errors/db/NotNullViolation.js';
|
|
22
24
|
import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
|
|
23
25
|
import CalendarDate from '../../helpers/CalendarDate.js';
|
|
24
26
|
import camelize from '../../helpers/camelize.js';
|
|
@@ -31,6 +33,7 @@ import _dropDb from '../../helpers/db/dropDb.js';
|
|
|
31
33
|
import loadPgClient from '../../helpers/db/loadPgClient.js';
|
|
32
34
|
import runMigration from '../../helpers/db/runMigration.js';
|
|
33
35
|
import EnvInternal from '../../helpers/EnvInternal.js';
|
|
36
|
+
import groupBy from '../../helpers/groupBy.js';
|
|
34
37
|
import isEmpty from '../../helpers/isEmpty.js';
|
|
35
38
|
import namespaceColumn from '../../helpers/namespaceColumn.js';
|
|
36
39
|
import normalizeUnicode from '../../helpers/normalizeUnicode.js';
|
|
@@ -433,11 +436,21 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
433
436
|
}
|
|
434
437
|
catch (error) {
|
|
435
438
|
switch (pgErrorType(error)) {
|
|
436
|
-
case
|
|
439
|
+
case INVALID_INPUT_SYNTAX:
|
|
437
440
|
throw new DataTypeColumnTypeMismatch({
|
|
438
441
|
dream,
|
|
439
442
|
error: error instanceof Error ? error : new Error('database column type error'),
|
|
440
443
|
});
|
|
444
|
+
case NOT_NULL_VIOLATION:
|
|
445
|
+
throw new NotNullViolation({
|
|
446
|
+
dream,
|
|
447
|
+
error: error instanceof Error ? error : new Error('not null violation'),
|
|
448
|
+
});
|
|
449
|
+
case CHECK_VIOLATION:
|
|
450
|
+
throw new CheckConstraintViolation({
|
|
451
|
+
dream,
|
|
452
|
+
error: error instanceof Error ? error : new Error('check constraint violation'),
|
|
453
|
+
});
|
|
441
454
|
}
|
|
442
455
|
throw error;
|
|
443
456
|
}
|
|
@@ -1202,7 +1215,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1202
1215
|
const keys = Object.keys(preloadStatement);
|
|
1203
1216
|
for (const key of keys) {
|
|
1204
1217
|
const nestedDreams = await this.applyOnePreload(key, dream, this.applyablePreloadOnStatements(preloadOnStatements[key]));
|
|
1205
|
-
if (nestedDreams) {
|
|
1218
|
+
if (nestedDreams.length) {
|
|
1206
1219
|
await this.applyPreload(preloadStatement[key], preloadOnStatements[key], nestedDreams);
|
|
1207
1220
|
}
|
|
1208
1221
|
}
|
|
@@ -1697,18 +1710,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1697
1710
|
*
|
|
1698
1711
|
* Used to preload polymorphic belongs to associations
|
|
1699
1712
|
*/
|
|
1700
|
-
async preloadPolymorphicBelongsTo(association, dreams) {
|
|
1701
|
-
|
|
1702
|
-
throw new Error(`Association ${association.as} points to an array of models but is not designated polymorphic`);
|
|
1703
|
-
if (association.type !== 'BelongsTo')
|
|
1704
|
-
throw new Error(`Polymorphic association ${association.as} points to an array of models but is ${association.type}. Only BelongsTo associations may point to an array of models.`);
|
|
1705
|
-
const nestedDreamsForNextRoundOfPreloading = [];
|
|
1706
|
-
for (const associatedModel of association.modelCB()) {
|
|
1707
|
-
await this.preloadPolymorphicAssociationModel(dreams, association, associatedModel, nestedDreamsForNextRoundOfPreloading);
|
|
1708
|
-
}
|
|
1709
|
-
return nestedDreamsForNextRoundOfPreloading;
|
|
1713
|
+
async preloadPolymorphicBelongsTo(association, associatedModels, dreams) {
|
|
1714
|
+
return compact(await Promise.all(associatedModels.map(associatedModel => this.preloadPolymorphicAssociationModel(dreams, association, associatedModel)))).flat();
|
|
1710
1715
|
}
|
|
1711
|
-
async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass
|
|
1716
|
+
async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass) {
|
|
1712
1717
|
const relevantAssociatedModels = dreams.filter((dream) => {
|
|
1713
1718
|
const field = association.foreignKeyTypeField();
|
|
1714
1719
|
return dream[field] === associatedDreamClass['stiBaseClassOrOwnClassName'] || dream[field] === null;
|
|
@@ -1727,7 +1732,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1727
1732
|
[associatedDreamClass.primaryKey]: relevantAssociatedModels.map((dream) => dream[association.foreignKey()]),
|
|
1728
1733
|
})
|
|
1729
1734
|
.all();
|
|
1730
|
-
loadedAssociations.forEach((loadedAssociation) => nestedDreamsForNextRoundOfPreloading.push(loadedAssociation));
|
|
1731
1735
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
1732
1736
|
// Associate each loaded association with each dream based on primary key and foreign key type
|
|
1733
1737
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1742,6 +1746,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1742
1746
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1743
1747
|
// end: Associate each loaded association with each dream based on primary key and foreign key type
|
|
1744
1748
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1749
|
+
return loadedAssociations;
|
|
1745
1750
|
}
|
|
1746
1751
|
}
|
|
1747
1752
|
/**
|
|
@@ -1780,12 +1785,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1780
1785
|
*
|
|
1781
1786
|
* Applies a preload statement
|
|
1782
1787
|
*/
|
|
1783
|
-
async applyOnePreload(
|
|
1788
|
+
async applyOnePreload(associationNameAndMaybeAlias, dreams, onStatement = {}) {
|
|
1784
1789
|
if (!Array.isArray(dreams))
|
|
1785
1790
|
dreams = [dreams];
|
|
1786
|
-
const
|
|
1787
|
-
|
|
1788
|
-
|
|
1791
|
+
const { name: associationName } = associationStringToNameAndAlias(associationNameAndMaybeAlias);
|
|
1792
|
+
dreams = dreams.filter(dream => dream.hasAssociation(associationName));
|
|
1793
|
+
const groupedDreams = groupBy(dreams, dream => dream.sanitizedConstructorName);
|
|
1794
|
+
return (await Promise.all(Object.keys(groupedDreams).map(key => this._applyOnePreload(associationName, groupedDreams[key], onStatement)))).flat();
|
|
1795
|
+
}
|
|
1796
|
+
async _applyOnePreload(associationName, dreams, onStatement = {}) {
|
|
1797
|
+
if (!dreams.length)
|
|
1798
|
+
return [];
|
|
1799
|
+
const dream = dreams[0];
|
|
1789
1800
|
const { name, alias: _alias } = associationStringToNameAndAlias(associationName);
|
|
1790
1801
|
const alias = _alias || name;
|
|
1791
1802
|
const association = dream['getAssociationMetadata'](name);
|
|
@@ -1793,8 +1804,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1793
1804
|
throw new UnexpectedUndefined();
|
|
1794
1805
|
const dreamClass = dream.constructor;
|
|
1795
1806
|
const dreamClassToHydrate = association.modelCB();
|
|
1796
|
-
if (
|
|
1797
|
-
|
|
1807
|
+
if (Array.isArray(dreamClassToHydrate)) {
|
|
1808
|
+
const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
|
|
1809
|
+
return preloadedPolymorphicBelongsTos;
|
|
1810
|
+
}
|
|
1798
1811
|
const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
|
|
1799
1812
|
const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
|
|
1800
1813
|
columnsToPluck.push(this.namespaceColumn(dreamClass.primaryKey, dreamClass.table));
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import yoctocolors from 'yoctocolors';
|
|
2
|
+
import { indent } from '../../helpers/indent.js';
|
|
3
|
+
export default function printSerializerHierarchyLevel({ serializerAssociationType, serializerAssociationName, associationSerializer, forDisplayDepth, }) {
|
|
4
|
+
const hierarchyLine = '└───';
|
|
5
|
+
const indentation = indent((hierarchyLine.length + 1) * forDisplayDepth, {
|
|
6
|
+
tabWidth: 1,
|
|
7
|
+
});
|
|
8
|
+
const prefix = `${hierarchyLine} `;
|
|
9
|
+
const nestedAssociationDisplay = indentation + `${prefix}${serializerAssociationType} ${yoctocolors.cyan(serializerAssociationName)}`;
|
|
10
|
+
console.log(nestedAssociationDisplay);
|
|
11
|
+
console.log(yoctocolors.gray(indentation +
|
|
12
|
+
indent(prefix.length, { tabWidth: 1 }) +
|
|
13
|
+
associationSerializer.globalName));
|
|
14
|
+
}
|
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
import EnvInternal from '../helpers/EnvInternal.js';
|
|
1
2
|
let _dreamApp = undefined;
|
|
2
3
|
export function cacheDreamApp(dreamconf) {
|
|
3
4
|
_dreamApp = dreamconf;
|
|
4
5
|
}
|
|
5
6
|
export function getCachedDreamAppOrFail() {
|
|
6
|
-
if (!_dreamApp)
|
|
7
|
-
|
|
7
|
+
if (!_dreamApp) {
|
|
8
|
+
const baseErrorMessage = 'Must call `initializeDreamApp` or `initializePsychicApp` before loading cached DreamApp.getOrFail().';
|
|
9
|
+
if (EnvInternal.isTest) {
|
|
10
|
+
throw new Error(`${baseErrorMessage}
|
|
11
|
+
|
|
12
|
+
Check for specs running directly in a \`describe\` or \`context\` block rather than nested within an \`it\` block.`);
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
throw new Error(baseErrorMessage);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
8
18
|
return _dreamApp;
|
|
9
19
|
}
|
|
@@ -19,6 +19,7 @@ export default async function generateMigration({ migrationName, columnsWithType
|
|
|
19
19
|
table: snakeify(pluralize(pascalizePath(fullyQualifiedParentName))),
|
|
20
20
|
columnsWithTypes,
|
|
21
21
|
primaryKeyType: primaryKeyType(),
|
|
22
|
+
stiChildClassName: pascalizePath(fullyQualifiedModelName),
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
25
|
else if (fullyQualifiedModelName) {
|
|
@@ -5,13 +5,17 @@ import foreignKeyTypeFromPrimaryKey from '../db/foreignKeyTypeFromPrimaryKey.js'
|
|
|
5
5
|
import snakeify from '../snakeify.js';
|
|
6
6
|
const STI_TYPE_COLUMN_NAME = 'type';
|
|
7
7
|
const COLUMNS_TO_INDEX = [STI_TYPE_COLUMN_NAME];
|
|
8
|
-
export default function generateMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', } = {}) {
|
|
8
|
+
export default function generateMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', createOrAlter = 'create', stiChildClassName, } = {}) {
|
|
9
9
|
const altering = createOrAlter === 'alter';
|
|
10
10
|
let requireCitextExtension = false;
|
|
11
|
-
const
|
|
11
|
+
const checkConstraints = [];
|
|
12
|
+
const { columnDefs, columnDrops, indexDefs, indexDrops } = columnsWithTypes.reduce((acc, attributeDeclaration) => {
|
|
12
13
|
const { columnDefs, columnDrops, indexDefs, indexDrops } = acc;
|
|
13
|
-
const [nonStandardAttributeName, attributeType, ...descriptors] =
|
|
14
|
-
const
|
|
14
|
+
const [nonStandardAttributeName, attributeType, ...descriptors] = attributeDeclaration.split(':');
|
|
15
|
+
const userWantsThisOptional = optionalFromDescriptors(descriptors);
|
|
16
|
+
// when creating a migration for an STI child, we don't want to include notNull;
|
|
17
|
+
// instead, we'll add a check constraint that uses the STI child class name
|
|
18
|
+
const optional = userWantsThisOptional || !!stiChildClassName;
|
|
15
19
|
const sqlAttributeType = getAttributeType(attributeType, descriptors);
|
|
16
20
|
let attributeName = snakeify(nonStandardAttributeName);
|
|
17
21
|
if (attributeName === undefined)
|
|
@@ -20,6 +24,17 @@ export default function generateMigrationContent({ table, columnsWithTypes = [],
|
|
|
20
24
|
return acc;
|
|
21
25
|
if (attributeType === 'citext')
|
|
22
26
|
requireCitextExtension = true;
|
|
27
|
+
if (stiChildClassName && !userWantsThisOptional) {
|
|
28
|
+
checkConstraints.push(`
|
|
29
|
+
|
|
30
|
+
await db.schema
|
|
31
|
+
.alterTable('${table}')
|
|
32
|
+
.addCheckConstraint(
|
|
33
|
+
'${table}_not_null_${attributeName}',
|
|
34
|
+
sql\`type != '${stiChildClassName}' OR ${attributeName} IS NOT NULL\`,
|
|
35
|
+
)
|
|
36
|
+
.execute()`);
|
|
37
|
+
}
|
|
23
38
|
switch (attributeType) {
|
|
24
39
|
case 'belongs_to':
|
|
25
40
|
columnDefs.push(generateBelongsToStr(attributeName, {
|
|
@@ -96,7 +111,7 @@ ${citextExtension}${generateEnumStatements(columnsWithTypes)} await db.schema
|
|
|
96
111
|
".addColumn('created_at', 'timestamp', col => col.notNull())" +
|
|
97
112
|
newlineDoubleIndent +
|
|
98
113
|
".addColumn('updated_at', 'timestamp', col => col.notNull())"}
|
|
99
|
-
.execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(newlineDoubleIndent)}
|
|
114
|
+
.execute()${indexDefs.length ? `\n${newlineIndent}` : ''}${indexDefs.join(newlineDoubleIndent)}${checkConstraints.join('')}
|
|
100
115
|
}
|
|
101
116
|
|
|
102
117
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
|
@@ -1,4 +1,10 @@
|
|
|
1
1
|
import generateMigrationContent from './generateMigrationContent.js';
|
|
2
|
-
export default function generateStiMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial',
|
|
3
|
-
return generateMigrationContent({
|
|
2
|
+
export default function generateStiMigrationContent({ table, columnsWithTypes = [], primaryKeyType = 'bigserial', stiChildClassName, }) {
|
|
3
|
+
return generateMigrationContent({
|
|
4
|
+
table,
|
|
5
|
+
columnsWithTypes,
|
|
6
|
+
primaryKeyType,
|
|
7
|
+
createOrAlter: 'alter',
|
|
8
|
+
stiChildClassName,
|
|
9
|
+
});
|
|
4
10
|
}
|
package/dist/esm/src/index.js
CHANGED
|
@@ -21,7 +21,9 @@ export { default as Query } from './dream/Query.js';
|
|
|
21
21
|
export { default as Encrypt } from './encrypt/index.js';
|
|
22
22
|
export { default as NonLoadedAssociation } from './errors/associations/NonLoadedAssociation.js';
|
|
23
23
|
export { default as CreateOrFindByFailedToCreateAndFind } from './errors/CreateOrFindByFailedToCreateAndFind.js';
|
|
24
|
+
export { default as CheckConstraintViolation } from './errors/db/CheckConstraintViolation.js';
|
|
24
25
|
export { default as DataTypeColumnTypeMismatch } from './errors/db/DataTypeColumnTypeMismatch.js';
|
|
26
|
+
export { default as NotNullViolation } from './errors/db/NotNullViolation.js';
|
|
25
27
|
export { default as GlobalNameNotSet } from './errors/dream-app/GlobalNameNotSet.js';
|
|
26
28
|
export { default as RecordNotFound } from './errors/RecordNotFound.js';
|
|
27
29
|
export { default as ValidationError } from './errors/ValidationError.js';
|
|
@@ -11,7 +11,8 @@ export function serializerForAssociatedClass(object, associationName, options) {
|
|
|
11
11
|
return null;
|
|
12
12
|
const dream = object;
|
|
13
13
|
const association = dream['getAssociationMetadata'](associationName);
|
|
14
|
-
const
|
|
15
|
-
|
|
14
|
+
const associatedClass = association.modelCB();
|
|
15
|
+
if (Array.isArray(associatedClass))
|
|
16
|
+
throw new Error('rendersOne flatten is incompatible with a polymorphic belongs-to association');
|
|
16
17
|
return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
|
|
17
18
|
}
|