Repository: directus/directus
File: api/src/services/items.ts (TypeScript)

Code before:
		const { RevisionsService } = await import('./revisions.js');

		const primaryKeyField = this.schema.collections[this.collection]!.primary;
		const fields = Object.keys(this.schema.collections[this.collection]!.fields);

		const aliases = Object.values(this.schema.collections[this.collection]!.fields)
			.filter((field) => field.alias === true)
			.map((field) => field.field);

		const payload: AnyItem = cloneDeep(data);
		const nestedActionEvents: ActionEventParams[] = [];

		// Run all hooks that are attached to this event so the end user has the chance to augment the
		// item that is about to be saved
		const payloadAfterHooks =
			opts.emitEvents !== false
				? await emitter.emitFilter(
						this.eventScope === 'items'
							? ['items.create', `${this.collection}.items.create`]
							: `${this.eventScope}.create`,
						payload,
						{
							collection: this.collection,
						},
						{
							database: this.knex,
							schema: this.schema,
							accountability: this.accountability,
						},
				  )
				: payload;

		const payloadWithPresets = this.accountability
			? await processPayload(
					{
						accountability: this.accountability,
						action: 'create',
						collection: this.collection,
						payload: payloadAfterHooks,
					},
					{
						knex: this.knex,
						schema: this.schema,
					},
			  )
			: payloadAfterHooks;

		if (opts.preMutationError) {
			throw opts.preMutationError;
		}


Review comment (suggestion):
Should this event callback not stay within in the transaction so extensions get the knex transaction and can potentially cancel/rollback.

Code after:
	async createOne(data: Partial<Item>, opts: MutationOptions = {}): Promise<PrimaryKey> {
		if (!opts.mutationTracker) opts.mutationTracker = this.createMutationTracker();

		if (!opts.bypassLimits) {
			opts.mutationTracker.trackMutations(1);
		}

		const { ActivityService } = await import('./activity.js');
		const { RevisionsService } = await import('./revisions.js');

		const primaryKeyField = this.schema.collections[this.collection]!.primary;
		const fields = Object.keys(this.schema.collections[this.collection]!.fields);

		const aliases = Object.values(this.schema.collections[this.collection]!.fields)
			.filter((field) => field.alias === true)
			.map((field) => field.field);

		const payload: AnyItem = cloneDeep(data);
		let actionHookPayload = payload;
		const nestedActionEvents: ActionEventParams[] = [];

		/**
		 * By wrapping the logic in a transaction, we make sure we automatically roll back all the
		 * changes in the DB if any of the parts contained within throws an error. This also means
		 * that any errors thrown in any nested relational changes will bubble up and cancel the whole
		 * update tree
		 */
		const primaryKey: PrimaryKey = await transaction(this.knex, async (trx) => {
			// Run all hooks that are attached to this event so the end user has the chance to augment the
			// item that is about to be saved
			const payloadAfterHooks =
				opts.emitEvents !== false
					? await emitter.emitFilter(
							this.eventScope === 'items'
								? ['items.create', `${this.collection}.items.create`]
								: `${this.eventScope}.create`,
							payload,
							{
								collection: this.collection,
							},
							{
								database: trx,
								schema: this.schema,
								accountability: this.accountability,
							},
					  )
					: payload;

			const payloadWithPresets = this.accountability
				? await processPayload(
						{