@rvoh/dream 1.2.0 → 1.3.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.
- package/dist/cjs/src/Dream.js +99 -31
- package/dist/cjs/src/db/errors.js +5 -1
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +46 -31
- 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/DataTypeColumnTypeMismatch.js +19 -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/helpers/sqlAttributes.js +7 -1
- package/dist/cjs/src/index.js +4 -2
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +3 -2
- package/dist/esm/src/Dream.js +99 -31
- package/dist/esm/src/db/errors.js +5 -1
- package/dist/esm/src/dream/QueryDriver/Kysely.js +46 -31
- 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/DataTypeColumnTypeMismatch.js +16 -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/helpers/sqlAttributes.js +7 -1
- package/dist/esm/src/index.js +1 -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 +3 -1
- 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/DataTypeColumnTypeMismatch.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 +1 -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/CliFileWriter.html +2 -2
- package/docs/classes/CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/DataTypeColumnTypeMismatch.html +14 -0
- 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/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 +1 -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 +2 -2
- package/CHANGELOG.md +0 -48
package/dist/esm/src/Dream.js
CHANGED
|
@@ -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 = [
|
|
@@ -7,8 +7,12 @@
|
|
|
7
7
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
import pg from 'pg';
|
|
10
|
+
const UNIQUE_CONSTRAINT_VIOLATION = 'UNIQUE_CONSTRAINT_VIOLATION';
|
|
11
|
+
const INVALID_INPUT_SYNTAX = 'INVALID_INPUT_SYNTAX';
|
|
10
12
|
export const PG_ERRORS = {
|
|
11
|
-
23505:
|
|
13
|
+
'23505': UNIQUE_CONSTRAINT_VIOLATION,
|
|
14
|
+
'22P02': INVALID_INPUT_SYNTAX,
|
|
15
|
+
'22007': INVALID_INPUT_SYNTAX,
|
|
12
16
|
};
|
|
13
17
|
function pgErrorFromCode(code) {
|
|
14
18
|
return PG_ERRORS[code] || null;
|
|
@@ -3,6 +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
7
|
import _db from '../../db/index.js';
|
|
7
8
|
import associationToGetterSetterProp from '../../decorators/field/association/associationToGetterSetterProp.js';
|
|
8
9
|
import PackageManager from '../../dream-app/helpers/PackageManager.js';
|
|
@@ -17,6 +18,7 @@ import MissingThroughAssociationSource from '../../errors/associations/MissingTh
|
|
|
17
18
|
import ThroughAssociationConditionsIncompatibleWithThroughAssociationSource from '../../errors/associations/ThroughAssociationConditionsIncompatibleWithThroughAssociationSource.js';
|
|
18
19
|
import CannotNegateSimilarityClause from '../../errors/CannotNegateSimilarityClause.js';
|
|
19
20
|
import CannotPassUndefinedAsAValueToAWhereClause from '../../errors/CannotPassUndefinedAsAValueToAWhereClause.js';
|
|
21
|
+
import DataTypeColumnTypeMismatch from '../../errors/db/DataTypeColumnTypeMismatch.js';
|
|
20
22
|
import UnexpectedUndefined from '../../errors/UnexpectedUndefined.js';
|
|
21
23
|
import CalendarDate from '../../helpers/CalendarDate.js';
|
|
22
24
|
import camelize from '../../helpers/camelize.js';
|
|
@@ -29,6 +31,7 @@ import _dropDb from '../../helpers/db/dropDb.js';
|
|
|
29
31
|
import loadPgClient from '../../helpers/db/loadPgClient.js';
|
|
30
32
|
import runMigration from '../../helpers/db/runMigration.js';
|
|
31
33
|
import EnvInternal from '../../helpers/EnvInternal.js';
|
|
34
|
+
import groupBy from '../../helpers/groupBy.js';
|
|
32
35
|
import isEmpty from '../../helpers/isEmpty.js';
|
|
33
36
|
import namespaceColumn from '../../helpers/namespaceColumn.js';
|
|
34
37
|
import normalizeUnicode from '../../helpers/normalizeUnicode.js';
|
|
@@ -413,19 +416,31 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
413
416
|
static async saveDream(dream, txn = null) {
|
|
414
417
|
const db = txn?.kyselyTransaction ?? _db('primary');
|
|
415
418
|
const sqlifiedAttributes = sqlAttributes(dream);
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
419
|
+
try {
|
|
420
|
+
if (dream.isPersisted) {
|
|
421
|
+
const query = db
|
|
422
|
+
.updateTable(dream.table)
|
|
423
|
+
.set(sqlifiedAttributes)
|
|
424
|
+
.where(namespaceColumn(dream.primaryKey, dream.table), '=', dream.primaryKeyValue);
|
|
425
|
+
return await executeDatabaseQuery(query.returning([...dream.columns()]), 'executeTakeFirstOrThrow');
|
|
426
|
+
}
|
|
427
|
+
else {
|
|
428
|
+
const query = db
|
|
429
|
+
.insertInto(dream.table)
|
|
430
|
+
.values(sqlifiedAttributes)
|
|
431
|
+
.returning([...dream.columns()]);
|
|
432
|
+
return await executeDatabaseQuery(query, 'executeTakeFirstOrThrow');
|
|
433
|
+
}
|
|
422
434
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
435
|
+
catch (error) {
|
|
436
|
+
switch (pgErrorType(error)) {
|
|
437
|
+
case 'INVALID_INPUT_SYNTAX':
|
|
438
|
+
throw new DataTypeColumnTypeMismatch({
|
|
439
|
+
dream,
|
|
440
|
+
error: error instanceof Error ? error : new Error('database column type error'),
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
throw error;
|
|
429
444
|
}
|
|
430
445
|
}
|
|
431
446
|
dbConnectionType(sqlCommandType) {
|
|
@@ -1188,7 +1203,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1188
1203
|
const keys = Object.keys(preloadStatement);
|
|
1189
1204
|
for (const key of keys) {
|
|
1190
1205
|
const nestedDreams = await this.applyOnePreload(key, dream, this.applyablePreloadOnStatements(preloadOnStatements[key]));
|
|
1191
|
-
if (nestedDreams) {
|
|
1206
|
+
if (nestedDreams.length) {
|
|
1192
1207
|
await this.applyPreload(preloadStatement[key], preloadOnStatements[key], nestedDreams);
|
|
1193
1208
|
}
|
|
1194
1209
|
}
|
|
@@ -1683,18 +1698,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1683
1698
|
*
|
|
1684
1699
|
* Used to preload polymorphic belongs to associations
|
|
1685
1700
|
*/
|
|
1686
|
-
async preloadPolymorphicBelongsTo(association, dreams) {
|
|
1687
|
-
|
|
1688
|
-
throw new Error(`Association ${association.as} points to an array of models but is not designated polymorphic`);
|
|
1689
|
-
if (association.type !== 'BelongsTo')
|
|
1690
|
-
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.`);
|
|
1691
|
-
const nestedDreamsForNextRoundOfPreloading = [];
|
|
1692
|
-
for (const associatedModel of association.modelCB()) {
|
|
1693
|
-
await this.preloadPolymorphicAssociationModel(dreams, association, associatedModel, nestedDreamsForNextRoundOfPreloading);
|
|
1694
|
-
}
|
|
1695
|
-
return nestedDreamsForNextRoundOfPreloading;
|
|
1701
|
+
async preloadPolymorphicBelongsTo(association, associatedModels, dreams) {
|
|
1702
|
+
return compact(await Promise.all(associatedModels.map(associatedModel => this.preloadPolymorphicAssociationModel(dreams, association, associatedModel)))).flat();
|
|
1696
1703
|
}
|
|
1697
|
-
async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass
|
|
1704
|
+
async preloadPolymorphicAssociationModel(dreams, association, associatedDreamClass) {
|
|
1698
1705
|
const relevantAssociatedModels = dreams.filter((dream) => {
|
|
1699
1706
|
const field = association.foreignKeyTypeField();
|
|
1700
1707
|
return dream[field] === associatedDreamClass['stiBaseClassOrOwnClassName'] || dream[field] === null;
|
|
@@ -1713,7 +1720,6 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1713
1720
|
[associatedDreamClass.primaryKey]: relevantAssociatedModels.map((dream) => dream[association.foreignKey()]),
|
|
1714
1721
|
})
|
|
1715
1722
|
.all();
|
|
1716
|
-
loadedAssociations.forEach((loadedAssociation) => nestedDreamsForNextRoundOfPreloading.push(loadedAssociation));
|
|
1717
1723
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
1718
1724
|
// Associate each loaded association with each dream based on primary key and foreign key type
|
|
1719
1725
|
//////////////////////////////////////////////////////////////////////////////////////////////
|
|
@@ -1728,6 +1734,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1728
1734
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1729
1735
|
// end: Associate each loaded association with each dream based on primary key and foreign key type
|
|
1730
1736
|
///////////////////////////////////////////////////////////////////////////////////////////////////
|
|
1737
|
+
return loadedAssociations;
|
|
1731
1738
|
}
|
|
1732
1739
|
}
|
|
1733
1740
|
/**
|
|
@@ -1766,12 +1773,18 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1766
1773
|
*
|
|
1767
1774
|
* Applies a preload statement
|
|
1768
1775
|
*/
|
|
1769
|
-
async applyOnePreload(
|
|
1776
|
+
async applyOnePreload(associationNameAndMaybeAlias, dreams, onStatement = {}) {
|
|
1770
1777
|
if (!Array.isArray(dreams))
|
|
1771
1778
|
dreams = [dreams];
|
|
1772
|
-
const
|
|
1773
|
-
|
|
1774
|
-
|
|
1779
|
+
const { name: associationName } = associationStringToNameAndAlias(associationNameAndMaybeAlias);
|
|
1780
|
+
dreams = dreams.filter(dream => dream.hasAssociation(associationName));
|
|
1781
|
+
const groupedDreams = groupBy(dreams, dream => dream.sanitizedConstructorName);
|
|
1782
|
+
return (await Promise.all(Object.keys(groupedDreams).map(key => this._applyOnePreload(associationName, groupedDreams[key], onStatement)))).flat();
|
|
1783
|
+
}
|
|
1784
|
+
async _applyOnePreload(associationName, dreams, onStatement = {}) {
|
|
1785
|
+
if (!dreams.length)
|
|
1786
|
+
return [];
|
|
1787
|
+
const dream = dreams[0];
|
|
1775
1788
|
const { name, alias: _alias } = associationStringToNameAndAlias(associationName);
|
|
1776
1789
|
const alias = _alias || name;
|
|
1777
1790
|
const association = dream['getAssociationMetadata'](name);
|
|
@@ -1779,8 +1792,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
1779
1792
|
throw new UnexpectedUndefined();
|
|
1780
1793
|
const dreamClass = dream.constructor;
|
|
1781
1794
|
const dreamClassToHydrate = association.modelCB();
|
|
1782
|
-
if (
|
|
1783
|
-
|
|
1795
|
+
if (Array.isArray(dreamClassToHydrate)) {
|
|
1796
|
+
const preloadedPolymorphicBelongsTos = await this.preloadPolymorphicBelongsTo(association, dreamClassToHydrate, dreams);
|
|
1797
|
+
return preloadedPolymorphicBelongsTos;
|
|
1798
|
+
}
|
|
1784
1799
|
const dreamClassToHydrateColumns = [...dreamClassToHydrate.columns()];
|
|
1785
1800
|
const columnsToPluck = dreamClassToHydrateColumns.map(column => this.namespaceColumn(column.toString(), alias));
|
|
1786
1801
|
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
|
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
export default class DataTypeColumnTypeMismatch extends Error {
|
|
2
|
+
dream;
|
|
3
|
+
error;
|
|
4
|
+
constructor({ dream, error }) {
|
|
5
|
+
super();
|
|
6
|
+
this.dream = dream;
|
|
7
|
+
this.error = error;
|
|
8
|
+
}
|
|
9
|
+
get message() {
|
|
10
|
+
return `\
|
|
11
|
+
Failed to save ${this.dream.sanitizedConstructorName}:
|
|
12
|
+
|
|
13
|
+
${this.error.message}
|
|
14
|
+
`;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -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
|
}
|
|
@@ -37,6 +37,12 @@ export default function sqlAttributes(dream) {
|
|
|
37
37
|
return result;
|
|
38
38
|
}, {});
|
|
39
39
|
}
|
|
40
|
+
/**
|
|
41
|
+
* Convert datetimes to UTC
|
|
42
|
+
*/
|
|
40
43
|
function valueToDatetime(val) {
|
|
41
|
-
|
|
44
|
+
if (typeof val !== 'string')
|
|
45
|
+
return val;
|
|
46
|
+
const datetime = DateTime.fromISO(val, { zone: 'UTC' });
|
|
47
|
+
return datetime.isValid ? datetime : val;
|
|
42
48
|
}
|
package/dist/esm/src/index.js
CHANGED
|
@@ -21,6 +21,7 @@ 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 DataTypeColumnTypeMismatch } from './errors/db/DataTypeColumnTypeMismatch.js';
|
|
24
25
|
export { default as GlobalNameNotSet } from './errors/dream-app/GlobalNameNotSet.js';
|
|
25
26
|
export { default as RecordNotFound } from './errors/RecordNotFound.js';
|
|
26
27
|
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
|
}
|