Skip to content

Commit

Permalink
init
Browse files Browse the repository at this point in the history
  • Loading branch information
JacopoPatroclo committed Feb 21, 2024
0 parents commit 0e6fe02
Show file tree
Hide file tree
Showing 17 changed files with 3,879 additions and 0 deletions.
23 changes: 23 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: CI workflow
on: [push, pull_request]
jobs:
test:
runs-on: ${{ matrix.os }}
strategy:
matrix:
node-version: [10.x, 12.x, 14.x, 16.x, 18.x, 20.x]
os: [ubuntu-latest, windows-latest]
steps:
- uses: actions/checkout@v2
- name: Use Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v1
with:
node-version: ${{ matrix.node-version }}
- name: Setup pnpm
uses: pnpm/action-setup@v3
with:
version: latest
- name: Install Dependencies
run: pnpm install --ignore-scripts
- name: Test
run: pnpm run test
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
node_modules
4 changes: 4 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"singleQuote": true,
"trailingComma": false
}
5 changes: 5 additions & 0 deletions .prittierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
# Add files here to ignore them from prettier formatting

/dist
/coverage
pnpm-lock.yaml
3 changes: 3 additions & 0 deletions .taprc
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
ts: false
jsx: false
coverage: false
122 changes: 122 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,122 @@
# fastify-better-flash

[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](http://standardjs.com/) ![CI workflow](__MY_PLUGIN_URL__
/workflows/CI%20workflow/badge.svg)

