@shakerquiz/guards 0.0.0-stage → 0.0.0
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/.github/workflows/publish.yml +32 -0
- package/bun.lock +31 -0
- package/dprint.json +27 -0
- package/jsconfig.json +14 -0
- package/package.json +21 -4
- package/source/guards/QUERY/channels.js +18 -0
- package/source/guards/QUERY/participants.js +31 -0
- package/source/guards/channel-mailing-message.js +17 -0
- package/source/guards/channel.js +11 -0
- package/source/guards/mailing.js +14 -0
- package/source/guards/message.js +16 -0
- package/source/guards/participant.js +20 -0
- package/source/index.js +7 -0
- package/source/utilities/guard-body.js +29 -0
- package/README.md +0 -3
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
name: Publish @shakerquiz/guards
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
pull_request:
|
|
5
|
+
types: [closed]
|
|
6
|
+
branches:
|
|
7
|
+
- main
|
|
8
|
+
workflow_dispatch:
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
contents: write
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
publish:
|
|
16
|
+
runs-on: ubuntu-latest
|
|
17
|
+
if: github.event.pull_request.merged == true || github.event_name == 'workflow_dispatch'
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v7
|
|
20
|
+
with:
|
|
21
|
+
fetch-depth: 0
|
|
22
|
+
ref: main
|
|
23
|
+
- uses: actions/setup-node@v7
|
|
24
|
+
with:
|
|
25
|
+
node-version: lts/*
|
|
26
|
+
- uses: oven-sh/setup-bun@v2
|
|
27
|
+
- run: git config user.name "github-actions[bot]"
|
|
28
|
+
- run: git config user.email "github-actions[bot]@users.noreply.github.com"
|
|
29
|
+
- run: npm version patch
|
|
30
|
+
- run: bun install --frozen-lockfile
|
|
31
|
+
- run: npm publish --tag latest --access public
|
|
32
|
+
- run: git push --follow-tags
|
package/bun.lock
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 2,
|
|
3
|
+
"configVersion": 1,
|
|
4
|
+
"workspaces": {
|
|
5
|
+
"": {
|
|
6
|
+
"name": "guards",
|
|
7
|
+
"dependencies": {
|
|
8
|
+
"@shakerquiz/utilities": "4.0.122",
|
|
9
|
+
"@yurkimus/string-tag": "0.0.4",
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "1.4.4",
|
|
13
|
+
},
|
|
14
|
+
},
|
|
15
|
+
},
|
|
16
|
+
"packages": {
|
|
17
|
+
"@shakerquiz/utilities": ["@shakerquiz/utilities@4.0.122", "", {}, "sha512-dNaiDNRQE7uq4YMqkiTBCK524tF8gGnZVq09aV2Nac1HVMMQ3iAQO9Ew3A2L/LxLFdjic8kY8009puCEwI825g=="],
|
|
18
|
+
|
|
19
|
+
"@types/bun": ["@types/bun@1.4.4", "", { "dependencies": { "bun-types": "1.4.4" } }, "sha512-9wkRDXUXK124pEnAutE6n/l18j+i9GC5IAHK8TmX/2nlySETmky0OU6yL5U8pW1AoKh++AN9auDCr647BfurjQ=="],
|
|
20
|
+
|
|
21
|
+
"@types/node": ["@types/node@26.6.6", "", { "dependencies": { "undici-types": "~8.9.0" } }, "sha512-MzQj+8Kx3XI40OmUAE6Qoh58ZMNstNNMwn2nfd3qWsfe7nDNSqJNzes4njhz6AHwrSHOffSLwXU6yb474Q4aog=="],
|
|
22
|
+
|
|
23
|
+
"@yurkimus/string-tag": ["@yurkimus/string-tag@0.0.4", "", {}, "sha512-X+FD6rYT3qNKcFTke3HNeQgFVEe/VhmwzBK3vGn9Z6Auxs1857eNplO1AYsfxpTE7Ql2nM2GyG54d5nx8t2guQ=="],
|
|
24
|
+
|
|
25
|
+
"bun-types": ["bun-types@1.4.4", "", { "dependencies": { "@types/node": "*", "undici-types": "*" } }, "sha512-yjrJh5oGX7mMPKGd19YXyIjmEuj4G4zNC30bs7PM9Az7NQ3ndF3j6hbeRdvJTRQEhJ/FYQwW0a2moweEi2hMxQ=="],
|
|
26
|
+
|
|
27
|
+
"undici-types": ["undici-types@8.12.3", "", {}, "sha512-EXE4ydL3S76dFb27UFjNVZHTyv/TLdATj/KUe29ptdcg6qXL48J2tZ3126E5kICj5jn781OR+XlVH1N6Mz8jbA=="],
|
|
28
|
+
|
|
29
|
+
"@types/node/undici-types": ["undici-types@8.9.0", "", {}, "sha512-KTDyRTYX8sWmKXAikPHHSyc63CRPETMctyjKFupcC6OBLXT3xsN0e9aF7m+mIXutFWpUXuedtowG7iLOzp0kQg=="],
|
|
30
|
+
}
|
|
31
|
+
}
|
package/dprint.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"typescript": {
|
|
3
|
+
"quoteStyle": "preferSingle",
|
|
4
|
+
"semiColons": "asi",
|
|
5
|
+
"indentWidth": 2,
|
|
6
|
+
"useTabs": false,
|
|
7
|
+
"useBraces": "maintain",
|
|
8
|
+
"arrowFunction.useParentheses": "preferNone",
|
|
9
|
+
"importDeclaration.forceSingleLine": true,
|
|
10
|
+
"lineWidth": 120
|
|
11
|
+
},
|
|
12
|
+
"json": {},
|
|
13
|
+
"markdown": {},
|
|
14
|
+
"dockerfile": {},
|
|
15
|
+
"yaml": {},
|
|
16
|
+
"excludes": [
|
|
17
|
+
"**/node_modules",
|
|
18
|
+
"**/*-lock.json"
|
|
19
|
+
],
|
|
20
|
+
"plugins": [
|
|
21
|
+
"https://plugins.dprint.dev/typescript-0.93.0.wasm",
|
|
22
|
+
"https://plugins.dprint.dev/json-0.19.3.wasm",
|
|
23
|
+
"https://plugins.dprint.dev/markdown-0.17.8.wasm",
|
|
24
|
+
"https://plugins.dprint.dev/dockerfile-0.3.2.wasm",
|
|
25
|
+
"https://plugins.dprint.dev/g-plane/pretty_yaml-v0.5.0.wasm"
|
|
26
|
+
]
|
|
27
|
+
}
|
package/jsconfig.json
ADDED
package/package.json
CHANGED
|
@@ -1,6 +1,23 @@
|
|
|
1
1
|
{
|
|
2
|
+
"type": "module",
|
|
2
3
|
"name": "@shakerquiz/guards",
|
|
3
|
-
"version": "0.0.0
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
|
|
4
|
+
"version": "0.0.0",
|
|
5
|
+
"author": "yurkimus <yurkimus@gmail.com>",
|
|
6
|
+
"license": "ISC",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "https://github.com/shaker-quiz/guards.git"
|
|
10
|
+
},
|
|
11
|
+
"exports": {
|
|
12
|
+
".": {
|
|
13
|
+
"default": "./source/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"dependencies": {
|
|
17
|
+
"@shakerquiz/utilities": "4.0.122",
|
|
18
|
+
"@yurkimus/string-tag": "0.0.4"
|
|
19
|
+
},
|
|
20
|
+
"devDependencies": {
|
|
21
|
+
"@types/bun": "1.4.4"
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import { Channel, Channels } from '@shakerquiz/utilities'
|
|
2
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
3
|
+
|
|
4
|
+
export const ChannelsQueryPropValidation = Object.freeze({
|
|
5
|
+
channel: value => Object.hasOwn(Channel, value),
|
|
6
|
+
|
|
7
|
+
id: value =>
|
|
8
|
+
Array.isArray(value)
|
|
9
|
+
? value.every(item => typeof item === 'string')
|
|
10
|
+
: value === 'Any',
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const ChannelsQueryPropException = Object.freeze({
|
|
14
|
+
channel: `Body.channel must be one of ${Channels.join(', ')}.`,
|
|
15
|
+
id: `Body.id must be 'Any' or an array of strings.`,
|
|
16
|
+
})
|
|
17
|
+
|
|
18
|
+
export const guardChannelsQueryBody = guardBody('Object', ChannelsQueryPropValidation, ChannelsQueryPropException)
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
import { Pattern } from '@shakerquiz/utilities'
|
|
2
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
3
|
+
|
|
4
|
+
export const ParticipantsQueryPropValidation = Object.freeze({
|
|
5
|
+
id: value =>
|
|
6
|
+
Array.isArray(value)
|
|
7
|
+
? value.every(item => typeof item === 'string' && Pattern.UUID.test(item))
|
|
8
|
+
: value === 'Any',
|
|
9
|
+
|
|
10
|
+
mongo_id: value =>
|
|
11
|
+
Array.isArray(value)
|
|
12
|
+
? value.every(item => typeof item === 'string')
|
|
13
|
+
: value === 'Any',
|
|
14
|
+
|
|
15
|
+
username: value =>
|
|
16
|
+
Array.isArray(value)
|
|
17
|
+
? value.every(item => typeof item === 'string')
|
|
18
|
+
: value === 'Any',
|
|
19
|
+
})
|
|
20
|
+
|
|
21
|
+
export const ParticipantsQueryPropException = Object.freeze({
|
|
22
|
+
id: `Body.id must be 'Any' or an array of uuid.`,
|
|
23
|
+
mongo_id: `Body.mongo_id must be 'Any' or an array of strings.`,
|
|
24
|
+
username: `Body.username must be 'Any' or an array of strings.`,
|
|
25
|
+
})
|
|
26
|
+
|
|
27
|
+
export const guardParticipantsQueryBody = guardBody(
|
|
28
|
+
'Object',
|
|
29
|
+
ParticipantsQueryPropValidation,
|
|
30
|
+
ParticipantsQueryPropException,
|
|
31
|
+
)
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
2
|
+
|
|
3
|
+
export const ChannelMailingMessagePropValidation = Object.freeze({
|
|
4
|
+
sender: value => typeof value === 'string' && value !== '',
|
|
5
|
+
recipient: value => typeof value === 'string' && value !== '',
|
|
6
|
+
})
|
|
7
|
+
|
|
8
|
+
export const ChannelMailingMessagePropException = Object.freeze({
|
|
9
|
+
sender: `Body.sender must be a non-empty string.`,
|
|
10
|
+
recipient: `Body.recipient must be a non-empty string.`,
|
|
11
|
+
})
|
|
12
|
+
|
|
13
|
+
export const guardChannelMailingMessageBody = guardBody(
|
|
14
|
+
'Object',
|
|
15
|
+
ChannelMailingMessagePropValidation,
|
|
16
|
+
ChannelMailingMessagePropException,
|
|
17
|
+
)
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
2
|
+
|
|
3
|
+
export const ChannelPropValidation = Object.freeze({
|
|
4
|
+
id: value => typeof value === 'string' && value !== '',
|
|
5
|
+
})
|
|
6
|
+
|
|
7
|
+
export const ChannelPropException = Object.freeze({
|
|
8
|
+
id: `Body.id must be a non-empty string.`,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const guardChannelBody = guardBody('Object', ChannelPropValidation, ChannelPropException)
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { Phase } from '@shakerquiz/utilities'
|
|
2
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
3
|
+
|
|
4
|
+
export const MailingPropValidation = Object.freeze({
|
|
5
|
+
phase: value => Object.hasOwn(Phase, value),
|
|
6
|
+
reason: value => typeof value === 'string',
|
|
7
|
+
})
|
|
8
|
+
|
|
9
|
+
export const MailingPropException = Object.freeze({
|
|
10
|
+
phase: `Body.phase must be a Phase.`,
|
|
11
|
+
reason: `Body.reason must be a string.`,
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
export const guardMailingBody = guardBody('Object', MailingPropValidation, MailingPropException)
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { MessageKind } from '@shakerquiz/utilities'
|
|
2
|
+
import { isStringTag } from '@yurkimus/string-tag'
|
|
3
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
4
|
+
|
|
5
|
+
export const MessagePropValidation = Object.freeze({
|
|
6
|
+
// Template is not implemented yet, so we handle only Inline messages.
|
|
7
|
+
kind: value => value === MessageKind.Inline,
|
|
8
|
+
content: value => (isStringTag('Object', value) || isStringTag('Array', value)) && Object.keys(value).length > 0,
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
export const MessagePropException = Object.freeze({
|
|
12
|
+
kind: `Body.kind must be '${MessageKind.Inline}'.`,
|
|
13
|
+
content: `Body.content must be a non-empty object or array.`,
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
export const guardMessageBody = guardBody('Object', MessagePropValidation, MessagePropException)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { ParticipantChoice } from '@shakerquiz/utilities'
|
|
2
|
+
import { guardBody } from 'source/utilities/guard-body.js'
|
|
3
|
+
|
|
4
|
+
export const ParticipantPropValidation = Object.freeze({
|
|
5
|
+
mongo_id: value => typeof value === 'string' && value !== '',
|
|
6
|
+
first_name: value => typeof value === 'string',
|
|
7
|
+
last_name: value => typeof value === 'string',
|
|
8
|
+
username: value => typeof value === 'string',
|
|
9
|
+
choice: value => Object.hasOwn(ParticipantChoice, value),
|
|
10
|
+
})
|
|
11
|
+
|
|
12
|
+
export const ParticipantPropException = Object.freeze({
|
|
13
|
+
mongo_id: `Body.mongo_id must be a non-empty string.`,
|
|
14
|
+
first_name: `Body.first_name must be a string.`,
|
|
15
|
+
last_name: `Body.last_name must be a string.`,
|
|
16
|
+
username: `Body.username must be a string.`,
|
|
17
|
+
choice: `Body.choice must be a ParticipantChoice.`,
|
|
18
|
+
})
|
|
19
|
+
|
|
20
|
+
export const guardParticipantBody = guardBody('Object', ParticipantPropValidation, ParticipantPropException)
|
package/source/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export * from 'source/guards/channel-mailing-message.js'
|
|
2
|
+
export * from 'source/guards/channel.js'
|
|
3
|
+
export * from 'source/guards/mailing.js'
|
|
4
|
+
export * from 'source/guards/message.js'
|
|
5
|
+
export * from 'source/guards/participant.js'
|
|
6
|
+
export * from 'source/guards/QUERY/channels.js'
|
|
7
|
+
export * from 'source/guards/QUERY/participants.js'
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { isStringTag } from '@yurkimus/string-tag'
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* @param {'Object'} stringTag
|
|
5
|
+
* @param {Record<string, (value: *) => boolean>} validation
|
|
6
|
+
* @param {Record<string, string>} exception
|
|
7
|
+
*
|
|
8
|
+
* @returns {(body: Record<string, *>, props: string[]) => string} The guard, which takes the body and the props of it to validate (each of them must be present and valid), and returns the message of the first violation, or '' (the neutral element) when there is none.
|
|
9
|
+
*/
|
|
10
|
+
export const guardBody = (stringTag, validation, exception) => (body, props) => {
|
|
11
|
+
if (body === undefined)
|
|
12
|
+
return 'Body must be valid JSON.'
|
|
13
|
+
|
|
14
|
+
if (!isStringTag(stringTag, body))
|
|
15
|
+
return `Body must be ${stringTag}.`
|
|
16
|
+
|
|
17
|
+
if (Object.keys(body).length === 0)
|
|
18
|
+
return 'Body must not be empty.'
|
|
19
|
+
|
|
20
|
+
for (let prop of Object.keys(body))
|
|
21
|
+
if (!Object.hasOwn(validation, prop))
|
|
22
|
+
return 'Body must not have additional props.'
|
|
23
|
+
|
|
24
|
+
for (let prop of props)
|
|
25
|
+
if (!validation[prop](body[prop]))
|
|
26
|
+
return exception[prop]
|
|
27
|
+
|
|
28
|
+
return ''
|
|
29
|
+
}
|
package/README.md
DELETED