@prairielearn/flash 2.0.0 → 2.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,21 @@
1
1
  # @prairielearn/flash
2
2
 
3
+ ## 2.0.2
4
+
5
+ ### Patch Changes
6
+
7
+ - 0f7c90f: Upgrade all JavaScript dependencies
8
+ - Updated dependencies [0f7c90f]
9
+ - @prairielearn/html@4.0.2
10
+
11
+ ## 2.0.1
12
+
13
+ ### Patch Changes
14
+
15
+ - 901fce8: Upgrade all JavaScript dependencies
16
+ - Updated dependencies [901fce8]
17
+ - @prairielearn/html@4.0.1
18
+
3
19
  ## 2.0.0
4
20
 
5
21
  ### Major Changes
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\nimport type { Request, Response, NextFunction } from 'express';\nimport { HtmlSafeString, html } from '@prairielearn/html';\n\nconst als = new AsyncLocalStorage<FlashStorage>();\n\nexport type FlashMessageType = 'notice' | 'success' | 'warning' | 'error';\n\nexport interface FlashMessage {\n type: FlashMessageType;\n message: string;\n}\n\nexport function flashMiddleware() {\n return (req: Request, _res: Response, next: NextFunction) => {\n const flashStorage = makeFlashStorage(req);\n als.run(flashStorage, () => next());\n };\n}\n\nexport function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];\nexport function flash(type: FlashMessageType, message: string | HtmlSafeString): void;\nexport function flash(\n type?: FlashMessageType | FlashMessageType[],\n message?: string | HtmlSafeString,\n) {\n const flashStorage = als.getStore();\n if (!flashStorage) {\n throw new Error('flash() must be called within a request');\n }\n\n if (Array.isArray(type)) {\n const messages = type.flatMap((type) => flashStorage.get(type));\n type.forEach((t) => flashStorage.clear(t));\n return messages;\n }\n\n if (type != null && message != null) {\n flashStorage.add(type, message);\n return;\n }\n\n if (type != null) {\n const message = flashStorage.get(type);\n flashStorage.clear(type);\n return message;\n }\n\n const messages = flashStorage.getAll();\n flashStorage.clearAll();\n return messages;\n}\n\ninterface FlashStorage {\n add(type: FlashMessageType, message: string | HtmlSafeString): void;\n get(type: FlashMessageType): FlashMessage[] | null;\n getAll(): FlashMessage[];\n clear(type: FlashMessageType): void;\n clearAll(): void;\n}\n\nfunction makeFlashStorage(req: Request): FlashStorage {\n if (!req.session) {\n throw new Error('@prairielearn/flash requires session support');\n }\n\n const session = req.session as any;\n\n return {\n add(type: FlashMessageType, message: string | HtmlSafeString) {\n session.flash ??= [];\n session.flash.push({ type, message: html`${message}`.toString() });\n },\n get(type: FlashMessageType) {\n const messages = session.flash ?? [];\n return messages.filter((message: FlashMessage) => message.type === type);\n },\n getAll() {\n return session.flash ?? [];\n },\n clear(type: FlashMessageType) {\n session.flash = session.flash?.filter((message: FlashMessage) => message.type !== type) ?? [];\n },\n clearAll() {\n session.flash = [];\n },\n } satisfies FlashStorage;\n}\n"]}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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","sourcesContent":["import { AsyncLocalStorage } from 'node:async_hooks';\n\nimport type { Request, Response, NextFunction } from 'express';\n\nimport { HtmlSafeString, html } from '@prairielearn/html';\n\nconst als = new AsyncLocalStorage<FlashStorage>();\n\nexport type FlashMessageType = 'notice' | 'success' | 'warning' | 'error';\n\nexport interface FlashMessage {\n type: FlashMessageType;\n message: string;\n}\n\nexport function flashMiddleware() {\n return (req: Request, _res: Response, next: NextFunction) => {\n const flashStorage = makeFlashStorage(req);\n als.run(flashStorage, () => next());\n };\n}\n\nexport function flash(type?: FlashMessageType | FlashMessageType[]): FlashMessage[];\nexport function flash(type: FlashMessageType, message: string | HtmlSafeString): void;\nexport function flash(\n type?: FlashMessageType | FlashMessageType[],\n message?: string | HtmlSafeString,\n) {\n const flashStorage = als.getStore();\n if (!flashStorage) {\n throw new Error('flash() must be called within a request');\n }\n\n if (Array.isArray(type)) {\n const messages = type.flatMap((type) => flashStorage.get(type));\n type.forEach((t) => flashStorage.clear(t));\n return messages;\n }\n\n if (type != null && message != null) {\n flashStorage.add(type, message);\n return;\n }\n\n if (type != null) {\n const message = flashStorage.get(type);\n flashStorage.clear(type);\n return message;\n }\n\n const messages = flashStorage.getAll();\n flashStorage.clearAll();\n return messages;\n}\n\ninterface FlashStorage {\n add(type: FlashMessageType, message: string | HtmlSafeString): void;\n get(type: FlashMessageType): FlashMessage[] | null;\n getAll(): FlashMessage[];\n clear(type: FlashMessageType): void;\n clearAll(): void;\n}\n\nfunction makeFlashStorage(req: Request): FlashStorage {\n if (!req.session) {\n throw new Error('@prairielearn/flash requires session support');\n }\n\n const session = req.session as any;\n\n return {\n add(type: FlashMessageType, message: string | HtmlSafeString) {\n session.flash ??= [];\n session.flash.push({ type, message: html`${message}`.toString() });\n },\n get(type: FlashMessageType) {\n const messages = session.flash ?? [];\n return messages.filter((message: FlashMessage) => message.type === type);\n },\n getAll() {\n return session.flash ?? [];\n },\n clear(type: FlashMessageType) {\n session.flash = session.flash?.filter((message: FlashMessage) => message.type !== type) ?? [];\n },\n clearAll() {\n session.flash = [];\n },\n } satisfies FlashStorage;\n}\n"]}
@@ -1,6 +1,6 @@
1
1
  import { assert } from 'chai';
