typeorm / typeorm

ORM for TypeScript and JavaScript. Supports MySQL, PostgreSQL, MariaDB, SQLite, MS SQL Server, Oracle, SAP Hana, WebSQL databases. Works in NodeJS, Browser, Ionic, Cordova and Electron platforms.
http://typeorm.io
MIT License
33.46k stars 6.21k forks source link

question: using subscribers on vanilla javascript #2305

Closed jkiyo closed 3 years ago

jkiyo commented 5 years ago

Issue type:

[X] question [ ] bug report [ ] feature request [ ] documentation issue

Database system/driver:

[ ] cordova [ ] mongodb [ ] mssql [ ] mysql / mariadb [ ] oracle [X] postgres [ ] sqlite [ ] sqljs [ ] react-native

TypeORM version:

[ ] latest [ ] @next [X] 0.2.6 (or put your version here)

I'm trying to configure subscribers using Javascript, but the beforeInsert method on my Subscriber class never get called... Maybe I'm missing some step or configuration?

Doc: https://github.com/typeorm/typeorm/blob/master/docs/connection-options.md

My code looks like this:

class Subscriber {
  beforeInsert (event) {
    console.log('fooo', event)
  }
}

module.exports = {
  name: 'default',
  type: 'postgres',
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  database: process.env.DB_DATABASE,
  entities: schemas.map(schema => new EntitySchema(schema)),
  synchronize: false,
  logging: true,
  subscribers: [ Subscriber ],
  extra: { max: 1 },
  migrations: [path.join(__dirname, '../../migrations/*.js')],
  cli: { migrationsDir: path.join(__dirname, '../../migrations/*.js') },
  logger: new QueryLogger()
}
jkiyo commented 5 years ago

For those who still have some trouble to use Subscribers in vanilla, here is my solution:

I've created an utility function to apply decorators into classes:

const DecorateClass = (decorators, target) => {
  decorators = Array.isArray(decorators) ? decorators : [decorators]
  decorators.forEach(decorator => decorator(target))
  return target
}

And then use the decorators:

const { EventSubscriber } = require('typeorm')

class EverythingSubscriber {
  /**
   * Returns the class of the entity to which events will listen.
   * If this method is omitted, then subscriber will listen to events of all entities.
   *
   * listenTo?(): Function;
   */

  /* afterLoad?(entity: Entity): Promise<any>|void; */
  afterLoad ({ entity }) { }

  /* beforeInsert?(event: InsertEvent<Entity>): Promise<any>|void; */
  beforeInsert ({ connection, queryRunner, manager, entity }) { }

  /* beforeUpdate?(event: UpdateEvent<Entity>): Promise<any>|void; */
  beforeUpdate ({ connection, queryRunner, manager, entity, databaseEntity, updatedColumns, updatedRelations }) { }

  /* beforeRemove?(event: RemoveEvent<Entity>): Promise<any>|void; */
  beforeRemove ({ connection, queryRunner, manager, entity, databaseEntity, entityId }) { }

  /* afterInsert?(event: InsertEvent<Entity>): Promise<any>|void; */
  afterInsert ({ connection, queryRunner, manager, entity }) { }

  /* afterUpdate?(event: UpdateEvent<Entity>): Promise<any>|void; */
  afterUpdate ({ connection, queryRunner, manager, entity }) { }

  /* afterRemove?(event: RemoveEvent<Entity>): Promise<any>|void; */
  afterRemove ({ connection, queryRunner, manager, entity }) { }
}

module.exports = DecorateClass(EventSubscriber(/* Decorator params */), EverythingSubscriber)

The DecorateClass function will only work on Classes, to decorate a method the implementation must be different...

pleerock commented 5 years ago

@jkiyo this feature is opened for contributions, if you are interested we can make an official solution for this problem

jkiyo commented 5 years ago

@pleerock I don't think that my solution is the best fit to solve the "subscribers in vanilla javascript" problem. I'm a newbie at the typeorm codebase, and I don't have much freetime to explore the code to make a contribution.

But why does the ConnectionMetadataBuilder.buildSubscribers method filters the subscribers passed by the ConnectionOptions.subscribers and use only the ones decorated by the EventSubscriber decorator (that stores the subscriber class at MetadataArgsStorage)?

If there is no impact, can we "skip" this filter for the subscribers provided through ConnectionOptions?

ameinhardt commented 4 years ago

thanks @jkiyo, registering with EventSubscriber works indeed! I'm not familiar with typescript either, so it would be nice to register listeners in Schemas like

listeners: [{
  type:         'after-insert',
  propertyName: (e) => console.log('AFTER INSERTED entity', e)
}]

Maybe EntitySchemaTransformer.prototype.transform() https://github.com/typeorm/typeorm/blob/efe1a5ff6e59c61056455b28cc6186e315e60b70/src/entity-schema/EntitySchemaTransformer.ts#L111 could get another block:

if(options.listeners) {
  options.listeners.forEach((listener) => {
    metadataArgsStorage.entityListeners.push({
      target:       options.target || options.name,
      type:         listener.type,
      propertyName: listener.propertyName
    });
  });
}

For being executed correctly, isAllowed could also receive the metadata.target: https://github.com/typeorm/typeorm/blob/efe1a5ff6e59c61056455b28cc6186e315e60b70/src/subscriber/Broadcaster.ts#L37 to being able to check for (this.entityMetadata.target === target): https://github.com/typeorm/typeorm/blob/efe1a5ff6e59c61056455b28cc6186e315e60b70/src/metadata/EntityListenerMetadata.ts#L63

and finally in execute() of that listener: https://github.com/typeorm/typeorm/blob/efe1a5ff6e59c61056455b28cc6186e315e60b70/src/metadata/EntityListenerMetadata.ts#L70

    if (typeof this.propertyName === 'function')
      return this.propertyName(entity);

Do you think that works, @pleerock?

imnotjames commented 3 years ago

For questions, please check out the community slack or check TypeORM's documentation page on other support avenues - cheers!

JoeCase commented 2 years ago

Has anyone advanced this? This is a bit old but running into this issued right now for listeners and subscribers in javascript.

Ttou commented 1 week ago

@jkiyo your solution will listen all entity schema, how to listen one