Skip to content

Releases: boostercloud/booster

Command handlers that return values

02 Aug 18:00
Compare
Choose a tag to compare

In this release we introduce the @Returns decorator that allows you to define the return type of a command handler.

This value will be returned to the client through GraphQL, instead of just returning true (that's left for the case of void).

As an example:

@Command({
  authorize: 'all',
})
export class CreateProduct {
  public constructor(readonly sku: string, readonly price: number) {}

  @Returns(String)
  public static async handle(command: CreateProduct, register: Register): Promise<string> {
    return "Product created!"
  }
}

Learn more in the Booster documentation

Introducing GraphQL Pagination on queries

08 Jul 15:46
Compare
Choose a tag to compare

From now on the Booster GraphQL API includes a type for your read models that stands for List<YourReadModelName>, which is the official way to work with pagination. Alternatively, and for compatibility with previous versions, there is still the type without the List prefix, which will be deprecated in future versions.

The new Read Model List type includes some new parameters that can be used on queries:

  • limit; an integer that specifies the maximum number of items to be returned.
  • afterCursor; a parameter to set the cursor property returned by the previous query, if not null.

Example:

query {
  ListProductReadModels
  (
    limit: 1,
    afterCursor: { id: "last-page-item"}
  ) {
    id
    sku
    availability
    price
  }
}

Besides the parameters, this type also returns a type {your-read-model-name}Connection, it includes the following properties:

  • cursor; if there are more results to paginate, it will return the object to pass to the afterCursor parameter on the next query. If there aren't more items to be shown, it will be undefined.
  • items; the list of items returned by the query, if there aren't any, it will be an empty list.

Local provider enabling and lazy provider loading

07 Jul 13:10
Compare
Choose a tag to compare

From now on, setting a provider is done by specifying config.providerPackage, rather than the old config.provider.
In this new way, instead of importing the Provider object and assigning it in the config.provider field, you simply specify the name of the package as a string:

  // Old way – Now deprecated
  import * as AWS from '@boostercloud/framework-provider-aws'

  Booster.configure(environment.name, (config: BoosterConfig) => {
    config.appName = 'my-cool-app'
    config.provider = AWSProvider()
  })

  // New way
  Booster.configure(environment.name, (config: BoosterConfig) => {
    config.appName = 'my-cool-app'
    config.providerPackage = '@boostercloud/framework-provider-aws'

    // New optional field for specifying rockets
    config.rockets = [/* your rockets here */]
  })

With this addition, now Booster provider libraries are loaded on runtime when they are needed, meaning that if you want to deploy the same application to different providers (e.g. AWS, and Azure) you won't get any runtime errors complaining that the SDK for the cloud provider is missing.

Commands' before hooks

05 Jul 15:01
Compare
Choose a tag to compare

This time we're adding before hooks to command handlers! They work the same as the Read Models ones, except that you can modify inputs instead of filters.

Here's an example in case you're curious, where we just check if the cartUserId is equal to the currentUser.id, which is the user id extracted from the auth token:

@Command({
  authorize: [User],
  before: [beforeFn],
})
export class ChangeCartItem {
  public constructor(readonly cartId: UUID, readonly productId: UUID, readonly quantity: number) {
  }
}

function beforeFn(input: CommandInput, currentUser?: UserEnvelope): CommandInput {
  if (input.cartUserId !== currentUser.id) {
    throw NonAuthorizedUserException() // We don't let this user to trigger the command
  }
  return input
}

This way, we can throw an exception and avoid this user calling this command.

Bug fix: Create classes instances of entities, read models and events in the Booster class

05 Jul 14:44
Compare
Choose a tag to compare

🐞 Bug Fixes

Until now, when you get entities, read models and events using the Booster class, you get a raw javascript object instead of an instance of their respective classes.
To fix this bug, a proper instance has been created for entities, read models and events of the Booster class.

For example, in the case of entities, the original code called the deprecated fetchEntitySnapshot method:

public static entity<TEntity extends EntityInterface>(
    entityClass: Class<TEntity>,
    entityID: UUID
  ): Promise<TEntity | undefined> {
    return fetchEntitySnapshot(this.config, this.logger, entityClass, entityID)
  }

And now, the deprecated fetchEntitySnapshot method has been removed and we call a new method called createInstance to fix it.

public static async entity<TEntity extends EntityInterface>(
    entityClass: Class<TEntity>,
    entityID: UUID
  ): Promise<TEntity | undefined> {
    const eventStore = new EventStore(this.config, this.logger)
    const entitySnapshotEnvelope = await eventStore.fetchEntitySnapshot(entityClass.name, entityID)
    return entitySnapshotEnvelope ? createInstance(entityClass, entitySnapshotEnvelope) : undefined
  }
export function createInstance<T>(instanceClass: Class<T>, rawObject: Record<string, any>): T {
  const instance = new instanceClass()
  Object.assign(instance, rawObject)
  return instance
}

Read models' before hooks

01 Jul 13:21
Compare
Choose a tag to compare

With this new version, we can now add before hooks to our @ReadModel annotations. This can become really handy to either:

  • Validate parameters
  • Change read model filters on the fly with programmatic logic
  • Deny access to specific users

For example, we could deny user's access when he/she's not the Cart's owner:

@ReadModel({
  authorize: [User],
  before: [validateUser],
})
export class CartReadModel {
  public constructor(
    readonly id: UUID,
    readonly userId: UUID
  ) {}
  // Your projections go here
}

function validateUser(filter: FilterFor<CartReadModel>, currentUser?: UserEnvelope): FilterFor<CartReadModel> {
  if (filter?.userId !== currentUser.id) throw NotAuthorizedError("...")
  return filter
}

You can also chain these before functions to split your logic:

import { changeFilters } from '../../filters-helper' // You can also use external functions!

@ReadModel({
  authorize: [User],
  before: [validateUser, validateEmail, changeFilters],
})
export class CartReadModel {
  public constructor(
    readonly id: UUID,
    readonly userId: UUID
  ) {}

  // Your projections go here
}

function validateUser(filter: FilterFor<CartReadModel>, currentUser?: UserEnvelope): FilterFor<CartReadModel> {
  if (filter.userId !== currentUser.id) throw NotAuthorizedError("...")
  return filter // This filter will be passed as a parameter to the validateEmail function
}

function validateEmail(filter: FilterFor<CartReadModel>, currentUser?: UserEnvelope): FilterFor<CartReadModel> {
  if (!filter.email.includes('myCompanyDomain.com')) throw NotAuthorizedError("...")
  return filter
}

As a side note, remember that the order in which filters are specified matters.

New GraphQL Schema and complex filters

03 May 11:11
Compare
Choose a tag to compare

Finally, one of the most requested features is here! Complex filters on the GraphQL API. This change will allow users to filter read models like never before, for instance:

query {
  ProductReadModels(filter: { price: { gt: 200 } }) {
    id
    sku
    availability
    price
  }
}

Now you can filter by specific types, complex properties, and even use filter combinators! You can find the complete set of filters in the documentation.

⚠ Breaking Changes

To perform these filters, the GraphQL schema has been redesigned, which introduces breaking changes for existing Booster applications with versions lower than v0.16.0.

Those are the required steps to migrate your app to use Booster v0.16.0 or later successfully.

Update package.json file.

Include the following dependencies to your package.json file on the devDependencies section.

"ttypescript": "1.5.12",
"metadata-booster": "0.3.1"

Also, update the TypeScript dependency version to 4.1.5 a ts-node to 9.1.1.

"typescript": "4.1.5",
"ts-node": "9.1.1",

Finally, change the compile script to use ttsc without npx instead of using npx tsc, it should look like this:

"compile": "ttsc -b tsconfig.json",

Update tsconfig.json file.

Remove the "emitDecoratorMetadata": true line from the tsconfig.json file, and add the following property.

"plugins": [
      { "transform": "metadata-booster" }
 ]

This property allows the application to use the metadata-booster plugin when compiling TypeScript code, allowing the compiled JavaScript files to know the properties of every type created. Ensure you have the following properties:

"experimentalDecorators": true,
"plugins": [
    { "transform": "metadata-booster" }
]

Note: remember to delete the emitDecoratorMetadata property to avoid getting compilation errors.

Upgrade npm

To be able to run the npm scripts properly, especially the compilation one, you will need to upgrade at least to npm v7 or later.

And that's it! 🎉 Your Booster application is ready to upgrade and use the new GraphQL API filters! 🚀

API for reading events and huge reliability improvements on high loads

19 Apr 15:41
Compare
Choose a tag to compare

It's been a lot going on in Booster lately so expect some very exciting news listed here!

⚠ Breaking Changes

  • In version 0.12.0 there was a change in the AWS Provider event store (DynamoDB) to create two new indexes. If you are updating from version 0.11.* or older, you will get an error when deploying your app because AWS limits the number of new indexes per deployment to just one. The solution is simple but involves doing two deploys. Follow these steps:
    1. Update all your Booster dependencies (core, types, etc) to this new version, "0.14.4", except the framework-provider-aws-infrastructure package.
    2. For that package, use the following version: "0.14.4-pre12step.1". This version is exactly the same as the "0.14.4" but it only creates one index. The package.json file should show something similar to this:
      image
    3. Deploy your application with boost deploy. You should not see any error now.
    4. When finished, change the version of framework-provider-aws-infrastructure to the final one, "0.14.4". The package.json file should now show something like this:
      image
    5. Do another deployment and you are done.

⚰️ Deprecated

  • Method Booster.fetchEntitySnapshot is deprecated and will be deleted in the next major version. Use Booster.entity instead.

🚀 Features & Improvements

  • Booster now generates a GraphQL API to directly read events (besides your read models). Documentation is being created (issue #790), but you can take a look at this PR description to know what is it about
  • You can also query events in your command or event handlers by using the Booster.events method
  • The storage and processing of events is now much more reliable on extremely high loads (thousands of events per second)
  • Added experimental support for Azure provider 🎉
  • Added experimental support for Kubernetes provider 🎉

🩹 Fixes

  • Fixed a bug caused by a race condition on huge loads when projecting read models. Now optimistic concurrency is used to ensure data is not corrupted.
  • Fixed a bug that was causing some events to be overwritten on huge loads.
  • Many, many, many more bug fixes

🐛 Known bug

  • We are working to provide full support to filter read models by any property. Some of those filters, although not documented, were available using a "beta" structure. If you were one of those adventurous users that were using them, you need to know that they are broken in this version. In the next version we will release full support for those filters, so stay tuned!

Ability to include "assets" in your project

02 Feb 11:34
Compare
Choose a tag to compare

This release allows you to include any file/folder as static assets in your project, ensuring they will be present when deployed. This is ideal for ".env" files, template files used for responses, etc.
For more information, take a look at the documentation of that feature: https://github.com/boostercloud/booster/tree/main/docs#booster-configuration

⚠ Breaking Changes

None! 🎉

🚀 Improvements

  • Now you can access in runtime the Booster configuration by doing Booster.config
  • The InfrastructureRocket.mountStack method now receives the Booster configuration as an extra parameter, making rockets much more flexible and allowing them to integrate better with the framework

🩹 Fixes

  • Fixed a bug with GraphQL when a project with not ReadModels was deployed
  • Fixed "missing rimraf" dependency bug.
    NOTE: Just by updating your Booster dependencies to this version in your current projects, that error won't go away, as it was in the "package.json" file that is generated when you create a new project. Please add "npx " to your package.json file as shown here:
    image
    Of course, new projects generated with the new version of the Booster CLI don't need to do anything.

Huge stability improvements. Move from Yarn to NPM

25 Jan 14:45
Compare
Choose a tag to compare

This is a huge release regarding the stability of the framework.

We’ve been having a lot of random issues lately because of dependency problems. Long story short: the underlying reason was that there are differences in the dependency management between Yarn and NPM and we were using both applications in different stages of the framework development. That was causing subtle errors but really hard to track, especially when running integration tests and deploying user applications.

Now, we only use NPM and Lerna to manage the monorepo.

⚠ Breaking Changes

There is one breaking change introduced in this release, but it is really easy to solve. After switching your Booster dependencies to this version, you need to change your src/index.ts file from this:

import { Booster } from '@boostercloud/framework-core'
export {
  Booster,
  boosterEventDispatcher,
  boosterPreSignUpChecker,
  boosterServeGraphQL,
  boosterNotifySubscribers,
  boosterTriggerScheduledCommand,
} from '@boostercloud/framework-core'

Booster.start()

To this (you can copy and paste):

import { Booster } from '@boostercloud/framework-core'
export {
  Booster,
  boosterEventDispatcher,
  boosterPreSignUpChecker,
  boosterServeGraphQL,
  boosterNotifySubscribers,
  boosterTriggerScheduledCommand,
} from '@boostercloud/framework-core'

Booster.start(__dirname) // <— This changed

As you can see, the only thing that changed is that now Booster.start takes a parameter with the folder where the file is located, which is __dirname.

New projects generated with the new version of the CLI boost new:project will work as expected and don’t need any change.



🚀 Improvements

There are several non-planned but very welcome improvements in this release:

  • Deployments are faster: When you deploy your Booster application, the framework will now prepare your code so that only the production dependencies are deployed. These dependences are much much lighter than the development dependencies, so it takes less time to compress and upload the project to the cloud provider
  • Warm up times are shorter: AWS Lambda functions (and the equivalent in other providers) are 10 times smaller, meaning that the first time they are executed (when the code needs to be parsed) will take much less time.

🩹 Fixes

This release has an uncountable number of fixes, especially regarding dependency management, that have made the framework much more stable and reliable.