This plugin is inspired by the [@fastify/flash](https://github.com/fastify/fastify-flash/tree/master) plugin. The main difference is the way it access the messages, using a "dot syntax" and it has a better typescript support. See below for more details.

Supports Fastify versions `4.x`

## Install
```sh
npm i fastify-better-flash @fastify/secure-session
```
```sh
pnpm i fastify-better-flash @fastify/secure-session
```
```sh
yarn add fastify-better-flash @fastify/secure-session
```


## Usage
Flash messages are stored in the session. First, we need to register the session plugin: (@fastify/secure-session)[https://www.npmjs.com/package/@fastify/secure-session].

```js
const fastify = require('fastify')()
const fastifySession = require('@fastify/secure-session')

fastify.register(fastifySession, {
// adapt this to point to the directory where secret-key is located
key: fs.readFileSync(path.join(__dirname, 'secret-key')),
cookie: {
// options from setCookie, see https://github.com/fastify/fastify-cookie
}
})
fastify.register(require('fastify-better-flash'))

fastify.listen({ port: 3000 })
```

### Usage

In your route handler you can use the `flash` method to set a flash message.
Let's say that we want to have the following messages:

```
success: boolean
validations: {
errors: Array<{ field: string, message: string }>
}
genericError: string
```

we can set them in the route handler like this:

```js

fastify.get('/', async (request, reply) => {
request.flash('success', true)
request.flash('validations.errors', [{ field: 'email', message: 'Email is required' }])
request.flash('genericError', 'Something went wrong')
})

```

Then we can consume them like this:

```js

fastify.get('/flash', async (request, reply) => {
const success = reply.flash('success')
// Note that we can access the nested object using the dot notation
const validation = reply.flash('validations')
const validationErrors = validation ? validation.errors : []
// it works with arrays too, this will return the first error field property
const validationErrors = reply.flash('validations.errors.0.field')
const genericError = reply.flash('genericError')
return { success, validationErrors, genericError }
})

```

### Typescript

You can define the schema of your flash messages using typescript.
By using the `fastify-better-flash` module you can customize the interface `FlashSessionType` that be extended to define the schema of your flash messages.

```ts

declare module 'fastify-better-flash' {
export interface FlashSessionType {
// Define your flash message schema here
success: boolean
validations: {
errors: Array<{ field: string, message: string }>
}
}
}

```

And then in your route handler you can use the `flash` method to set a flash message.

```ts

fastify.get('/', async (request, reply) => {
// Also the keys will be typed
request.flash('success', 'This is a success message') // This will not compile

const validationErrors = reply.flash('validations.errors') // This will have the correct types
return reply.redirect('/flash')
})

```

By design the getter method `flash` will alaways return the type that you have defined or `undefined`. This is meant to signal the developer that the presence of the object in the flash session storage is not guaranteed on every request.

## Acknowledgements

## License

Licensed under [MIT](./LICENSE).<br/>
51 changes: 51 additions & 0 deletions flash.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const utils = require('./utils')

module.exports = function () {
return {
/**
* @name flash
* @description Set a flash message
* @param {string} scope
* @param {any} value
*/
setter (scope, value) {
if (!this.session) {
throw new Error('Session not found, make sure to register @fastify/secure-session')
}

if (typeof scope !== 'string') {
throw new Error('Expected scope to be a string')
}

// in this way we are a drop in replacement for @fastify/flash
let currentSession = this.session.get('flash')
if (!currentSession) {
currentSession = {}
}

utils.attachThingToObjectGivenPath(scope, currentSession, value)

this.session.set('flash', currentSession)
},
getter (scope) {
if (!this.request.session) {
throw new Error('Session not found, make sure to register @fastify/secure-session')
}

if (typeof scope !== 'string') {
throw new Error('Expected scope to be a string')
}

const currentSession = this.request.session.get('flash')
if (!currentSession) {
return undefined
}

const output = utils.retriveThingFromObjectGivenPath(scope, currentSession)
utils.deleteThingFromObjectGivenPath(scope, currentSession)

this.request.session.set('flash', currentSession)
return output
}
}
}
42 changes: 42 additions & 0 deletions index.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import './module.d'
import { FastifyPluginCallback } from 'fastify'
import { FlashSessionType } from 'fastify-better-flash'

declare module 'fastify' {
export interface FastifyRequest {
flash: <PathType extends Path<FlashSessionType>>(scope: PathType, message: PathToValue<FlashSessionType, PathType>) => void
}
export interface FastifyReply {
flash: <PathType extends Path<FlashSessionType>>(scope: PathType) => PathToValue<FlashSessionType, PathType> | undefined
}
}

export type Path<T = unknown> = T extends object
? T extends Array<infer ItemType>
? ItemType extends object ? `${number}` | `${number}.${Path<ItemType>}` : `${number}`
: { [K in keyof T]: T[K] extends object ? `${K & string}` | `${K & string}.${Path<T[K]>}` : `${K & string}` }[keyof T]
: string

export type PathToValue<T = unknown, P extends Path<T> = Path<T>> =
T extends object
? P extends keyof T
? T[P]
: P extends `${infer Key}.${infer Rest}`
? Key extends keyof T
? Rest extends Path<T[Key]>
? PathToValue<T[Key], Rest>
: never
: T extends Array<infer ItemType>
? Rest extends Path<ItemType>
? PathToValue<ItemType, Rest>
: never
: never
: P extends `${number}`
? T extends Array<infer ItemType>
? ItemType
: never
: never
: any

declare const betterflash: FastifyPluginCallback<() => string>
export default betterflash
16 changes: 16 additions & 0 deletions index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
'use strict'

const fp = require('fastify-plugin')

module.exports = fp(function (fastify, opts, done) {
const flash = require('./flash')()
fastify.decorateRequest('flash', flash.setter)
fastify.decorateReply('flash', flash.getter)
done()
}, {
fastify: '^4.x',
name: 'fastify-better-flash',
decorators: {
request: ['session']
}
})
4 changes: 4 additions & 0 deletions module.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module 'fastify-better-flash' {
// eslint-disable-next-line @typescript-eslint/no-empty-interface
export interface FlashSessionType {}
}
37 changes: 37 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{
"name": "fastify-better-flash",
"version": "1.0.0",
"description": "",
"main": "index.js",
"directories": {
"test": "test"
},
"scripts": {
"test": "npm run lint && npm run unit && npm run test:typescript",
"lint": "standard && npm run lint:typescript",
"lint:typescript": "ts-standard",
"test:typescript": "tsd",
"unit": "node --test"
},
"keywords": [],
"author": "",
"license": "MIT",
"types": "index.d.ts",
"dependencies": {
"fastify-plugin": "^4.0.0"
},
"devDependencies": {
"@types/node": "^20.4.4",
"fastify": "^4.0.0-rc.2",
"fastify-tsconfig": "^2.0.0",
"prettier": "^3.2.5",
"standard": "^17.0.0",
"ts-standard": "^12.0.1",
"tsd": "^0.30.4",
"typescript": "^5.3.3",
"@fastify/secure-session": "^7.0.0"
},
"tsd": {
"directory": "test"
}
}
Loading

0 comments on commit 0e6fe02

Please sign in to comment.