@rvoh/dream 2.32.0 → 2.34.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/README.md +1 -1
- package/dist/cjs/src/Dream.js +14 -1
- package/dist/cjs/src/cli/index.js +4 -4
- package/dist/cjs/src/db/helpers/syncDbTypesFiles.js +6 -7
- package/dist/cjs/src/decorators/Decorators.js +20 -0
- package/dist/cjs/src/decorators/field/lifecycle/AfterCreateCommit.js +4 -0
- package/dist/cjs/src/decorators/field/lifecycle/AfterDestroyCommit.js +4 -0
- package/dist/cjs/src/decorators/field/lifecycle/AfterSaveCommit.js +4 -0
- package/dist/cjs/src/decorators/field/lifecycle/AfterUpdateCommit.js +4 -0
- package/dist/cjs/src/dream/DreamTransaction.js +14 -3
- package/dist/cjs/src/dream/QueryDriver/Base.js +7 -0
- package/dist/cjs/src/dream/QueryDriver/Kysely.js +8 -1
- package/dist/cjs/src/dream/QueryDriver/Postgres.js +7 -1
- package/dist/cjs/src/dream/QueryDriver/helpers/pg/loadPgClient.js +29 -1
- package/dist/cjs/src/dream/internal/runHooksFor.js +16 -5
- package/dist/cjs/src/dream/internal/withConcurrentWriterRetry.js +6 -1
- package/dist/esm/src/Dream.js +14 -1
- package/dist/esm/src/cli/index.js +4 -4
- package/dist/esm/src/db/helpers/syncDbTypesFiles.js +6 -7
- package/dist/esm/src/decorators/Decorators.js +20 -0
- package/dist/esm/src/decorators/field/lifecycle/AfterCreateCommit.js +4 -0
- package/dist/esm/src/decorators/field/lifecycle/AfterDestroyCommit.js +4 -0
- package/dist/esm/src/decorators/field/lifecycle/AfterSaveCommit.js +4 -0
- package/dist/esm/src/decorators/field/lifecycle/AfterUpdateCommit.js +4 -0
- package/dist/esm/src/dream/DreamTransaction.js +14 -3
- package/dist/esm/src/dream/QueryDriver/Base.js +7 -0
- package/dist/esm/src/dream/QueryDriver/Kysely.js +8 -1
- package/dist/esm/src/dream/QueryDriver/Postgres.js +7 -1
- package/dist/esm/src/dream/QueryDriver/helpers/pg/loadPgClient.js +29 -1
- package/dist/esm/src/dream/internal/runHooksFor.js +16 -5
- package/dist/esm/src/dream/internal/withConcurrentWriterRetry.js +6 -1
- package/dist/types/src/Dream.d.ts +14 -1
- package/dist/types/src/cli/index.d.ts +7 -6
- package/dist/types/src/decorators/Decorators.d.ts +24 -4
- package/dist/types/src/decorators/field/lifecycle/AfterCreateCommit.d.ts +6 -2
- package/dist/types/src/decorators/field/lifecycle/AfterDestroyCommit.d.ts +6 -1
- package/dist/types/src/decorators/field/lifecycle/AfterSaveCommit.d.ts +6 -2
- package/dist/types/src/decorators/field/lifecycle/AfterUpdateCommit.d.ts +6 -2
- package/dist/types/src/dream/DreamTransaction.d.ts +8 -1
- package/dist/types/src/dream/QueryDriver/Base.d.ts +3 -0
- package/dist/types/src/dream/QueryDriver/Kysely.d.ts +1 -0
- package/dist/types/src/dream/QueryDriver/helpers/pg/loadPgClient.d.ts +5 -0
- package/dist/types/src/dream/internal/runHooksFor.d.ts +5 -0
- package/dist/types/src/dream/internal/withConcurrentWriterRetry.d.ts +2 -1
- package/dist/types/src/dream-app/index.d.ts +11 -1
- package/dist/types/src/types/lifecycle.d.ts +29 -0
- package/dist/types/src/types/types/lifecycle.ts +46 -0
- package/docs/assets/hierarchy.js +1 -1
- package/docs/assets/search.js +1 -1
- package/docs/classes/db.DreamMigrationHelpers.html +11 -11
- package/docs/classes/db.KyselyQueryDriver.html +47 -45
- package/docs/classes/db.PostgresQueryDriver.html +48 -46
- package/docs/classes/db.QueryDriverBase.html +46 -44
- package/docs/classes/errors.CannotSaveMissingDream.html +5 -5
- package/docs/classes/errors.CheckConstraintViolation.html +5 -5
- package/docs/classes/errors.ColumnOverflow.html +5 -5
- package/docs/classes/errors.CreateOrFindByFailedToCreateAndFind.html +5 -5
- package/docs/classes/errors.DataIncompatibleWithDatabaseField.html +5 -5
- package/docs/classes/errors.DataTypeColumnTypeMismatch.html +5 -5
- package/docs/classes/errors.DecryptionError.html +4 -4
- package/docs/classes/errors.DecryptionParseError.html +4 -4
- package/docs/classes/errors.DecryptionRotationError.html +5 -5
- package/docs/classes/errors.GlobalNameNotSet.html +5 -5
- package/docs/classes/errors.InvalidCalendarDate.html +5 -5
- package/docs/classes/errors.InvalidClockTime.html +5 -5
- package/docs/classes/errors.InvalidClockTimeTz.html +5 -5
- package/docs/classes/errors.InvalidDateTime.html +5 -5
- package/docs/classes/errors.MissingSerializersDefinition.html +5 -5
- package/docs/classes/errors.NonLoadedAssociation.html +5 -5
- package/docs/classes/errors.NotNullViolation.html +5 -5
- package/docs/classes/errors.RecordNotFound.html +5 -5
- package/docs/classes/errors.SortableScopeDidNotStabilize.html +5 -5
- package/docs/classes/errors.SortableScopeLockWaitTimedOut.html +5 -5
- package/docs/classes/errors.ValidationError.html +5 -5
- package/docs/classes/index.CalendarDate.html +33 -33
- package/docs/classes/index.ClockTime.html +32 -32
- package/docs/classes/index.ClockTimeTz.html +35 -35
- package/docs/classes/index.DateTime.html +86 -86
- package/docs/classes/index.Decorators.html +39 -23
- package/docs/classes/index.Dream.html +135 -125
- package/docs/classes/index.DreamApp.html +10 -10
- package/docs/classes/index.DreamTransaction.html +6 -2
- package/docs/classes/index.Env.html +2 -2
- package/docs/classes/index.Query.html +65 -65
- package/docs/classes/system.CliFileWriter.html +4 -4
- package/docs/classes/system.DreamBin.html +2 -2
- package/docs/classes/system.DreamCLI.html +11 -11
- package/docs/classes/system.DreamImporter.html +2 -2
- package/docs/classes/system.DreamLogos.html +2 -2
- package/docs/classes/system.DreamSerializerBuilder.html +11 -11
- package/docs/classes/system.ObjectSerializerBuilder.html +8 -8
- package/docs/classes/system.PathHelpers.html +3 -3
- package/docs/classes/utils.Encrypt.html +3 -3
- package/docs/classes/utils.Range.html +2 -2
- package/docs/functions/db.closeAllDbConnections.html +1 -1
- package/docs/functions/db.dreamDbConnections.html +1 -1
- package/docs/functions/db.untypedDb.html +1 -1
- package/docs/functions/db.validateColumn.html +1 -1
- package/docs/functions/db.validateTable.html +1 -1
- package/docs/functions/errors.pgErrorType.html +1 -1
- package/docs/functions/index.DreamSerializer.html +1 -1
- package/docs/functions/index.ObjectSerializer.html +1 -1
- package/docs/functions/index.ReplicaSafe.html +1 -1
- package/docs/functions/index.STI.html +1 -1
- package/docs/functions/index.SoftDelete.html +1 -1
- package/docs/functions/utils.camelize.html +1 -1
- package/docs/functions/utils.capitalize.html +1 -1
- package/docs/functions/utils.cloneDeepSafe.html +1 -1
- package/docs/functions/utils.compact.html +1 -1
- package/docs/functions/utils.groupBy.html +1 -1
- package/docs/functions/utils.hyphenize.html +1 -1
- package/docs/functions/utils.intersection.html +1 -1
- package/docs/functions/utils.isEmpty.html +3 -3
- package/docs/functions/utils.normalizeUnicode.html +1 -1
- package/docs/functions/utils.pascalize.html +1 -1
- package/docs/functions/utils.percent.html +1 -1
- package/docs/functions/utils.range.html +1 -1
- package/docs/functions/utils.round.html +1 -1
- package/docs/functions/utils.sanitizeString.html +1 -1
- package/docs/functions/utils.snakeify.html +1 -1
- package/docs/functions/utils.sort.html +1 -1
- package/docs/functions/utils.sortBy.html +1 -1
- package/docs/functions/utils.sortObjectByKey.html +1 -1
- package/docs/functions/utils.sortObjectByValue.html +1 -1
- package/docs/functions/utils.uncapitalize.html +1 -1
- package/docs/functions/utils.uniq.html +1 -1
- package/docs/hierarchy.html +1 -1
- package/docs/index.html +1 -1
- package/docs/interfaces/openapi.OpenapiDescription.html +2 -2
- package/docs/interfaces/openapi.OpenapiSchemaProperties.html +1 -1
- package/docs/interfaces/openapi.OpenapiSchemaPropertiesShorthand.html +1 -1
- package/docs/interfaces/openapi.OpenapiTypeFieldObject.html +1 -1
- package/docs/interfaces/types.BelongsToStatement.html +2 -2
- package/docs/interfaces/types.DecoratorContext.html +2 -2
- package/docs/interfaces/types.DreamAppInitOptions.html +2 -2
- package/docs/interfaces/types.DreamAppOpts.html +2 -2
- package/docs/interfaces/types.DreamDbConfig.html +13 -5
- package/docs/interfaces/types.DurationObject.html +2 -2
- package/docs/interfaces/types.EncryptOptions.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersMany.html +2 -2
- package/docs/interfaces/types.InternalAnyTypedSerializerRendersOne.html +2 -2
- package/docs/interfaces/types.SerializerRendererOpts.html +2 -2
- package/docs/types/openapi.CommonOpenapiSchemaObjectFields.html +1 -1
- package/docs/types/openapi.OpenapiAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiFormats.html +1 -1
- package/docs/types/openapi.OpenapiNumberFormats.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArray.html +1 -1
- package/docs/types/openapi.OpenapiSchemaArrayShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBody.html +1 -1
- package/docs/types/openapi.OpenapiSchemaBodyShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaCommonFields.html +1 -1
- package/docs/types/openapi.OpenapiSchemaExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaExpressionRefSchemaShorthand.html +2 -2
- package/docs/types/openapi.OpenapiSchemaInteger.html +1 -1
- package/docs/types/openapi.OpenapiSchemaNull.html +2 -2
- package/docs/types/openapi.OpenapiSchemaNumber.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObject.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAllOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectAnyOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBase.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectBaseShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOf.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectOneOfShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaObjectShorthand.html +1 -1
- package/docs/types/openapi.OpenapiSchemaPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAllOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionAnyOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionOneOf.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializableRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandExpressionSerializerRef.html +2 -2
- package/docs/types/openapi.OpenapiSchemaShorthandPrimitiveGeneric.html +1 -1
- package/docs/types/openapi.OpenapiSchemaString.html +1 -1
- package/docs/types/openapi.OpenapiShorthandAllTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveBaseTypes.html +1 -1
- package/docs/types/openapi.OpenapiShorthandPrimitiveTypes.html +1 -1
- package/docs/types/openapi.OpenapiTypeField.html +1 -1
- package/docs/types/system.DreamAppAllowedPackageManagersEnum.html +1 -1
- package/docs/types/types.CalendarDateDurationUnit.html +1 -1
- package/docs/types/types.CalendarDateObject.html +1 -1
- package/docs/types/types.Camelized.html +1 -1
- package/docs/types/types.ClockTimeObject.html +1 -1
- package/docs/types/types.DbConnectionType.html +1 -1
- package/docs/types/types.DbTypes.html +1 -1
- package/docs/types/types.DreamAssociationMetadata.html +1 -1
- package/docs/types/types.DreamAttributes.html +1 -1
- package/docs/types/types.DreamClassAssociationAndStatement.html +1 -1
- package/docs/types/types.DreamClassColumn.html +1 -1
- package/docs/types/types.DreamColumn.html +1 -1
- package/docs/types/types.DreamColumnNames.html +1 -1
- package/docs/types/types.DreamLogLevel.html +1 -1
- package/docs/types/types.DreamLogger.html +2 -2
- package/docs/types/types.DreamModelSerializerType.html +1 -1
- package/docs/types/types.DreamOrViewModelClassSerializerKey.html +1 -1
- package/docs/types/types.DreamOrViewModelSerializerKey.html +1 -1
- package/docs/types/types.DreamParamSafeAttributes.html +1 -1
- package/docs/types/types.DreamParamSafeColumnNames.html +1 -1
- package/docs/types/types.DreamSerializable.html +1 -1
- package/docs/types/types.DreamSerializableArray.html +1 -1
- package/docs/types/types.DreamSerializerKey.html +1 -1
- package/docs/types/types.DreamSerializers.html +1 -1
- package/docs/types/types.DreamVirtualColumns.html +1 -1
- package/docs/types/types.DurationUnit.html +1 -1
- package/docs/types/types.EncryptAlgorithm.html +1 -1
- package/docs/types/types.HasManyStatement.html +1 -1
- package/docs/types/types.HasOneStatement.html +1 -1
- package/docs/types/types.Hyphenized.html +1 -1
- package/docs/types/types.Pascalized.html +1 -1
- package/docs/types/types.PrimaryKeyType.html +1 -1
- package/docs/types/types.RoundingPrecision.html +1 -1
- package/docs/types/types.SerializerCasing.html +1 -1
- package/docs/types/types.SimpleObjectSerializerType.html +1 -1
- package/docs/types/types.Snakeified.html +1 -1
- package/docs/types/types.StrictInterface.html +1 -1
- package/docs/types/types.UpdateableAssociationProperties.html +1 -1
- package/docs/types/types.UpdateableProperties.html +1 -1
- package/docs/types/types.ValidationType.html +1 -1
- package/docs/types/types.ViewModel.html +2 -2
- package/docs/types/types.ViewModelClass.html +1 -1
- package/docs/types/types.WeekdayName.html +1 -1
- package/docs/types/types.WhereStatementForDream.html +1 -1
- package/docs/types/types.WhereStatementForDreamClass.html +1 -1
- package/docs/variables/index.DreamConst.html +1 -1
- package/docs/variables/index.ops.html +1 -1
- package/docs/variables/openapi.openapiPrimitiveTypes.html +1 -1
- package/docs/variables/openapi.openapiShorthandPrimitiveTypes.html +1 -1
- package/docs/variables/system.DreamAppAllowedPackageManagersEnumValues.html +1 -1
- package/docs/variables/system.primaryKeyTypes.html +1 -1
- package/package.json +5 -2
package/README.md
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
Dream is a typescript-driven, esm-first ORM built on top of [kysely](http://kysely.dev). It is built to operate within the [Psychic web framework](https://psychicframework.com/docs/intro), but can be brought into other projects and used without the encapsulating framework (though this is theoretical, and we do not encourage it at this time). It is actively being developed to support production-grade applications in use within the [RVOHealth organization](https://www.rvohealth.com), who has kindly sponsored the continued development of this ORM, as well as the Psychic web framework as a whole.
|
|
1
|
+
Dream is a typescript-driven, esm-first ORM built on top of [kysely](http://kysely.dev). It supports Node.js 24 and newer, with Node.js 26 as the primary development and CI runtime. It is built to operate within the [Psychic web framework](https://psychicframework.com/docs/intro), but can be brought into other projects and used without the encapsulating framework (though this is theoretical, and we do not encourage it at this time). It is actively being developed to support production-grade applications in use within the [RVOHealth organization](https://www.rvohealth.com), who has kindly sponsored the continued development of this ORM, as well as the Psychic web framework as a whole.
|
|
2
2
|
|
|
3
3
|
Documentation: [https://psychicframework.com/docs/intro](https://psychicframework.com/docs/intro)
|
|
4
4
|
Psychic web framework github: [https://github.com/rvohealth/psychic](https://github.com/rvohealth/psychic)
|
package/dist/cjs/src/Dream.js
CHANGED
|
@@ -2946,6 +2946,10 @@ export default class Dream {
|
|
|
2946
2946
|
* being persisted to the database, with the values
|
|
2947
2947
|
* that were last persisted to the database.
|
|
2948
2948
|
*
|
|
2949
|
+
* On a record that has not been persisted, every
|
|
2950
|
+
* column counts as changed: the object has a key
|
|
2951
|
+
* for every column.
|
|
2952
|
+
*
|
|
2949
2953
|
* ```ts
|
|
2950
2954
|
* const user = User.new({ email: 'original@email', password: 'howyadoin' })
|
|
2951
2955
|
* await user.save()
|
|
@@ -3077,6 +3081,10 @@ export default class Dream {
|
|
|
3077
3081
|
* Returns true if the columnName provided has
|
|
3078
3082
|
* changes that have not yet been persisted.
|
|
3079
3083
|
*
|
|
3084
|
+
* On a record that has not been persisted, every
|
|
3085
|
+
* column counts as changed, so this returns true
|
|
3086
|
+
* for every column, whether or not it was assigned.
|
|
3087
|
+
*
|
|
3080
3088
|
* @param attribute - the column name to check
|
|
3081
3089
|
* @returns A boolean
|
|
3082
3090
|
*/
|
|
@@ -3096,10 +3104,15 @@ export default class Dream {
|
|
|
3096
3104
|
* of columns that have changed since last persist,
|
|
3097
3105
|
* and their current values.
|
|
3098
3106
|
*
|
|
3107
|
+
* On a record that has not been persisted, every
|
|
3108
|
+
* column counts as changed: the object has a key
|
|
3109
|
+
* for every column, including those never assigned,
|
|
3110
|
+
* whose value is `undefined`.
|
|
3111
|
+
*
|
|
3099
3112
|
* ```ts
|
|
3100
3113
|
* const user = User.new({ email: 'hello@world' })
|
|
3101
3114
|
* user.dirtyAttributes()
|
|
3102
|
-
* // { email: 'hello@world' }
|
|
3115
|
+
* // { id: undefined, email: 'hello@world', name: undefined, ...every other column: undefined }
|
|
3103
3116
|
*
|
|
3104
3117
|
* await user.save()
|
|
3105
3118
|
*
|
|
@@ -508,10 +508,10 @@ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`
|
|
|
508
508
|
* (package.json scripts, CLI argv, scaffold templates) — never from
|
|
509
509
|
* runtime request input or any other untrusted external source.
|
|
510
510
|
*
|
|
511
|
-
* Argv-form
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
511
|
+
* Argv-form passes config values and paths literally to the child,
|
|
512
|
+
* without interpreting shell metacharacters. Credentials belong in
|
|
513
|
+
* the child's environment, since argv is visible in process listings
|
|
514
|
+
* and in the refused-spawn error.
|
|
515
515
|
*
|
|
516
516
|
* ## Layered defense
|
|
517
517
|
*
|
|
@@ -13,16 +13,14 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
13
13
|
const dbFilename = dbTypesFilenameForConnection(connectionName);
|
|
14
14
|
const dbSyncFilePath = path.join(dreamPath('types'), dbFilename);
|
|
15
15
|
const absoluteDbSyncPath = path.join(dreamApp.projectRoot, dbSyncFilePath);
|
|
16
|
+
const password = await driverClass.codegenPassword(dbConf.password);
|
|
16
17
|
await CliFileWriter.cache(absoluteDbSyncPath);
|
|
17
18
|
const lowLevelDbOpts = dreamApp.dbCredentialsFor(connectionName);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// the child rather than parsed by a shell — credentials are preserved and any
|
|
22
|
-
// future mutable source can't open a command-injection surface.
|
|
23
|
-
const userinfo = `${dbConf.user}${dbConf.password ? `:${dbConf.password}` : ''}`;
|
|
19
|
+
// Pass the URL only in the child's environment. A URL in argv exposes the
|
|
20
|
+
// password in the process command line and DreamCLI's refused-spawn error.
|
|
21
|
+
const userinfo = `${encodeURIComponent(dbConf.user)}${password ? `:${encodeURIComponent(password)}` : ''}`;
|
|
24
22
|
const url = `${driverClass.syncDialect}://${userinfo}@${dbConf.host}:${dbConf.port}/${dbConf.name}`;
|
|
25
|
-
const args = [`--dialect=${driverClass.syncDialect}`,
|
|
23
|
+
const args = [`--dialect=${driverClass.syncDialect}`, '--url=env(DATABASE_URL)'];
|
|
26
24
|
if (lowLevelDbOpts?.tableIncludePattern) {
|
|
27
25
|
args.push(`--include-pattern=${lowLevelDbOpts.tableIncludePattern}`);
|
|
28
26
|
}
|
|
@@ -32,6 +30,7 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
32
30
|
args.push(`--out-file=${absoluteDbSyncPath}`);
|
|
33
31
|
await DreamCLI.spawn('kysely-codegen', {
|
|
34
32
|
args,
|
|
33
|
+
env: { ...process.env, DATABASE_URL: url },
|
|
35
34
|
onStdout: message => {
|
|
36
35
|
DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) + ' ' + message, {
|
|
37
36
|
logPrefixColor: 'greenBright',
|
|
@@ -491,6 +491,11 @@ export default class Decorators {
|
|
|
491
491
|
* }
|
|
492
492
|
* ```
|
|
493
493
|
*
|
|
494
|
+
* The decorated method is called with no argument, once the
|
|
495
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
496
|
+
* compile error. A subclass override of the method that is not itself
|
|
497
|
+
* decorated is not checked; it is still called with no argument.
|
|
498
|
+
*
|
|
494
499
|
* @returns The AfterCreateCommit decorator
|
|
495
500
|
*/
|
|
496
501
|
AfterCreateCommit(opts) {
|
|
@@ -526,6 +531,11 @@ export default class Decorators {
|
|
|
526
531
|
* }
|
|
527
532
|
* ```
|
|
528
533
|
*
|
|
534
|
+
* The decorated method is called with no argument, once the
|
|
535
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
536
|
+
* compile error. A subclass override of the method that is not itself
|
|
537
|
+
* decorated is not checked; it is still called with no argument.
|
|
538
|
+
*
|
|
529
539
|
* @returns The AfterSaveCommit decorator
|
|
530
540
|
*
|
|
531
541
|
*/
|
|
@@ -562,6 +572,11 @@ export default class Decorators {
|
|
|
562
572
|
* }
|
|
563
573
|
* ```
|
|
564
574
|
*
|
|
575
|
+
* The decorated method is called with no argument, once the
|
|
576
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
577
|
+
* compile error. A subclass override of the method that is not itself
|
|
578
|
+
* decorated is not checked; it is still called with no argument.
|
|
579
|
+
*
|
|
565
580
|
* @returns The AfterUpdateCommit decorator
|
|
566
581
|
*
|
|
567
582
|
*/
|
|
@@ -598,6 +613,11 @@ export default class Decorators {
|
|
|
598
613
|
* }
|
|
599
614
|
* ```
|
|
600
615
|
*
|
|
616
|
+
* The decorated method is called with no argument, once the
|
|
617
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
618
|
+
* compile error. A subclass override of the method that is not itself
|
|
619
|
+
* decorated is not checked; it is still called with no argument.
|
|
620
|
+
*
|
|
601
621
|
* @returns The AfterDestroyCommit decorator
|
|
602
622
|
*
|
|
603
623
|
*/
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterCreateCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterDestroyCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterSaveCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterUpdateCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommitHook } from './internal/runHooksFor.js';
|
|
2
2
|
// though this class is called `DreamTransaction`, it is not itself
|
|
3
3
|
// a transaction class, as much as a collector for various callbacks
|
|
4
4
|
// that must be run after the underlying transaction is commited (i.e.
|
|
@@ -6,6 +6,10 @@ import { runHook } from './internal/runHooksFor.js';
|
|
|
6
6
|
export default class DreamTransaction {
|
|
7
7
|
_kyselyTransaction;
|
|
8
8
|
commitHooks = [];
|
|
9
|
+
// Set when the query driver starts the commit hooks, which it does only once
|
|
10
|
+
// the transaction has committed. `withConcurrentWriterRetry` reads it to
|
|
11
|
+
// never run a committed operation again.
|
|
12
|
+
committed = false;
|
|
9
13
|
get kyselyTransaction() {
|
|
10
14
|
return this._kyselyTransaction;
|
|
11
15
|
}
|
|
@@ -15,9 +19,16 @@ export default class DreamTransaction {
|
|
|
15
19
|
addCommitHook(hookStatement, dreamInstance) {
|
|
16
20
|
this.commitHooks.push({ dreamInstance, hookStatement });
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Runs the commit hooks queued during the transaction, in the order they
|
|
24
|
+
* were queued. A query driver calls this once the transaction has
|
|
25
|
+
* committed. Each hook is called with no argument, since the transaction
|
|
26
|
+
* it would otherwise receive can no longer run queries.
|
|
27
|
+
*/
|
|
28
|
+
async runAfterCommitHooks() {
|
|
29
|
+
this.committed = true;
|
|
19
30
|
for (const hook of this.commitHooks) {
|
|
20
|
-
await
|
|
31
|
+
await runCommitHook(hook.hookStatement, hook.dreamInstance);
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
}
|
|
@@ -626,6 +626,13 @@ export default class QueryDriverBase {
|
|
|
626
626
|
static get syncDialect() {
|
|
627
627
|
return 'postgres';
|
|
628
628
|
}
|
|
629
|
+
/** @internal Resolve the credential used by the database type generator. */
|
|
630
|
+
static codegenPassword(password) {
|
|
631
|
+
if (typeof password === 'function') {
|
|
632
|
+
throw new Error('This database adapter does not support a password provider for type generation');
|
|
633
|
+
}
|
|
634
|
+
return Promise.resolve(password);
|
|
635
|
+
}
|
|
629
636
|
/**
|
|
630
637
|
* Returns the sql that would be executed by this Query
|
|
631
638
|
*
|
|
@@ -71,6 +71,7 @@ import softDeleteDream from '../internal/softDeleteDream.js';
|
|
|
71
71
|
import sqlResultToDreamInstance from '../internal/sqlResultToDreamInstance.js';
|
|
72
72
|
import Query from '../Query.js';
|
|
73
73
|
import QueryDriverBase from './Base.js';
|
|
74
|
+
import { PasswordProviderPoolClient } from './helpers/pg/loadPgClient.js';
|
|
74
75
|
import checkForNeedToBeRunMigrations from './helpers/kysely/checkForNeedToBeRunMigrations.js';
|
|
75
76
|
import foreignKeyTypeFromPrimaryKey from './helpers/kysely/foreignKeyTypeFromPrimaryKey.js';
|
|
76
77
|
import runMigration from './helpers/kysely/runMigration.js';
|
|
@@ -103,7 +104,10 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
103
104
|
// Spread pg passthrough first; Dream's resolved fields follow and
|
|
104
105
|
// always win (per-connection database name, TLS directive).
|
|
105
106
|
...(connectionConf.pg ?? {}),
|
|
107
|
+
Client: typeof connectionConf.password === 'function' ? PasswordProviderPoolClient : undefined,
|
|
106
108
|
user: connectionConf.user || '',
|
|
109
|
+
// pg accepts a callback here and resolves it for each new client when
|
|
110
|
+
// the server requests password authentication.
|
|
107
111
|
password: connectionConf.password || '',
|
|
108
112
|
database: DreamApp.getOrFail().dbName(connectionName, dbConnectionType),
|
|
109
113
|
host: connectionConf.host || 'localhost',
|
|
@@ -227,6 +231,9 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
227
231
|
static get syncDialect() {
|
|
228
232
|
return 'postgres';
|
|
229
233
|
}
|
|
234
|
+
static async codegenPassword(password) {
|
|
235
|
+
return typeof password === 'function' ? await password() : password;
|
|
236
|
+
}
|
|
230
237
|
/**
|
|
231
238
|
* @internal
|
|
232
239
|
*
|
|
@@ -344,7 +351,7 @@ export default class KyselyQueryDriver extends QueryDriverBase {
|
|
|
344
351
|
dreamTransaction.kyselyTransaction = kyselyTransaction;
|
|
345
352
|
callbackResponse = (await callback(dreamTransaction));
|
|
346
353
|
});
|
|
347
|
-
await dreamTransaction.runAfterCommitHooks(
|
|
354
|
+
await dreamTransaction.runAfterCommitHooks();
|
|
348
355
|
return callbackResponse;
|
|
349
356
|
}
|
|
350
357
|
/**
|
|
@@ -332,7 +332,13 @@ export default class PostgresQueryDriver extends KyselyQueryDriver {
|
|
|
332
332
|
keepAlive: true,
|
|
333
333
|
keepAliveInitialDelayMillis: LOCK_CONNECTION_KEEPALIVE_INITIAL_DELAY_MS,
|
|
334
334
|
});
|
|
335
|
-
|
|
335
|
+
try {
|
|
336
|
+
await client.connect();
|
|
337
|
+
}
|
|
338
|
+
catch (err) {
|
|
339
|
+
await client.end().catch(() => undefined);
|
|
340
|
+
throw err;
|
|
341
|
+
}
|
|
336
342
|
return {
|
|
337
343
|
async tryAcquire(namespace, index) {
|
|
338
344
|
const result = await client.query('SELECT pg_try_advisory_lock($1::int4, $2::int4) AS locked', [
|
|
@@ -8,6 +8,28 @@
|
|
|
8
8
|
// @ts-ignore
|
|
9
9
|
import pg from 'pg';
|
|
10
10
|
import DreamApp from '../../../../dream-app/index.js';
|
|
11
|
+
export class PasswordProviderPoolClient extends pg.Client {
|
|
12
|
+
connect(callback) {
|
|
13
|
+
if (callback) {
|
|
14
|
+
super.connect(err => {
|
|
15
|
+
if (err)
|
|
16
|
+
this.destroyConnectionStream();
|
|
17
|
+
callback(err);
|
|
18
|
+
});
|
|
19
|
+
}
|
|
20
|
+
else {
|
|
21
|
+
return super.connect().catch(err => {
|
|
22
|
+
this.destroyConnectionStream();
|
|
23
|
+
throw err;
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
destroyConnectionStream() {
|
|
28
|
+
// pg reports a rejected password provider without closing its socket.
|
|
29
|
+
;
|
|
30
|
+
this.connection?.stream.destroy();
|
|
31
|
+
}
|
|
32
|
+
}
|
|
11
33
|
export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
12
34
|
const dreamconf = DreamApp.getOrFail();
|
|
13
35
|
const creds = dreamconf.dbCredentialsFor(connectionName)?.primary;
|
|
@@ -20,6 +42,12 @@ export default async function loadPgClient({ connectionName, useSystemDb, }) {
|
|
|
20
42
|
user: creds.user,
|
|
21
43
|
password: creds.password,
|
|
22
44
|
});
|
|
23
|
-
|
|
45
|
+
try {
|
|
46
|
+
await client.connect();
|
|
47
|
+
}
|
|
48
|
+
catch (err) {
|
|
49
|
+
await client.end().catch(() => undefined);
|
|
50
|
+
throw err;
|
|
51
|
+
}
|
|
24
52
|
return client;
|
|
25
53
|
}
|
|
@@ -35,7 +35,7 @@ export default async function runHooksFor(key, dream, alreadyPersisted, beforeSa
|
|
|
35
35
|
if (txn)
|
|
36
36
|
txn.addCommitHook(hook, dream);
|
|
37
37
|
else
|
|
38
|
-
await
|
|
38
|
+
await runCommitHook(hook, dream);
|
|
39
39
|
}
|
|
40
40
|
break;
|
|
41
41
|
case 'afterSave':
|
|
@@ -50,7 +50,7 @@ export default async function runHooksFor(key, dream, alreadyPersisted, beforeSa
|
|
|
50
50
|
if (txn)
|
|
51
51
|
txn.addCommitHook(hook, dream);
|
|
52
52
|
else
|
|
53
|
-
await
|
|
53
|
+
await runCommitHook(hook, dream);
|
|
54
54
|
}
|
|
55
55
|
break;
|
|
56
56
|
case 'afterUpdate':
|
|
@@ -63,7 +63,7 @@ export default async function runHooksFor(key, dream, alreadyPersisted, beforeSa
|
|
|
63
63
|
if (txn)
|
|
64
64
|
txn.addCommitHook(hook, dream);
|
|
65
65
|
else
|
|
66
|
-
await
|
|
66
|
+
await runCommitHook(hook, dream);
|
|
67
67
|
}
|
|
68
68
|
break;
|
|
69
69
|
default:
|
|
@@ -79,7 +79,7 @@ export default async function runHooksFor(key, dream, alreadyPersisted, beforeSa
|
|
|
79
79
|
if (txn)
|
|
80
80
|
txn.addCommitHook(hook, dream);
|
|
81
81
|
else
|
|
82
|
-
await
|
|
82
|
+
await runCommitHook(hook, dream);
|
|
83
83
|
break;
|
|
84
84
|
default:
|
|
85
85
|
await runHook(hook, dream, txn);
|
|
@@ -88,6 +88,18 @@ export default async function runHooksFor(key, dream, alreadyPersisted, beforeSa
|
|
|
88
88
|
}
|
|
89
89
|
}
|
|
90
90
|
export async function runHook(statement, dream, txn) {
|
|
91
|
+
assertHookMethodExists(statement, dream);
|
|
92
|
+
await dream[statement.method](txn);
|
|
93
|
+
}
|
|
94
|
+
/**
|
|
95
|
+
* Commit hooks run after the transaction has committed, so they are called
|
|
96
|
+
* with no argument.
|
|
97
|
+
*/
|
|
98
|
+
export async function runCommitHook(statement, dream) {
|
|
99
|
+
assertHookMethodExists(statement, dream);
|
|
100
|
+
await dream[statement.method]();
|
|
101
|
+
}
|
|
102
|
+
function assertHookMethodExists(statement, dream) {
|
|
91
103
|
if (typeof dream[statement.method] !== 'function') {
|
|
92
104
|
throw new Error(`
|
|
93
105
|
Attempting to run ${statement.method} as part of the ${statement.type}
|
|
@@ -96,7 +108,6 @@ Dream model hook sequence, but we encountered a method that does not exist.
|
|
|
96
108
|
Please make sure "${statement.method}" is defined on ${dream['sanitizedConstructorName']}
|
|
97
109
|
`);
|
|
98
110
|
}
|
|
99
|
-
await dream[statement.method](txn);
|
|
100
111
|
}
|
|
101
112
|
function shouldRunBeforeCreateHook(dream, statement) {
|
|
102
113
|
let shouldRun = false;
|
|
@@ -22,7 +22,8 @@ export const MAX_CONCURRENT_WRITER_ATTEMPTS = 3;
|
|
|
22
22
|
* rows mid-statement as well. Either way the database has undone the whole
|
|
23
23
|
* transaction, nothing has committed and no commit hook has run, so running
|
|
24
24
|
* the operation again reads the rows as the other writer left them. The last
|
|
25
|
-
* attempt's error is the adapter's own.
|
|
25
|
+
* attempt's error is the adapter's own. An error raised after COMMIT, such as a
|
|
26
|
+
* commit hook's, is never retried.
|
|
26
27
|
*
|
|
27
28
|
* A unique violation raised by a statement inside the callback is not a
|
|
28
29
|
* collision but a write that violates a constraint outright, and is not
|
|
@@ -46,15 +47,19 @@ export const MAX_CONCURRENT_WRITER_ATTEMPTS = 3;
|
|
|
46
47
|
export default async function withConcurrentWriterRetry(dreamClass, callback) {
|
|
47
48
|
const queryDriverClass = DreamApp.getOrFail().dbConnectionQueryDriverClass(dreamClass.prototype.connectionName);
|
|
48
49
|
for (let attempt = 1;; attempt++) {
|
|
50
|
+
let attemptTransaction = null;
|
|
49
51
|
let callbackResolved = false;
|
|
50
52
|
try {
|
|
51
53
|
return await dreamClass.transaction(async (txn) => {
|
|
54
|
+
attemptTransaction = txn;
|
|
52
55
|
const result = await callback(txn);
|
|
53
56
|
callbackResolved = true;
|
|
54
57
|
return result;
|
|
55
58
|
});
|
|
56
59
|
}
|
|
57
60
|
catch (error) {
|
|
61
|
+
if (attemptTransaction?.['committed'])
|
|
62
|
+
throw error;
|
|
58
63
|
const undoneByConcurrentWriter = queryDriverClass.isDeadlock(error) ||
|
|
59
64
|
(callbackResolved && queryDriverClass.isUniqueConstraintViolation(error));
|
|
60
65
|
if (!undoneByConcurrentWriter || attempt === MAX_CONCURRENT_WRITER_ATTEMPTS)
|
package/dist/esm/src/Dream.js
CHANGED
|
@@ -2946,6 +2946,10 @@ export default class Dream {
|
|
|
2946
2946
|
* being persisted to the database, with the values
|
|
2947
2947
|
* that were last persisted to the database.
|
|
2948
2948
|
*
|
|
2949
|
+
* On a record that has not been persisted, every
|
|
2950
|
+
* column counts as changed: the object has a key
|
|
2951
|
+
* for every column.
|
|
2952
|
+
*
|
|
2949
2953
|
* ```ts
|
|
2950
2954
|
* const user = User.new({ email: 'original@email', password: 'howyadoin' })
|
|
2951
2955
|
* await user.save()
|
|
@@ -3077,6 +3081,10 @@ export default class Dream {
|
|
|
3077
3081
|
* Returns true if the columnName provided has
|
|
3078
3082
|
* changes that have not yet been persisted.
|
|
3079
3083
|
*
|
|
3084
|
+
* On a record that has not been persisted, every
|
|
3085
|
+
* column counts as changed, so this returns true
|
|
3086
|
+
* for every column, whether or not it was assigned.
|
|
3087
|
+
*
|
|
3080
3088
|
* @param attribute - the column name to check
|
|
3081
3089
|
* @returns A boolean
|
|
3082
3090
|
*/
|
|
@@ -3096,10 +3104,15 @@ export default class Dream {
|
|
|
3096
3104
|
* of columns that have changed since last persist,
|
|
3097
3105
|
* and their current values.
|
|
3098
3106
|
*
|
|
3107
|
+
* On a record that has not been persisted, every
|
|
3108
|
+
* column counts as changed: the object has a key
|
|
3109
|
+
* for every column, including those never assigned,
|
|
3110
|
+
* whose value is `undefined`.
|
|
3111
|
+
*
|
|
3099
3112
|
* ```ts
|
|
3100
3113
|
* const user = User.new({ email: 'hello@world' })
|
|
3101
3114
|
* user.dirtyAttributes()
|
|
3102
|
-
* // { email: 'hello@world' }
|
|
3115
|
+
* // { id: undefined, email: 'hello@world', name: undefined, ...every other column: undefined }
|
|
3103
3116
|
*
|
|
3104
3117
|
* await user.save()
|
|
3105
3118
|
*
|
|
@@ -508,10 +508,10 @@ ${INDENT}Examples: User, Place, Room/Bedroom, Settings/CommunicationPreferences`
|
|
|
508
508
|
* (package.json scripts, CLI argv, scaffold templates) — never from
|
|
509
509
|
* runtime request input or any other untrusted external source.
|
|
510
510
|
*
|
|
511
|
-
* Argv-form
|
|
512
|
-
*
|
|
513
|
-
*
|
|
514
|
-
*
|
|
511
|
+
* Argv-form passes config values and paths literally to the child,
|
|
512
|
+
* without interpreting shell metacharacters. Credentials belong in
|
|
513
|
+
* the child's environment, since argv is visible in process listings
|
|
514
|
+
* and in the refused-spawn error.
|
|
515
515
|
*
|
|
516
516
|
* ## Layered defense
|
|
517
517
|
*
|
|
@@ -13,16 +13,14 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
13
13
|
const dbFilename = dbTypesFilenameForConnection(connectionName);
|
|
14
14
|
const dbSyncFilePath = path.join(dreamPath('types'), dbFilename);
|
|
15
15
|
const absoluteDbSyncPath = path.join(dreamApp.projectRoot, dbSyncFilePath);
|
|
16
|
+
const password = await driverClass.codegenPassword(dbConf.password);
|
|
16
17
|
await CliFileWriter.cache(absoluteDbSyncPath);
|
|
17
18
|
const lowLevelDbOpts = dreamApp.dbCredentialsFor(connectionName);
|
|
18
|
-
//
|
|
19
|
-
//
|
|
20
|
-
|
|
21
|
-
// the child rather than parsed by a shell — credentials are preserved and any
|
|
22
|
-
// future mutable source can't open a command-injection surface.
|
|
23
|
-
const userinfo = `${dbConf.user}${dbConf.password ? `:${dbConf.password}` : ''}`;
|
|
19
|
+
// Pass the URL only in the child's environment. A URL in argv exposes the
|
|
20
|
+
// password in the process command line and DreamCLI's refused-spawn error.
|
|
21
|
+
const userinfo = `${encodeURIComponent(dbConf.user)}${password ? `:${encodeURIComponent(password)}` : ''}`;
|
|
24
22
|
const url = `${driverClass.syncDialect}://${userinfo}@${dbConf.host}:${dbConf.port}/${dbConf.name}`;
|
|
25
|
-
const args = [`--dialect=${driverClass.syncDialect}`,
|
|
23
|
+
const args = [`--dialect=${driverClass.syncDialect}`, '--url=env(DATABASE_URL)'];
|
|
26
24
|
if (lowLevelDbOpts?.tableIncludePattern) {
|
|
27
25
|
args.push(`--include-pattern=${lowLevelDbOpts.tableIncludePattern}`);
|
|
28
26
|
}
|
|
@@ -32,6 +30,7 @@ export default async function syncDbTypesFiles(connectionName) {
|
|
|
32
30
|
args.push(`--out-file=${absoluteDbSyncPath}`);
|
|
33
31
|
await DreamCLI.spawn('kysely-codegen', {
|
|
34
32
|
args,
|
|
33
|
+
env: { ...process.env, DATABASE_URL: url },
|
|
35
34
|
onStdout: message => {
|
|
36
35
|
DreamCLI.logger.logContinueProgress(colorize(`[db]`, { color: 'greenBright' }) + ' ' + message, {
|
|
37
36
|
logPrefixColor: 'greenBright',
|
|
@@ -491,6 +491,11 @@ export default class Decorators {
|
|
|
491
491
|
* }
|
|
492
492
|
* ```
|
|
493
493
|
*
|
|
494
|
+
* The decorated method is called with no argument, once the
|
|
495
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
496
|
+
* compile error. A subclass override of the method that is not itself
|
|
497
|
+
* decorated is not checked; it is still called with no argument.
|
|
498
|
+
*
|
|
494
499
|
* @returns The AfterCreateCommit decorator
|
|
495
500
|
*/
|
|
496
501
|
AfterCreateCommit(opts) {
|
|
@@ -526,6 +531,11 @@ export default class Decorators {
|
|
|
526
531
|
* }
|
|
527
532
|
* ```
|
|
528
533
|
*
|
|
534
|
+
* The decorated method is called with no argument, once the
|
|
535
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
536
|
+
* compile error. A subclass override of the method that is not itself
|
|
537
|
+
* decorated is not checked; it is still called with no argument.
|
|
538
|
+
*
|
|
529
539
|
* @returns The AfterSaveCommit decorator
|
|
530
540
|
*
|
|
531
541
|
*/
|
|
@@ -562,6 +572,11 @@ export default class Decorators {
|
|
|
562
572
|
* }
|
|
563
573
|
* ```
|
|
564
574
|
*
|
|
575
|
+
* The decorated method is called with no argument, once the
|
|
576
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
577
|
+
* compile error. A subclass override of the method that is not itself
|
|
578
|
+
* decorated is not checked; it is still called with no argument.
|
|
579
|
+
*
|
|
565
580
|
* @returns The AfterUpdateCommit decorator
|
|
566
581
|
*
|
|
567
582
|
*/
|
|
@@ -598,6 +613,11 @@ export default class Decorators {
|
|
|
598
613
|
* }
|
|
599
614
|
* ```
|
|
600
615
|
*
|
|
616
|
+
* The decorated method is called with no argument, once the
|
|
617
|
+
* transaction has committed, so declaring a parameter on it is a
|
|
618
|
+
* compile error. A subclass override of the method that is not itself
|
|
619
|
+
* decorated is not checked; it is still called with no argument.
|
|
620
|
+
*
|
|
601
621
|
* @returns The AfterDestroyCommit decorator
|
|
602
622
|
*
|
|
603
623
|
*/
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterCreateCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterDestroyCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterSaveCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -7,6 +7,10 @@ import { blankHooksFactory } from './shared.js';
|
|
|
7
7
|
* a transaction, the method will be run after the save
|
|
8
8
|
* is complete.
|
|
9
9
|
*
|
|
10
|
+
* The method is called with no argument: the transaction has already
|
|
11
|
+
* committed, so there is none to hand it. Decorating a method that
|
|
12
|
+
* declares a parameter is a compile error (see {@link CommitHookDecorator}).
|
|
13
|
+
*
|
|
10
14
|
* class User extends ApplicationModel {
|
|
11
15
|
* @deco.AfterUpdateCommit()
|
|
12
16
|
* public doSomething() {
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { runCommitHook } from './internal/runHooksFor.js';
|
|
2
2
|
// though this class is called `DreamTransaction`, it is not itself
|
|
3
3
|
// a transaction class, as much as a collector for various callbacks
|
|
4
4
|
// that must be run after the underlying transaction is commited (i.e.
|
|
@@ -6,6 +6,10 @@ import { runHook } from './internal/runHooksFor.js';
|
|
|
6
6
|
export default class DreamTransaction {
|
|
7
7
|
_kyselyTransaction;
|
|
8
8
|
commitHooks = [];
|
|
9
|
+
// Set when the query driver starts the commit hooks, which it does only once
|
|
10
|
+
// the transaction has committed. `withConcurrentWriterRetry` reads it to
|
|
11
|
+
// never run a committed operation again.
|
|
12
|
+
committed = false;
|
|
9
13
|
get kyselyTransaction() {
|
|
10
14
|
return this._kyselyTransaction;
|
|
11
15
|
}
|
|
@@ -15,9 +19,16 @@ export default class DreamTransaction {
|
|
|
15
19
|
addCommitHook(hookStatement, dreamInstance) {
|
|
16
20
|
this.commitHooks.push({ dreamInstance, hookStatement });
|
|
17
21
|
}
|
|
18
|
-
|
|
22
|
+
/**
|
|
23
|
+
* Runs the commit hooks queued during the transaction, in the order they
|
|
24
|
+
* were queued. A query driver calls this once the transaction has
|
|
25
|
+
* committed. Each hook is called with no argument, since the transaction
|
|
26
|
+
* it would otherwise receive can no longer run queries.
|
|
27
|
+
*/
|
|
28
|
+
async runAfterCommitHooks() {
|
|
29
|
+
this.committed = true;
|
|
19
30
|
for (const hook of this.commitHooks) {
|
|
20
|
-
await
|
|
31
|
+
await runCommitHook(hook.hookStatement, hook.dreamInstance);
|
|
21
32
|
}
|
|
22
33
|
}
|
|
23
34
|
}
|