2
- import { flashMiddleware, flash } from './index.js';
3
2
  import { html } from '@prairielearn/html';
3
+ import { flashMiddleware, flash } from './index.js';
4
4
  describe('flash', () => {
5
5
  it('throws an error if no session present', () => {
6
6
  assert.throw(() => {
@@ -1 +1 @@
1
- {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from 'chai';\n\nimport { flashMiddleware, flash } from './index.js';\nimport { html } from '@prairielearn/html';\n\ndescribe('flash', () => {\n it('throws an error if no session present', () => {\n assert.throw(() => {\n flashMiddleware()({} as any, {} as any, () => {});\n });\n });\n\n it('throws an error when middleware is not used', () => {\n assert.throw(() => {\n flash('notice', 'Hello world');\n });\n assert.throw(() => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n });\n });\n\n it('adds a flash using string message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: 'hello world' }]);\n });\n });\n\n it('adds a flash and escapes unsafe HTML', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', '<b>hello world</b>');\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },\n ]);\n });\n });\n\n it('adds a flash with HTML-safe message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);\n });\n });\n\n it('stores multiples flashes with the same type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('notice', '<< goodbye world');\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'notice', message: '&lt;&lt; goodbye world' },\n { type: 'notice', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns flash message for a given type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns all flashes', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'error', message: '&lt;&lt; goodbye world' },\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('clears flash after it has been read', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n\n assert.deepEqual(flash('notice'), []);\n assert.deepEqual(flash('error'), []);\n assert.deepEqual(flash('success'), []);\n assert.isEmpty(flash());\n });\n });\n});\n"]}
1
+ {"version":3,"file":"index.test.js","sourceRoot":"","sources":["../src/index.test.ts"],"names":[],"mappings":"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","sourcesContent":["import { assert } from 'chai';\n\nimport { html } from '@prairielearn/html';\n\nimport { flashMiddleware, flash } from './index.js';\n\ndescribe('flash', () => {\n it('throws an error if no session present', () => {\n assert.throw(() => {\n flashMiddleware()({} as any, {} as any, () => {});\n });\n });\n\n it('throws an error when middleware is not used', () => {\n assert.throw(() => {\n flash('notice', 'Hello world');\n });\n assert.throw(() => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n });\n });\n\n it('adds a flash using string message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: 'hello world' }]);\n });\n });\n\n it('adds a flash and escapes unsafe HTML', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', '<b>hello world</b>');\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: '&lt;b&gt;hello world&lt;/b&gt;' },\n ]);\n });\n });\n\n it('adds a flash with HTML-safe message', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [{ type: 'notice', message: '<p>hello &amp; world</p>' }]);\n });\n });\n\n it('stores multiples flashes with the same type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('notice', '<< goodbye world');\n flash('notice', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'notice', message: '&lt;&lt; goodbye world' },\n { type: 'notice', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns flash message for a given type', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('returns all flashes', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash(), [\n { type: 'notice', message: 'hello world' },\n { type: 'error', message: '&lt;&lt; goodbye world' },\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n });\n });\n\n it('clears flash after it has been read', () => {\n const req = {\n session: {},\n } as any;\n const res = {} as any;\n\n flashMiddleware()(req, res, () => {\n flash('notice', 'hello world');\n flash('error', '<< goodbye world');\n flash('success', html`<p>hello ${'&'} world</p>`);\n\n assert.sameDeepMembers(flash('notice'), [{ type: 'notice', message: 'hello world' }]);\n assert.sameDeepMembers(flash('error'), [\n { type: 'error', message: '&lt;&lt; goodbye world' },\n ]);\n assert.sameDeepMembers(flash('success'), [\n { type: 'success', message: '<p>hello &amp; world</p>' },\n ]);\n\n assert.deepEqual(flash('notice'), []);\n assert.deepEqual(flash('error'), []);\n assert.deepEqual(flash('success'), []);\n assert.isEmpty(flash());\n });\n });\n});\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@prairielearn/flash",
3
- "version": "2.0.0",
3
+ "version": "2.0.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "repository": {
@@ -11,19 +11,31 @@
11
11
  "scripts": {
12
12
  "build": "tsc",
13
13
  "dev": "tsc --watch --preserveWatchOutput",
14
- "test": "mocha src/**/*.test.ts"
14
+ "test": "c8 mocha src/**/*.test.ts"
15
15
  },
16
16
  "dependencies": {
17
- "@prairielearn/html": "^4.0.0",
17
+ "@prairielearn/html": "^4.0.2",
18
18
  "@types/express": "^4.17.21"
19
19
  },
20
20
  "devDependencies": {
21
21
  "@prairielearn/tsconfig": "^0.0.0",
22
22
  "@types/express-session": "^1.18.0",
23
- "@types/node": "^20.12.2",
24
- "chai": "^4.4.1",
23
+ "@types/node": "^20.13.0",
24
+ "c8": "^9.1.0",
25
+ "chai": "^5.1.1",
25
26
  "mocha": "^10.4.0",
26
- "tsx": "^4.9.3",
27
- "typescript": "^5.4.3"
27
+ "tsx": "^4.11.0",
28
+ "typescript": "^5.4.5"
29
+ },
30
+ "c8": {
31
+ "reporter": [
32
+ "html",
33
+ "text-summary",
34
+ "cobertura"
35
+ ],
36
+ "all": true,
37
+ "include": [
38
+ "src/**"
39
+ ]
28
40
  }
29
41
  }
package/src/index.test.ts CHANGED
@@ -1,8 +1,9 @@
1
1
  import { assert } from 'chai';
2
2
 
3
- import { flashMiddleware, flash } from './index.js';
4
3
  import { html } from '@prairielearn/html';
5
4
 
5
+ import { flashMiddleware, flash } from './index.js';
6
+
6
7
  describe('flash', () => {
7
8
  it('throws an error if no session present', () => {
8
9
  assert.throw(() => {
package/src/index.ts CHANGED
@@ -1,5 +1,7 @@
1
1
  import { AsyncLocalStorage } from 'node:async_hooks';
2
+
2
3
  import type { Request, Response, NextFunction } from 'express';
4
+
3
5
  import { HtmlSafeString, html } from '@prairielearn/html';
4
6
 
5
7
  const als = new AsyncLocalStorage<FlashStorage>();