- Delete old Vite+Svelte frontend - Initialize new SvelteKit project with TypeScript - Configure Tailwind CSS v4 + DaisyUI - Implement JWT authentication with auto-refresh - Create login page with form validation (Zod) - Add protected route guards - Update Docker configuration for single-stage build - Add E2E tests with Playwright (6/11 passing) - Fix Svelte 5 reactivity with $state() runes Known issues: - 5 E2E tests failing (timing/async issues) - Token refresh implementation needs debugging - Validation error display timing
163 lines
4.6 KiB
Markdown
163 lines
4.6 KiB
Markdown
# @poppinss/macroable
|
|
> Extend classes from outside in using Macros and getters
|
|
|
|
[![gh-workflow-image]][gh-workflow-url] [![typescript-image]][typescript-url] [![npm-image]][npm-url] [![license-image]][license-url]
|
|
|
|
Macroable offers a simple API for adding properties and getters to the class prototype. You might not even need this package, if you are happy writing `Object.defineProperty` calls yourself.
|
|
|
|
## Usage
|
|
Install the package from npm packages registry as follows.
|
|
|
|
```sh
|
|
npm i @poppinss/macroable
|
|
|
|
# yarn lovers
|
|
yarn add @poppinss/macroable
|
|
```
|
|
|
|
And import the `Macroable` class.
|
|
|
|
```ts
|
|
import Macroable from '@poppinss/macroable'
|
|
export class Route extends Macroable {}
|
|
```
|
|
|
|
Now, you can add properties to the Route class from outside-in. This is usually needed, when you want the consumer of your classes to be able to extend them by adding custom properties.
|
|
|
|
## Macros
|
|
Getters are added to the class prototype directly.
|
|
|
|
```ts
|
|
Route.macro('head', function (uri, callback) {
|
|
return this.route(['HEAD'], uri, callback)
|
|
})
|
|
```
|
|
|
|
And now, you can will be use the `head` method from an instance of the `Route` class.
|
|
|
|
```ts
|
|
const route = new Route()
|
|
route.head('/', () => {})
|
|
```
|
|
|
|
Adding a macro is same as writing the following code in JavaScript.
|
|
|
|
```ts
|
|
Route.prototype.head = function () {
|
|
}
|
|
```
|
|
|
|
## Instance properties
|
|
Since, macros are defined on the prototype of the class and therefore they loose the `this` context when destructured from the class instance. For example:
|
|
|
|
```ts
|
|
HttpContext.macro('getUser', function (this: HttpContext) {
|
|
return this.auth.user
|
|
})
|
|
|
|
const { getUser } = ctx
|
|
getUser() // ❌ Error: Cannot read property auth of undefined
|
|
```
|
|
|
|
In order to fix this issue, the properties that can be destructured must be defined as instance properties on the class.
|
|
|
|
```ts
|
|
HttpContext.instanceProperty('getUser', function (this: HttpContext) {
|
|
return this.auth.user
|
|
})
|
|
|
|
const { getUser } = ctx
|
|
getUser() // ✅ Works fine
|
|
```
|
|
|
|
## Getters
|
|
Getters are added to the class prototype using the `Object.defineProperty`. The implementation of a getter is always a function.
|
|
|
|
```ts
|
|
Route.getter('version', function () {
|
|
return 'v1'
|
|
})
|
|
```
|
|
|
|
And now access the version as follows.
|
|
|
|
```ts
|
|
const route = new Route()
|
|
route.version // v1
|
|
```
|
|
|
|
Adding a getter is same as writing the following code in JavaScript.
|
|
|
|
```ts
|
|
Object.defineProperty(Route.prototype, 'version', {
|
|
get() {
|
|
const value = callback()
|
|
return value
|
|
},
|
|
configurable: false,
|
|
enumerable: false,
|
|
})
|
|
```
|
|
|
|
## Singleton getters
|
|
Singleton getters are also defined on the class prototype. However, their values are cached after the first access.
|
|
|
|
```ts
|
|
const singleton = true
|
|
|
|
Mysql.getter('version', function () {
|
|
return this.config.driver.split('-')[1]
|
|
}, singleton)
|
|
```
|
|
|
|
Adding a singleton getter is same as writing the following code in JavaScript.
|
|
|
|
```ts
|
|
Object.defineProperty(Mysql.prototype, 'version', {
|
|
get() {
|
|
const value = callback()
|
|
|
|
// Cache value on the class instance
|
|
Object.defineProperty(this, 'version', {
|
|
configurable: false,
|
|
enumerable: false,
|
|
value: value,
|
|
writable: false,
|
|
})
|
|
|
|
return value
|
|
},
|
|
configurable: false,
|
|
enumerable: false,
|
|
})
|
|
```
|
|
|
|
## TypeScript types
|
|
You will have to use [module augmentation](https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation) in order to define the types for the dynamically added properties.
|
|
|
|
## Contributing
|
|
|
|
One of our primary goals is to have a vibrant community of users and contributors who believes in the principles of the framework.
|
|
|
|
We encourage you to read the [contribution guide](https://github.com/poppinss/.github/blob/main/docs/CONTRIBUTING.md) before contributing to the framework.
|
|
|
|
## Code of Conduct
|
|
|
|
In order to ensure that the community is welcoming to all, please review and abide by the [Code of Conduct](https://github.com/poppinss/.github/blob/main/docs/CODE_OF_CONDUCT.md).
|
|
|
|
## License
|
|
|
|
Poppinss macroable is open-sourced software licensed under the [MIT license](LICENSE.md).
|
|
|
|
[gh-workflow-image]: https://img.shields.io/github/actions/workflow/status/poppinss/macroable/checks.yml?style=for-the-badge
|
|
[gh-workflow-url]: https://github.com/poppinss/macroable/actions/workflows/checks.yml "Github action"
|
|
|
|
[typescript-image]: https://img.shields.io/badge/Typescript-294E80.svg?style=for-the-badge&logo=typescript
|
|
[typescript-url]: "typescript"
|
|
|
|
[npm-image]: https://img.shields.io/npm/v/@poppinss/macroable.svg?style=for-the-badge&logo=npm
|
|
[npm-url]: https://npmjs.org/package/@poppinss/macroable 'npm'
|
|
|
|
[license-image]: https://img.shields.io/npm/l/@poppinss/macroable?color=blueviolet&style=for-the-badge
|
|
[license-url]: LICENSE.md 'license'
|