@rvoh/dream 0.44.8 → 0.45.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 +246 -23
- package/dist/cjs/src/cli/index.js +16 -0
- package/dist/cjs/src/db/migration-helpers/DreamMigrationHelpers.js +18 -14
- package/dist/cjs/src/decorators/Decorators.js +2 -1
- package/dist/cjs/src/dream/Query.js +162 -24
- package/dist/cjs/src/dream/internal/extractNestedPaths.js +37 -0
- package/dist/cjs/src/helpers/indent.js +18 -0
- package/dist/cjs/src/serializer/SerializerRenderer.js +3 -18
- package/dist/cjs/src/serializer/builders/DreamSerializerBuilder.js +139 -3
- package/dist/cjs/src/serializer/builders/ObjectSerializerBuilder.js +176 -3
- package/dist/cjs/src/serializer/helpers/serializerForAssociatedClass.js +20 -0
- package/dist/esm/src/Dream.js +246 -23
- package/dist/esm/src/cli/index.js +16 -0
- package/dist/esm/src/db/migration-helpers/DreamMigrationHelpers.js +18 -14
- package/dist/esm/src/decorators/Decorators.js +2 -1
- package/dist/esm/src/dream/Query.js +162 -24
- package/dist/esm/src/dream/internal/extractNestedPaths.js +34 -0
- package/dist/esm/src/helpers/indent.js +15 -0
- package/dist/esm/src/serializer/SerializerRenderer.js +3 -18
- package/dist/esm/src/serializer/builders/DreamSerializerBuilder.js +139 -3
- package/dist/esm/src/serializer/builders/ObjectSerializerBuilder.js +176 -3
- package/dist/esm/src/serializer/helpers/serializerForAssociatedClass.js +17 -0
- package/dist/types/src/Dream.d.ts +218 -24
- package/dist/types/src/db/migration-helpers/DreamMigrationHelpers.d.ts +18 -14
- package/dist/types/src/decorators/Decorators.d.ts +2 -1
- package/dist/types/src/dream/Query.d.ts +154 -25
- package/dist/types/src/dream/internal/extractNestedPaths.d.ts +21 -0
- package/dist/types/src/helpers/indent.d.ts +3 -0
- package/dist/types/src/index.d.ts +1 -1
- package/dist/types/src/serializer/builders/DreamSerializerBuilder.d.ts +176 -5
- package/dist/types/src/serializer/builders/ObjectSerializerBuilder.d.ts +176 -3
- package/dist/types/src/serializer/helpers/serializerForAssociatedClass.d.ts +9 -0
- package/dist/types/src/types/dream.d.ts +2 -1
- package/dist/types/src/types/dream.ts +26 -1
- package/docs/assets/highlight.css +7 -0
- 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/CreateOrFindByFailedToCreateAndFind.html +3 -3
- package/docs/classes/Decorators.html +21 -20
- package/docs/classes/Dream.html +288 -209
- 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 +21 -8
- package/docs/classes/DreamSerializerBuilder.html +66 -2
- 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 +66 -2
- package/docs/classes/Query.html +118 -78
- 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/DreamAssociationMetadata.html +1 -1
- package/docs/types/DreamAttributes.html +1 -1
- package/docs/types/DreamClassAssociationAndStatement.html +1 -0
- 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/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
|
@@ -11,11 +11,13 @@ import CannotPaginateWithLimit from '../errors/pagination/CannotPaginateWithLimi
|
|
|
11
11
|
import CannotPaginateWithOffset from '../errors/pagination/CannotPaginateWithOffset.js';
|
|
12
12
|
import RecordNotFound from '../errors/RecordNotFound.js';
|
|
13
13
|
import cloneDeepSafe from '../helpers/cloneDeepSafe.js';
|
|
14
|
+
import compact from '../helpers/compact.js';
|
|
14
15
|
import isObject from '../helpers/isObject.js';
|
|
15
16
|
import namespaceColumn from '../helpers/namespaceColumn.js';
|
|
16
17
|
import protectAgainstPollutingAssignment from '../helpers/protectAgainstPollutingAssignment.js';
|
|
17
18
|
import ops from '../ops/index.js';
|
|
18
19
|
import computedPaginatePage from './internal/computedPaginatePage.js';
|
|
20
|
+
import extractNestedPaths from './internal/extractNestedPaths.js';
|
|
19
21
|
import PostgresQueryDriver from './QueryDriver/Postgres.js';
|
|
20
22
|
export default class Query {
|
|
21
23
|
/**
|
|
@@ -396,8 +398,8 @@ export default class Query {
|
|
|
396
398
|
* ```
|
|
397
399
|
*
|
|
398
400
|
* @param cb - The callback to call for each found record
|
|
399
|
-
* @param
|
|
400
|
-
* @param
|
|
401
|
+
* @param __namedParameters - Options for batch processing
|
|
402
|
+
* @param __namedParameters.batchSize - The batch size you wish to collect records in. If not provided, it will default to 1000
|
|
401
403
|
* @returns void
|
|
402
404
|
*/
|
|
403
405
|
async findEach(cb, { batchSize = Query.BATCH_SIZES.FIND_EACH } = {}) {
|
|
@@ -421,10 +423,10 @@ export default class Query {
|
|
|
421
423
|
}
|
|
422
424
|
/**
|
|
423
425
|
* Load each specified association using a single SQL query.
|
|
424
|
-
* See {@link
|
|
426
|
+
* See {@link Query.preload} for preloading in separate queries.
|
|
425
427
|
*
|
|
426
428
|
* Note: since leftJoinPreload loads via single query, it has
|
|
427
|
-
* some downsides and that may be avoided using {@link
|
|
429
|
+
* some downsides and that may be avoided using {@link Query.preload}:
|
|
428
430
|
* 1. `limit` and `offset` will be automatically removed
|
|
429
431
|
* 2. `through` associations will bring additional namespaces into the query that can conflict with through associations from other associations, creating an invalid query
|
|
430
432
|
* 3. each nested association will result in an additional record which duplicates data from the outer record. E.g., given `.leftJoinPreload('a', 'b', 'c')`, if each `a` has 10 `b` and each `b` has 10 `c`, then for one `a`, 100 records will be returned, each of which has all of the columns of `a`. `.preload('a', 'b', 'c')` would perform three separate SQL queries, but the data for a single `a` would only be returned once.
|
|
@@ -452,7 +454,7 @@ export default class Query {
|
|
|
452
454
|
}
|
|
453
455
|
/**
|
|
454
456
|
* Load each specified association using a separate SQL query.
|
|
455
|
-
* See {@link
|
|
457
|
+
* See {@link Query.leftJoinPreload} for preloading in a single query.
|
|
456
458
|
*
|
|
457
459
|
* ```ts
|
|
458
460
|
* const user = await User.query().preload('posts', 'comments', { visibilty: 'public' }, 'replies').first()
|
|
@@ -469,6 +471,141 @@ export default class Query {
|
|
|
469
471
|
this.fleshOutJoinStatements([], preloadStatements, preloadOnStatements, null, [...args]);
|
|
470
472
|
return this.clone({ preloadStatements, preloadOnStatements: preloadOnStatements });
|
|
471
473
|
}
|
|
474
|
+
/**
|
|
475
|
+
* Recursively preloads all Dream associations referenced by `rendersOne` and `rendersMany`
|
|
476
|
+
* in a DreamSerializer. This traverses the entire content tree of serializers to automatically
|
|
477
|
+
* load all necessary associations, eliminating N+1 query problems and removing the need to
|
|
478
|
+
* manually remember which associations to preload for serialization.
|
|
479
|
+
*
|
|
480
|
+
* This method decouples data loading code from data rendering code by having the serializer
|
|
481
|
+
* (rendering code) inform the query (loading code) about which associations are needed.
|
|
482
|
+
* As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or
|
|
483
|
+
* modifying existing ones - the loading code automatically adapts without requiring
|
|
484
|
+
* corresponding modifications to preload statements.
|
|
485
|
+
*
|
|
486
|
+
* This method analyzes the serializer (specified by `serializerKey` or 'default') and
|
|
487
|
+
* automatically preloads all associations that will be needed during serialization.
|
|
488
|
+
*
|
|
489
|
+
* ```ts
|
|
490
|
+
* // Instead of manually specifying all associations:
|
|
491
|
+
* await User.preload('posts', 'comments', 'replies').all()
|
|
492
|
+
*
|
|
493
|
+
* // Automatically preload everything needed for serialization:
|
|
494
|
+
* await User.preloadForSerialization({ serializerKey: 'summary' }).all()
|
|
495
|
+
*
|
|
496
|
+
* // Add where conditions to specific associations during preloading:
|
|
497
|
+
* await User.preloadForSerialization({
|
|
498
|
+
* serializerKey: 'detailed',
|
|
499
|
+
* modifierFn: (dreamClass, associationName) => {
|
|
500
|
+
* if (dreamClass.typeof(Post) && associationName === 'comments') {
|
|
501
|
+
* return { and: { published: true } }
|
|
502
|
+
* }
|
|
503
|
+
* }
|
|
504
|
+
* }).all()
|
|
505
|
+
*
|
|
506
|
+
* // Skip preloading specific associations to handle them manually:
|
|
507
|
+
* await User.preloadForSerialization({
|
|
508
|
+
* serializerKey: 'summary',
|
|
509
|
+
* modifierFn: (dreamClass, associationName) => {
|
|
510
|
+
* if (dreamClass.typeof(User) && associationName === 'posts') {
|
|
511
|
+
* return 'omit' // Handle posts preloading separately with custom logic
|
|
512
|
+
* }
|
|
513
|
+
* }
|
|
514
|
+
* })
|
|
515
|
+
* .preload('posts', { and: { featured: true } }) // Custom preloading
|
|
516
|
+
* .all()
|
|
517
|
+
* ```
|
|
518
|
+
*
|
|
519
|
+
* @param opts - Configuration options for serialization preloading
|
|
520
|
+
* @param opts.serializerKey - The serializer key to use for determining which associations to preload. Defaults to 'default'
|
|
521
|
+
* @param opts.modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading
|
|
522
|
+
* @returns A Query with all serialization associations preloaded
|
|
523
|
+
*/
|
|
524
|
+
preloadForSerialization({ serializerKey, modifierFn, } = {}) {
|
|
525
|
+
const preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
|
|
526
|
+
let query = this;
|
|
527
|
+
preloadArgs.forEach(dreamClassAndAssociationNameTupleArray => {
|
|
528
|
+
query = query.preload(...this.convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn));
|
|
529
|
+
});
|
|
530
|
+
return query;
|
|
531
|
+
}
|
|
532
|
+
/**
|
|
533
|
+
* Recursively preloads all Dream associations referenced by `rendersOne` and `rendersMany`
|
|
534
|
+
* in a DreamSerializer using left join preloading. This traverses the entire content tree
|
|
535
|
+
* of serializers to automatically load all necessary associations in a single query,
|
|
536
|
+
* eliminating N+1 query problems and removing the need to manually remember which
|
|
537
|
+
* associations to preload for serialization.
|
|
538
|
+
*
|
|
539
|
+
* This method decouples data loading code from data rendering code by having the serializer
|
|
540
|
+
* (rendering code) inform the query (loading code) about which associations are needed.
|
|
541
|
+
* As serializers evolve over time - adding new `rendersOne` and `rendersMany` calls or
|
|
542
|
+
* modifying existing ones - the loading code automatically adapts without requiring
|
|
543
|
+
* corresponding modifications to left join preload statements.
|
|
544
|
+
*
|
|
545
|
+
* This method analyzes the serializer (specified by `serializerKey` or 'default') and
|
|
546
|
+
* automatically left join preloads all associations that will be needed during serialization.
|
|
547
|
+
*
|
|
548
|
+
* Note: Left join preloading loads all data in a single SQL query but has trade-offs compared
|
|
549
|
+
* to regular preloading. See {@link Dream.leftJoinPreload} for details about limitations.
|
|
550
|
+
*
|
|
551
|
+
* ```ts
|
|
552
|
+
* // Instead of manually specifying all associations:
|
|
553
|
+
* await User.leftJoinPreload('posts', 'comments', 'replies').all()
|
|
554
|
+
*
|
|
555
|
+
* // Automatically left join preload everything needed for serialization:
|
|
556
|
+
* await User.leftJoinPreloadForSerialization({ serializerKey: 'summary' }).all()
|
|
557
|
+
*
|
|
558
|
+
* // Add where conditions to specific associations during left join preloading:
|
|
559
|
+
* await User.leftJoinPreloadForSerialization({
|
|
560
|
+
* serializerKey: 'detailed',
|
|
561
|
+
* modifierFn: (dreamClass, associationName) => {
|
|
562
|
+
* if (dreamClass.typeof(Post) && associationName === 'comments') {
|
|
563
|
+
* return { and: { published: true } }
|
|
564
|
+
* }
|
|
565
|
+
* }
|
|
566
|
+
* }).all()
|
|
567
|
+
*
|
|
568
|
+
* // Skip left join preloading specific associations to handle them manually:
|
|
569
|
+
* await User.leftJoinPreloadForSerialization({
|
|
570
|
+
* serializerKey: 'summary',
|
|
571
|
+
* modifierFn: (dreamClass, associationName) => {
|
|
572
|
+
* if (dreamClass.typeof(User) && associationName === 'posts') {
|
|
573
|
+
* return 'omit' // Handle posts preloading separately with custom logic
|
|
574
|
+
* }
|
|
575
|
+
* }
|
|
576
|
+
* })
|
|
577
|
+
* .preload('posts', { and: { featured: true } }) // Custom preloading instead
|
|
578
|
+
* .all()
|
|
579
|
+
* ```
|
|
580
|
+
*
|
|
581
|
+
* @param opts - Configuration options for serialization preloading
|
|
582
|
+
* @param opts.serializerKey - The serializer key to use for determining which associations to preload. Defaults to 'default'
|
|
583
|
+
* @param opts.modifierFn - Optional callback function to modify or omit specific associations during preloading. Called for each association with the Dream class and association name. Return an object with `and`, `andAny`, or `andNot` properties to add where conditions, return 'omit' to skip preloading that association (useful when you want to handle it manually), or return undefined to use default preloading
|
|
584
|
+
* @returns A Query with all serialization associations left join preloaded
|
|
585
|
+
*/
|
|
586
|
+
leftJoinPreloadForSerialization({ serializerKey, modifierFn, } = {}) {
|
|
587
|
+
const preloadArgs = extractNestedPaths(this.dreamClass['serializationMap'](serializerKey));
|
|
588
|
+
let query = this;
|
|
589
|
+
const counter = { count: 0 };
|
|
590
|
+
preloadArgs.forEach(dreamClassAndAssociationNameTupleArray => {
|
|
591
|
+
query = query.leftJoinPreload(...this.convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn, counter));
|
|
592
|
+
});
|
|
593
|
+
return query;
|
|
594
|
+
}
|
|
595
|
+
convertDreamClassAndAssociationNameTupleArrayToPreloadArgs(dreamClassAndAssociationNameTupleArray, modifierFn, counter) {
|
|
596
|
+
return compact(dreamClassAndAssociationNameTupleArray.flatMap(dreamClassAndAssociationNameTuple => {
|
|
597
|
+
const associationName = dreamClassAndAssociationNameTuple[1];
|
|
598
|
+
const aliasedAssociationName = counter
|
|
599
|
+
? `${dreamClassAndAssociationNameTuple[1]} as drsz${counter.count++}`
|
|
600
|
+
: dreamClassAndAssociationNameTuple[1];
|
|
601
|
+
if (!modifierFn)
|
|
602
|
+
return aliasedAssociationName;
|
|
603
|
+
const modifier = modifierFn(dreamClassAndAssociationNameTuple[0], associationName);
|
|
604
|
+
if (modifier === 'omit')
|
|
605
|
+
return undefined;
|
|
606
|
+
return [aliasedAssociationName, modifier];
|
|
607
|
+
}));
|
|
608
|
+
}
|
|
472
609
|
/**
|
|
473
610
|
* Returns a new Query instance, with the provided
|
|
474
611
|
* joins statement attached
|
|
@@ -783,7 +920,7 @@ export default class Query {
|
|
|
783
920
|
* // [User{name: 'a', id: 99}, User{name: 'a', id: 97}, User{ name: 'b', id: 98 } ...]
|
|
784
921
|
* ```
|
|
785
922
|
*
|
|
786
|
-
* @param
|
|
923
|
+
* @param arg - Either a string or an object specifying order. If a string, the order is implicitly ascending. If the orderStatement is an object, statements will be provided in the order of the keys set in the object
|
|
787
924
|
* @returns A cloned Query with the order clause applied
|
|
788
925
|
*/
|
|
789
926
|
order(arg) {
|
|
@@ -885,7 +1022,7 @@ export default class Query {
|
|
|
885
1022
|
* })
|
|
886
1023
|
* ```
|
|
887
1024
|
*
|
|
888
|
-
* @param
|
|
1025
|
+
* @param dreamTransaction - A DreamTransaction instance (usually collected by calling `ApplicationModel.transaction`)
|
|
889
1026
|
* @returns A cloned Query with the transaction applied
|
|
890
1027
|
*
|
|
891
1028
|
*/
|
|
@@ -1082,6 +1219,7 @@ export default class Query {
|
|
|
1082
1219
|
* // 2
|
|
1083
1220
|
* ```
|
|
1084
1221
|
*
|
|
1222
|
+
* @param opts - Pagination options
|
|
1085
1223
|
* @param opts.page - the page number that you want to fetch results for
|
|
1086
1224
|
* @param opts.pageSize - the number of results per page (optional)
|
|
1087
1225
|
* @returns results.recordCount - A number representing the total number of records matching your query
|
|
@@ -1245,11 +1383,11 @@ export default class Query {
|
|
|
1245
1383
|
* // 12
|
|
1246
1384
|
* ```
|
|
1247
1385
|
*
|
|
1248
|
-
* @param
|
|
1249
|
-
* @param
|
|
1250
|
-
* @param
|
|
1251
|
-
* @param
|
|
1252
|
-
* @param
|
|
1386
|
+
* @param __namedParameters - Options for destroying the instance
|
|
1387
|
+
* @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
|
|
1388
|
+
* @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1389
|
+
* @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
|
|
1390
|
+
* @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
|
|
1253
1391
|
* @returns The number of records that were removed
|
|
1254
1392
|
*/
|
|
1255
1393
|
async destroy({ skipHooks, cascade, } = {}) {
|
|
@@ -1285,18 +1423,18 @@ export default class Query {
|
|
|
1285
1423
|
* were destroyed.
|
|
1286
1424
|
*
|
|
1287
1425
|
* To destroy without bypassing the SoftDelete
|
|
1288
|
-
* decorator, use {@link Query.
|
|
1426
|
+
* decorator, use {@link Query.destroy} instead.
|
|
1289
1427
|
*
|
|
1290
1428
|
* ```ts
|
|
1291
1429
|
* await User.where({ email: ops.ilike('%burpcollaborator%') }).reallyDestroy()
|
|
1292
1430
|
* // 12
|
|
1293
1431
|
* ```
|
|
1294
1432
|
*
|
|
1295
|
-
* @param
|
|
1296
|
-
* @param
|
|
1297
|
-
* @param
|
|
1298
|
-
* @param
|
|
1299
|
-
* @param
|
|
1433
|
+
* @param __namedParameters - Options for destroying the instance
|
|
1434
|
+
* @param __namedParameters.skipHooks - If true, skips applying model hooks during the destroy operation. Defaults to false
|
|
1435
|
+
* @param __namedParameters.cascade - If false, skips destroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1436
|
+
* @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade destroying. Defaults to false
|
|
1437
|
+
* @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade destroying. Defaults to an empty array
|
|
1300
1438
|
* @returns The number of records that were removed
|
|
1301
1439
|
*/
|
|
1302
1440
|
async reallyDestroy({ skipHooks, cascade, } = {}) {
|
|
@@ -1314,11 +1452,11 @@ export default class Query {
|
|
|
1314
1452
|
* // 12
|
|
1315
1453
|
* ```
|
|
1316
1454
|
*
|
|
1317
|
-
* @param
|
|
1318
|
-
* @param
|
|
1319
|
-
* @param
|
|
1320
|
-
* @param
|
|
1321
|
-
* @param
|
|
1455
|
+
* @param __namedParameters - Options for undestroying the instance
|
|
1456
|
+
* @param __namedParameters.skipHooks - If true, skips applying model hooks during the undestroy operation. Defaults to false
|
|
1457
|
+
* @param __namedParameters.cascade - If false, skips undestroying associations marked `dependent: 'destroy'`. Defaults to true
|
|
1458
|
+
* @param __namedParameters.bypassAllDefaultScopes - If true, bypasses all default scopes when cascade undestroying. Defaults to false
|
|
1459
|
+
* @param __namedParameters.defaultScopesToBypass - An array of default scope names to bypass when cascade undestroying (soft delete is always bypassed). Defaults to an empty array
|
|
1322
1460
|
* @returns The number of records that were removed
|
|
1323
1461
|
*/
|
|
1324
1462
|
async undestroy({ cascade, skipHooks, } = {}) {
|
|
@@ -1346,7 +1484,7 @@ export default class Query {
|
|
|
1346
1484
|
* though cascading may still happen at the database level.
|
|
1347
1485
|
*
|
|
1348
1486
|
* To apply model hooks and association dependent destroy,
|
|
1349
|
-
* use {@link Query.
|
|
1487
|
+
* use {@link Query.destroy} instead.
|
|
1350
1488
|
*
|
|
1351
1489
|
* ```ts
|
|
1352
1490
|
* await User.where({ email: ops.ilike('%burpcollaborator%').delete() })
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Turns RecursiveSerializerInfo into an array of arrays of tuples,
|
|
3
|
+
* each tuple including a Dream class and an association name
|
|
4
|
+
*
|
|
5
|
+
* E.g., returns the following:
|
|
6
|
+
* ```ts
|
|
7
|
+
* [
|
|
8
|
+
* [[Post, 'a'], [Pet, 'b']],
|
|
9
|
+
* [[Post, 'a'], [Balloon, 'd'], [BalloonLine, 'e']]
|
|
10
|
+
* ]
|
|
11
|
+
* ```
|
|
12
|
+
*/
|
|
13
|
+
export default function extractNestedPaths(obj) {
|
|
14
|
+
const paths = [];
|
|
15
|
+
function traverse(current, currentPath) {
|
|
16
|
+
const associationNames = Object.keys(current);
|
|
17
|
+
if (associationNames.length === 0) {
|
|
18
|
+
paths.push([...currentPath]);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
for (const associationName of associationNames) {
|
|
22
|
+
const serializerInfo = current[associationName];
|
|
23
|
+
if (serializerInfo === undefined)
|
|
24
|
+
throw new Error('shouldn’t be undefined');
|
|
25
|
+
const dreamClass = serializerInfo.parentDreamClass;
|
|
26
|
+
const nestedSerializerInfo = serializerInfo.nestedSerializerInfo;
|
|
27
|
+
const tuple = [dreamClass, associationName];
|
|
28
|
+
const newPath = [...currentPath, tuple];
|
|
29
|
+
traverse(nestedSerializerInfo, newPath);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
traverse(obj, []);
|
|
33
|
+
return paths;
|
|
34
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function indent(number, { tabWidth = 2 } = {}) {
|
|
2
|
+
let spacesString = '';
|
|
3
|
+
const indentationUnit = indentationUnitString(tabWidth);
|
|
4
|
+
for (let i = 0; i < number; i++) {
|
|
5
|
+
spacesString += indentationUnit;
|
|
6
|
+
}
|
|
7
|
+
return spacesString;
|
|
8
|
+
}
|
|
9
|
+
function indentationUnitString(number) {
|
|
10
|
+
let spacesString = '';
|
|
11
|
+
for (let i = 0; i < number; i++) {
|
|
12
|
+
spacesString += ' ';
|
|
13
|
+
}
|
|
14
|
+
return spacesString;
|
|
15
|
+
}
|
|
@@ -6,7 +6,8 @@ import round from '../helpers/round.js';
|
|
|
6
6
|
import snakeify from '../helpers/snakeify.js';
|
|
7
7
|
import DreamSerializerBuilder from './builders/DreamSerializerBuilder.js';
|
|
8
8
|
import ObjectSerializerBuilder from './builders/ObjectSerializerBuilder.js';
|
|
9
|
-
import inferSerializerFromDreamOrViewModel
|
|
9
|
+
import inferSerializerFromDreamOrViewModel from './helpers/inferSerializerFromDreamOrViewModel.js';
|
|
10
|
+
import { serializerForAssociatedClass } from './helpers/serializerForAssociatedClass.js';
|
|
10
11
|
export default class SerializerRenderer {
|
|
11
12
|
serializerBuilder;
|
|
12
13
|
passthroughData;
|
|
@@ -51,7 +52,7 @@ export default class SerializerRenderer {
|
|
|
51
52
|
case 'delegatedAttribute': {
|
|
52
53
|
const outputAttributeName = this.setCase(attribute.options?.as ?? attribute.name);
|
|
53
54
|
const target = data[attribute.targetName];
|
|
54
|
-
const value = target[attribute.name] ?? attribute.options?.default;
|
|
55
|
+
const value = target?.[attribute.name] ?? attribute.options?.default;
|
|
55
56
|
accumulator[outputAttributeName] = applyRenderingOptionsToAttribute(value, attribute.options, this.passthroughData, this.renderOpts);
|
|
56
57
|
return accumulator;
|
|
57
58
|
}
|
|
@@ -201,19 +202,3 @@ function serializerForAssociatedObject(associatedObject, options) {
|
|
|
201
202
|
return options.serializer;
|
|
202
203
|
return inferSerializerFromDreamOrViewModel(associatedObject, options.serializerKey);
|
|
203
204
|
}
|
|
204
|
-
/**
|
|
205
|
-
* Only used when flatten: true, and the associated model is null, in which case,
|
|
206
|
-
* we need something to determine the keys that will be flattened into the
|
|
207
|
-
* rendering serializer
|
|
208
|
-
*/
|
|
209
|
-
function serializerForAssociatedClass(object, associationName, options) {
|
|
210
|
-
if (options.serializer)
|
|
211
|
-
return options.serializer;
|
|
212
|
-
if (!object.isDreamInstance)
|
|
213
|
-
return null;
|
|
214
|
-
const dream = object;
|
|
215
|
-
const association = dream['getAssociationMetadata'](associationName);
|
|
216
|
-
const associatedClasses = association.modelCB();
|
|
217
|
-
const associatedClass = Array.isArray(associatedClasses) ? associatedClasses[0] : associatedClasses;
|
|
218
|
-
return inferSerializersFromDreamClassOrViewModelClass(associatedClass, options.serializerKey)[0] ?? null;
|
|
219
|
-
}
|
|
@@ -20,15 +20,74 @@ export default class DreamSerializerBuilder {
|
|
|
20
20
|
});
|
|
21
21
|
return this;
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Includes an attribute from a nested object in the serialized output.
|
|
25
|
+
*
|
|
26
|
+
* Serializes an attribute from a target object. If the target object or
|
|
27
|
+
* the delegated attribute is null/undefined, the `default` option value
|
|
28
|
+
* will be used if provided.
|
|
29
|
+
*
|
|
30
|
+
* @param targetName - The property name containing the target object
|
|
31
|
+
* @param name - The attribute name within the target object
|
|
32
|
+
* @param options - Configuration options including OpenAPI schema, default value, and output customization
|
|
33
|
+
* @returns The serializer builder for method chaining
|
|
34
|
+
*
|
|
35
|
+
* @example
|
|
36
|
+
* ```typescript
|
|
37
|
+
* // Delegate to user.email
|
|
38
|
+
* .delegatedAttribute('user', 'email', {
|
|
39
|
+
* openapi: { type: 'string', format: 'email' }
|
|
40
|
+
* })
|
|
41
|
+
*
|
|
42
|
+
* // With default value for null target or attribute
|
|
43
|
+
* .delegatedAttribute('user', 'displayName', {
|
|
44
|
+
* openapi: { type: 'string' },
|
|
45
|
+
* default: 'Unknown User'
|
|
46
|
+
* })
|
|
47
|
+
* ```
|
|
48
|
+
*
|
|
49
|
+
* See: {@link https://your-docs-url.com/docs/serializers/attributes | Serializer Attributes Documentation}
|
|
50
|
+
*/
|
|
23
51
|
delegatedAttribute(targetName, name, options) {
|
|
24
52
|
this.attributes.push({
|
|
25
53
|
type: 'delegatedAttribute',
|
|
26
|
-
targetName,
|
|
54
|
+
targetName: targetName,
|
|
27
55
|
name: name,
|
|
28
56
|
options: options ?? {},
|
|
29
57
|
});
|
|
30
58
|
return this;
|
|
31
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Includes a computed value in the serialized output.
|
|
62
|
+
*
|
|
63
|
+
* Executes a callback function to generate a custom attribute value.
|
|
64
|
+
* The `openapi` option is always required since the return type cannot be inferred.
|
|
65
|
+
*
|
|
66
|
+
* @param name - The attribute name for the computed value
|
|
67
|
+
* @param fn - Callback function that returns the computed value
|
|
68
|
+
* @param options - Configuration options including required OpenAPI schema and optional flattening
|
|
69
|
+
* @returns The serializer builder for method chaining
|
|
70
|
+
*
|
|
71
|
+
* @example
|
|
72
|
+
* ```typescript
|
|
73
|
+
* // Simple computed value
|
|
74
|
+
* .customAttribute('initials', () =>
|
|
75
|
+
* `${user.firstName?.[0]}${user.lastName?.[0]}`.toUpperCase(),
|
|
76
|
+
* { openapi: { type: 'string' } }
|
|
77
|
+
* )
|
|
78
|
+
*
|
|
79
|
+
* // Flattened object properties
|
|
80
|
+
* .customAttribute('metadata', () => ({ age: 30, city: 'NYC' }), {
|
|
81
|
+
* flatten: true,
|
|
82
|
+
* openapi: {
|
|
83
|
+
* age: { type: 'integer' },
|
|
84
|
+
* city: { type: 'string' }
|
|
85
|
+
* }
|
|
86
|
+
* })
|
|
87
|
+
* ```
|
|
88
|
+
*
|
|
89
|
+
* See: {@link https://your-docs-url.com/docs/serializers/attributes | Serializer Attributes Documentation}
|
|
90
|
+
*/
|
|
32
91
|
customAttribute(name, fn, options) {
|
|
33
92
|
this.attributes.push({
|
|
34
93
|
type: 'customAttribute',
|
|
@@ -38,22 +97,99 @@ export default class DreamSerializerBuilder {
|
|
|
38
97
|
});
|
|
39
98
|
return this;
|
|
40
99
|
}
|
|
100
|
+
/**
|
|
101
|
+
* Includes a single associated object in the serialized output.
|
|
102
|
+
*
|
|
103
|
+
* When rendering a Dream association, the OpenAPI shape is automatically
|
|
104
|
+
* inferred from that associated Dream's serializer.
|
|
105
|
+
*
|
|
106
|
+
* @param name - The association property name
|
|
107
|
+
* @param options - Configuration options for serialization and schema definition
|
|
108
|
+
* @returns The serializer builder for method chaining
|
|
109
|
+
*
|
|
110
|
+
* @example
|
|
111
|
+
* ```typescript
|
|
112
|
+
* // DreamSerializer with inference
|
|
113
|
+
* .rendersOne('user') // Infers from Dream association
|
|
114
|
+
*
|
|
115
|
+
* // With specific serializer
|
|
116
|
+
* .rendersOne('user', { serializerKey: 'summary' })
|
|
117
|
+
*
|
|
118
|
+
* // ObjectSerializer (explicit configuration required)
|
|
119
|
+
* .rendersOne('owner', UserSerializer, {
|
|
120
|
+
* openapi: { $ref: '#/components/schemas/User' }
|
|
121
|
+
* })
|
|
122
|
+
* ```
|
|
123
|
+
*
|
|
124
|
+
* See: {@link https://your-docs-url.com/docs/serializers/associations | Serializer Associations Documentation}
|
|
125
|
+
*/
|
|
41
126
|
rendersOne(name, options) {
|
|
42
127
|
this.attributes.push({
|
|
43
128
|
type: 'rendersOne',
|
|
44
|
-
name,
|
|
129
|
+
name: name,
|
|
45
130
|
options: options ?? {},
|
|
46
131
|
});
|
|
47
132
|
return this;
|
|
48
133
|
}
|
|
134
|
+
/**
|
|
135
|
+
* Includes an array of associated objects in the serialized output.
|
|
136
|
+
*
|
|
137
|
+
* When rendering a Dream association, the OpenAPI shape is automatically
|
|
138
|
+
* inferred from that associated Dream's serializer.
|
|
139
|
+
*
|
|
140
|
+
* @param name - The association property name (should be an array)
|
|
141
|
+
* @param options - Configuration options for serialization and schema definition
|
|
142
|
+
* @returns The serializer builder for method chaining
|
|
143
|
+
*
|
|
144
|
+
* @example
|
|
145
|
+
* ```typescript
|
|
146
|
+
* // DreamSerializer with inference
|
|
147
|
+
* .rendersMany('posts') // Infers from Dream association
|
|
148
|
+
*
|
|
149
|
+
* // With specific serializer
|
|
150
|
+
* .rendersMany('posts', { serializerKey: 'summary' })
|
|
151
|
+
*
|
|
152
|
+
* // ObjectSerializer (explicit configuration required)
|
|
153
|
+
* .rendersMany('articles', ArticleSerializer, {
|
|
154
|
+
* openapi: {
|
|
155
|
+
* type: 'array',
|
|
156
|
+
* items: { $ref: '#/components/schemas/Article' }
|
|
157
|
+
* }
|
|
158
|
+
* })
|
|
159
|
+
* ```
|
|
160
|
+
*
|
|
161
|
+
* See: {@link https://your-docs-url.com/docs/serializers/associations | Serializer Associations Documentation}
|
|
162
|
+
*/
|
|
49
163
|
rendersMany(name, options) {
|
|
50
164
|
this.attributes.push({
|
|
51
165
|
type: 'rendersMany',
|
|
52
|
-
name,
|
|
166
|
+
name: name,
|
|
53
167
|
options: options ?? {},
|
|
54
168
|
});
|
|
55
169
|
return this;
|
|
56
170
|
}
|
|
171
|
+
/**
|
|
172
|
+
* Executes the serializer and returns the serialized output.
|
|
173
|
+
*
|
|
174
|
+
* This method processes all defined attributes, custom attributes, delegated attributes,
|
|
175
|
+
* and associations to produce the final serialized object. The result is suitable for
|
|
176
|
+
* JSON.stringify() and API responses.
|
|
177
|
+
*
|
|
178
|
+
* @param passthrough - Additional data to pass through to nested serializers
|
|
179
|
+
* @param opts - Rendering options for customizing the serialization process
|
|
180
|
+
* @returns The serialized object
|
|
181
|
+
*
|
|
182
|
+
* @example
|
|
183
|
+
* ```typescript
|
|
184
|
+
* const result = UserSerializer(user).render()
|
|
185
|
+
* // Returns: { id: 1, email: 'user@example.com', ... }
|
|
186
|
+
*
|
|
187
|
+
* // With passthrough data
|
|
188
|
+
* const result = UserSerializer(user).render({ currentUserId: 123 })
|
|
189
|
+
* ```
|
|
190
|
+
*
|
|
191
|
+
* See: {@link https://your-docs-url.com/docs/serializers/render | Serializer Rendering Documentation}
|
|
192
|
+
*/
|
|
57
193
|
render(passthrough = {}, opts = {}) {
|
|
58
194
|
return new SerializerRenderer(this, passthrough, opts).render();
|
|
59
195
|
}
|