skuba 11.1.0-jest30-20250620003740 → 11.2.0-lfs-20250711064710
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/jest/moduleNameMapper.js +4 -1
- package/lib/api/git/getChangedFiles.js +27 -2
- package/lib/api/git/getChangedFiles.js.map +3 -3
- package/lib/cli/configure/ensureTemplateCompletion.js +4 -2
- package/lib/cli/configure/ensureTemplateCompletion.js.map +2 -2
- package/lib/cli/init/getConfig.d.ts +6 -7
- package/lib/cli/init/types.d.ts +13 -109
- package/lib/cli/init/types.js +30 -20
- package/lib/cli/init/types.js.map +2 -2
- package/lib/cli/lint/internalLints/patchRenovateConfig.js +3 -3
- package/lib/cli/lint/internalLints/patchRenovateConfig.js.map +2 -2
- package/lib/cli/migrate/nodeVersion/checks.d.ts +3 -3
- package/lib/cli/migrate/nodeVersion/checks.js +11 -11
- package/lib/cli/migrate/nodeVersion/checks.js.map +2 -2
- package/lib/cli/migrate/nodeVersion/index.js +18 -18
- package/lib/cli/migrate/nodeVersion/index.js.map +2 -2
- package/lib/utils/error.d.ts +3 -23
- package/lib/utils/error.js +18 -8
- package/lib/utils/error.js.map +2 -2
- package/lib/utils/manifest.d.ts +2 -2
- package/lib/utils/manifest.js +4 -4
- package/lib/utils/manifest.js.map +2 -2
- package/lib/utils/packageManager.d.ts +5 -2
- package/lib/utils/packageManager.js +2 -2
- package/lib/utils/packageManager.js.map +2 -2
- package/lib/utils/template.d.ts +9 -38
- package/lib/utils/template.js +22 -10
- package/lib/utils/template.js.map +2 -2
- package/lib/utils/version.d.ts +2 -10
- package/lib/utils/version.js +9 -9
- package/lib/utils/version.js.map +2 -2
- package/package.json +15 -15
- package/template/express-rest-api/.buildkite/pipeline.yml +1 -1
- package/template/express-rest-api/Dockerfile.dev-deps +1 -1
- package/template/express-rest-api/package.json +1 -1
- package/template/greeter/.buildkite/pipeline.yml +1 -1
- package/template/greeter/Dockerfile +1 -1
- package/template/greeter/package.json +2 -2
- package/template/koa-rest-api/.buildkite/pipeline.yml +1 -1
- package/template/koa-rest-api/Dockerfile.dev-deps +1 -1
- package/template/koa-rest-api/package.json +7 -7
- package/template/koa-rest-api/src/api/jobs/postJob.test.ts +1 -1
- package/template/koa-rest-api/src/config.ts +1 -1
- package/template/koa-rest-api/src/framework/logging.ts +21 -12
- package/template/koa-rest-api/src/framework/server.test.ts +91 -60
- package/template/koa-rest-api/src/framework/validation.test.ts +31 -31
- package/template/koa-rest-api/src/framework/validation.ts +17 -23
- package/template/koa-rest-api/src/testing/types.ts +1 -1
- package/template/koa-rest-api/src/types/jobs.ts +1 -1
- package/template/lambda-sqs-worker-cdk/.buildkite/pipeline.yml +2 -2
- package/template/lambda-sqs-worker-cdk/Dockerfile +1 -1
- package/template/lambda-sqs-worker-cdk/infra/__snapshots__/appStack.test.ts.snap +6 -0
- package/template/lambda-sqs-worker-cdk/package.json +5 -5
- package/template/lambda-sqs-worker-cdk/src/app.test.ts +76 -17
- package/template/lambda-sqs-worker-cdk/src/config.ts +1 -1
- package/template/lambda-sqs-worker-cdk/src/framework/handler.test.ts +35 -15
- package/template/lambda-sqs-worker-cdk/src/framework/logging.ts +22 -11
- package/template/lambda-sqs-worker-cdk/src/framework/validation.test.ts +6 -9
- package/template/lambda-sqs-worker-cdk/src/framework/validation.ts +4 -8
- package/template/lambda-sqs-worker-cdk/src/testing/types.ts +1 -1
- package/template/lambda-sqs-worker-cdk/src/types/jobScorer.ts +1 -1
- package/template/lambda-sqs-worker-cdk/src/types/pipelineEvents.ts +1 -1
- package/template/koa-rest-api/src/testing/logging.ts +0 -16
- package/template/lambda-sqs-worker-cdk/src/testing/logging.ts +0 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "skuba",
|
|
3
|
-
"version": "11.
|
|
3
|
+
"version": "11.2.0-lfs-20250711064710",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "SEEK development toolkit for backend applications and packages",
|
|
6
6
|
"homepage": "https://github.com/seek-oss/skuba#readme",
|
|
@@ -55,7 +55,7 @@
|
|
|
55
55
|
"@jest/types": "^30.0.0",
|
|
56
56
|
"@octokit/graphql": "^9.0.0",
|
|
57
57
|
"@octokit/graphql-schema": "^15.3.0",
|
|
58
|
-
"@octokit/rest": "^
|
|
58
|
+
"@octokit/rest": "^22.0.0",
|
|
59
59
|
"@octokit/types": "^14.0.0",
|
|
60
60
|
"@types/jest": "^30.0.0",
|
|
61
61
|
"@types/node": "^22.0.0",
|
|
@@ -85,7 +85,7 @@
|
|
|
85
85
|
"npm-run-path": "^4.0.1",
|
|
86
86
|
"npm-which": "^3.0.1",
|
|
87
87
|
"picomatch": "^4.0.0",
|
|
88
|
-
"prettier": "~3.
|
|
88
|
+
"prettier": "~3.6.0",
|
|
89
89
|
"prettier-plugin-packagejson": "^2.4.10",
|
|
90
90
|
"read-pkg-up": "^7.0.1",
|
|
91
91
|
"semantic-release": "^24.2.3",
|
|
@@ -97,37 +97,37 @@
|
|
|
97
97
|
"tsconfig-seek": "2.0.0",
|
|
98
98
|
"tsx": "^4.16.2",
|
|
99
99
|
"typescript": "~5.8.0",
|
|
100
|
-
"zod": "^3.
|
|
101
|
-
"eslint-config-skuba": "6.1.
|
|
100
|
+
"zod": "^3.25.67",
|
|
101
|
+
"eslint-config-skuba": "6.1.1"
|
|
102
102
|
},
|
|
103
103
|
"devDependencies": {
|
|
104
|
-
"@changesets/cli": "2.29.
|
|
104
|
+
"@changesets/cli": "2.29.5",
|
|
105
105
|
"@changesets/get-github-info": "0.6.0",
|
|
106
106
|
"@jest/reporters": "30.0.2",
|
|
107
107
|
"@jest/test-result": "30.0.2",
|
|
108
108
|
"@types/ejs": "3.1.5",
|
|
109
|
-
"@types/express": "5.0.
|
|
109
|
+
"@types/express": "5.0.3",
|
|
110
110
|
"@types/fs-extra": "11.0.4",
|
|
111
111
|
"@types/koa": "2.15.0",
|
|
112
112
|
"@types/lodash.mergewith": "4.6.9",
|
|
113
113
|
"@types/minimist": "1.2.5",
|
|
114
114
|
"@types/module-alias": "2.0.4",
|
|
115
|
-
"@types/npm-registry-fetch": "8.0.
|
|
116
|
-
"@types/npm-which": "3.0.
|
|
115
|
+
"@types/npm-registry-fetch": "8.0.8",
|
|
116
|
+
"@types/npm-which": "3.0.4",
|
|
117
117
|
"@types/picomatch": "4.0.0",
|
|
118
118
|
"@types/semver": "7.7.0",
|
|
119
119
|
"@types/supertest": "6.0.3",
|
|
120
|
-
"enhanced-resolve": "5.18.
|
|
120
|
+
"enhanced-resolve": "5.18.2",
|
|
121
121
|
"express": "5.1.0",
|
|
122
|
-
"fastify": "5.
|
|
123
|
-
"jest-diff": "30.0.
|
|
122
|
+
"fastify": "5.4.0",
|
|
123
|
+
"jest-diff": "30.0.3",
|
|
124
124
|
"jsonfile": "6.1.0",
|
|
125
125
|
"koa": "3.0.0",
|
|
126
|
-
"memfs": "4.17.
|
|
126
|
+
"memfs": "4.17.2",
|
|
127
127
|
"remark-cli": "12.0.1",
|
|
128
128
|
"remark-preset-lint-recommended": "7.0.1",
|
|
129
|
-
"semver": "7.7.
|
|
130
|
-
"supertest": "7.1.
|
|
129
|
+
"semver": "7.7.2",
|
|
130
|
+
"supertest": "7.1.1",
|
|
131
131
|
"type-fest": "2.19.0"
|
|
132
132
|
},
|
|
133
133
|
"peerDependencies": {
|
|
@@ -17,9 +17,9 @@
|
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
19
|
"@types/node": "^22.13.10",
|
|
20
|
-
"skuba": "11.
|
|
20
|
+
"skuba": "11.2.0-lfs-20250711064710"
|
|
21
21
|
},
|
|
22
|
-
"packageManager": "pnpm@10.12.
|
|
22
|
+
"packageManager": "pnpm@10.12.4",
|
|
23
23
|
"engines": {
|
|
24
24
|
"node": ">=22"
|
|
25
25
|
}
|
|
@@ -13,15 +13,15 @@
|
|
|
13
13
|
"test:watch": "skuba test --watch"
|
|
14
14
|
},
|
|
15
15
|
"dependencies": {
|
|
16
|
-
"@koa/bodyparser": "^
|
|
16
|
+
"@koa/bodyparser": "^6.0.0",
|
|
17
17
|
"@koa/router": "^13.0.0",
|
|
18
18
|
"@opentelemetry/api": "^1.9.0",
|
|
19
19
|
"@opentelemetry/core": "^2.0.0",
|
|
20
|
-
"@opentelemetry/exporter-trace-otlp-grpc": "^0.
|
|
21
|
-
"@opentelemetry/instrumentation-aws-sdk": "^0.
|
|
22
|
-
"@opentelemetry/instrumentation-http": "^0.
|
|
20
|
+
"@opentelemetry/exporter-trace-otlp-grpc": "^0.202.0",
|
|
21
|
+
"@opentelemetry/instrumentation-aws-sdk": "^0.54.0",
|
|
22
|
+
"@opentelemetry/instrumentation-http": "^0.202.0",
|
|
23
23
|
"@opentelemetry/propagator-b3": "^2.0.0",
|
|
24
|
-
"@opentelemetry/sdk-node": "^0.
|
|
24
|
+
"@opentelemetry/sdk-node": "^0.202.0",
|
|
25
25
|
"@seek/logger": "^10.0.0",
|
|
26
26
|
"hot-shots": "^10.0.0",
|
|
27
27
|
"koa": "^2.16.1",
|
|
@@ -29,7 +29,7 @@
|
|
|
29
29
|
"seek-datadog-custom-metrics": "^4.6.3",
|
|
30
30
|
"seek-koala": "^7.0.0",
|
|
31
31
|
"skuba-dive": "^2.0.0",
|
|
32
|
-
"zod": "^3.
|
|
32
|
+
"zod": "^3.25.67"
|
|
33
33
|
},
|
|
34
34
|
"devDependencies": {
|
|
35
35
|
"@types/chance": "^1.1.3",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"skuba": "*",
|
|
45
45
|
"supertest": "^7.0.0"
|
|
46
46
|
},
|
|
47
|
-
"packageManager": "pnpm@10.12.
|
|
47
|
+
"packageManager": "pnpm@10.12.4",
|
|
48
48
|
"engines": {
|
|
49
49
|
"node": ">=22"
|
|
50
50
|
}
|
|
@@ -27,7 +27,7 @@ describe('postJobHandler', () => {
|
|
|
27
27
|
.expect(422)
|
|
28
28
|
.expect(({ text }) =>
|
|
29
29
|
expect(text).toMatchInlineSnapshot(
|
|
30
|
-
`"{"message":"Input validation failed","invalidFields":{"/hirer":"
|
|
30
|
+
`"{"message":"Input validation failed","invalidFields":{"/hirer":"Invalid input: expected object, received undefined"}}"`,
|
|
31
31
|
),
|
|
32
32
|
);
|
|
33
33
|
});
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import createLogger from '@seek/logger';
|
|
1
|
+
import createLogger, { createDestination } from '@seek/logger';
|
|
2
2
|
import { RequestLogging } from 'seek-koala';
|
|
3
3
|
|
|
4
4
|
import { config } from 'src/config';
|
|
@@ -8,18 +8,27 @@ const { createContextMiddleware, mixin } =
|
|
|
8
8
|
|
|
9
9
|
export const contextMiddleware = createContextMiddleware();
|
|
10
10
|
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
version: config.version,
|
|
15
|
-
},
|
|
11
|
+
const { destination, stdoutMock } = createDestination({
|
|
12
|
+
mock: config.environment === 'test',
|
|
13
|
+
});
|
|
16
14
|
|
|
17
|
-
|
|
15
|
+
export { stdoutMock };
|
|
18
16
|
|
|
19
|
-
|
|
17
|
+
export const logger = createLogger(
|
|
18
|
+
{
|
|
19
|
+
base: {
|
|
20
|
+
environment: config.environment,
|
|
21
|
+
version: config.version,
|
|
22
|
+
},
|
|
20
23
|
|
|
21
|
-
|
|
24
|
+
mixin,
|
|
22
25
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
+
level: config.logLevel,
|
|
27
|
+
|
|
28
|
+
name: config.name,
|
|
29
|
+
|
|
30
|
+
transport:
|
|
31
|
+
config.environment === 'local' ? { target: 'pino-pretty' } : undefined,
|
|
32
|
+
},
|
|
33
|
+
destination,
|
|
34
|
+
);
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import Router from '@koa/router';
|
|
2
2
|
|
|
3
|
-
import { logger } from 'src/testing/logging';
|
|
4
3
|
import { metricsClient } from 'src/testing/metrics';
|
|
5
4
|
import { agentFromRouter } from 'src/testing/server';
|
|
6
5
|
import { chance } from 'src/testing/types';
|
|
7
6
|
import type { Middleware } from 'src/types/koa';
|
|
8
7
|
|
|
8
|
+
import { stdoutMock } from './logging';
|
|
9
|
+
|
|
9
10
|
const middleware = jest.fn<void, Parameters<Middleware>>();
|
|
10
11
|
|
|
11
12
|
const router = new Router()
|
|
@@ -15,10 +16,10 @@ const router = new Router()
|
|
|
15
16
|
const agent = agentFromRouter(router);
|
|
16
17
|
|
|
17
18
|
describe('createApp', () => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
afterEach(() => {
|
|
20
|
+
metricsClient.clear();
|
|
21
|
+
stdoutMock.clear();
|
|
22
|
+
});
|
|
22
23
|
|
|
23
24
|
it('handles root route', async () => {
|
|
24
25
|
middleware.mockImplementation((ctx) => (ctx.body = ''));
|
|
@@ -29,9 +30,7 @@ describe('createApp', () => {
|
|
|
29
30
|
.expect('server', /.+/)
|
|
30
31
|
.expect('x-api-version', /.+/);
|
|
31
32
|
|
|
32
|
-
expect(
|
|
33
|
-
|
|
34
|
-
expect(logger.info).not.toHaveBeenCalled();
|
|
33
|
+
expect(stdoutMock.calls).toHaveLength(0);
|
|
35
34
|
|
|
36
35
|
metricsClient.expectTagSubset(['env:test', 'version:test']);
|
|
37
36
|
metricsClient.expectTagSubset([
|
|
@@ -51,9 +50,7 @@ describe('createApp', () => {
|
|
|
51
50
|
.expect('server', /.+/)
|
|
52
51
|
.expect('x-api-version', /.+/);
|
|
53
52
|
|
|
54
|
-
expect(
|
|
55
|
-
|
|
56
|
-
expect(logger.info).not.toHaveBeenCalled();
|
|
53
|
+
expect(stdoutMock.calls).toHaveLength(0);
|
|
57
54
|
|
|
58
55
|
metricsClient.expectTagSubset([
|
|
59
56
|
'http_method:put',
|
|
@@ -72,13 +69,15 @@ describe('createApp', () => {
|
|
|
72
69
|
.expect('server', /.+/)
|
|
73
70
|
.expect('x-api-version', /.+/);
|
|
74
71
|
|
|
75
|
-
expect(
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
72
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
73
|
+
{
|
|
74
|
+
level: 30,
|
|
75
|
+
method: 'GET',
|
|
76
|
+
msg: 'Client error',
|
|
77
|
+
status: 404,
|
|
78
|
+
url: '/unknown',
|
|
79
|
+
},
|
|
80
|
+
]);
|
|
82
81
|
|
|
83
82
|
metricsClient.expectTagSubset([
|
|
84
83
|
'http_method:get',
|
|
@@ -102,13 +101,16 @@ describe('createApp', () => {
|
|
|
102
101
|
.expect('server', /.+/)
|
|
103
102
|
.expect('x-api-version', /.+/);
|
|
104
103
|
|
|
105
|
-
expect(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
104
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
105
|
+
{
|
|
106
|
+
level: 30,
|
|
107
|
+
method: 'GET',
|
|
108
|
+
msg: 'Client error',
|
|
109
|
+
route: '/',
|
|
110
|
+
status: 400,
|
|
111
|
+
url: '/',
|
|
112
|
+
},
|
|
113
|
+
]);
|
|
112
114
|
|
|
113
115
|
metricsClient.expectTagSubset([
|
|
114
116
|
'http_method:get',
|
|
@@ -129,13 +131,20 @@ describe('createApp', () => {
|
|
|
129
131
|
.expect('server', /.+/)
|
|
130
132
|
.expect('x-api-version', /.+/);
|
|
131
133
|
|
|
132
|
-
expect(
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
134
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
135
|
+
{
|
|
136
|
+
err: {
|
|
137
|
+
statusCode: 400,
|
|
138
|
+
type: 'BadRequestError',
|
|
139
|
+
},
|
|
140
|
+
level: 30,
|
|
141
|
+
method: 'GET',
|
|
142
|
+
msg: 'Client error',
|
|
143
|
+
route: '/',
|
|
144
|
+
status: 400,
|
|
145
|
+
url: '/',
|
|
146
|
+
},
|
|
147
|
+
]);
|
|
139
148
|
|
|
140
149
|
metricsClient.expectTagSubset([
|
|
141
150
|
'http_method:get',
|
|
@@ -156,13 +165,20 @@ describe('createApp', () => {
|
|
|
156
165
|
.expect('server', /.+/)
|
|
157
166
|
.expect('x-api-version', /.+/);
|
|
158
167
|
|
|
159
|
-
expect(
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
168
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
169
|
+
{
|
|
170
|
+
err: {
|
|
171
|
+
statusCode: 500,
|
|
172
|
+
type: 'InternalServerError',
|
|
173
|
+
},
|
|
174
|
+
level: 50,
|
|
175
|
+
method: 'GET',
|
|
176
|
+
msg: 'Server error',
|
|
177
|
+
route: '/',
|
|
178
|
+
status: 500,
|
|
179
|
+
url: '/',
|
|
180
|
+
},
|
|
181
|
+
]);
|
|
166
182
|
|
|
167
183
|
metricsClient.expectTagSubset([
|
|
168
184
|
'http_method:get',
|
|
@@ -185,13 +201,20 @@ describe('createApp', () => {
|
|
|
185
201
|
.expect('server', /.+/)
|
|
186
202
|
.expect('x-api-version', /.+/);
|
|
187
203
|
|
|
188
|
-
expect(
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
204
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
205
|
+
{
|
|
206
|
+
err: {
|
|
207
|
+
message: err.message,
|
|
208
|
+
type: 'Error',
|
|
209
|
+
},
|
|
210
|
+
level: 50,
|
|
211
|
+
method: 'GET',
|
|
212
|
+
msg: 'Server error',
|
|
213
|
+
route: '/',
|
|
214
|
+
status: 500,
|
|
215
|
+
url: '/',
|
|
216
|
+
},
|
|
217
|
+
]);
|
|
195
218
|
|
|
196
219
|
metricsClient.expectTagSubset([
|
|
197
220
|
'http_method:get',
|
|
@@ -212,13 +235,17 @@ describe('createApp', () => {
|
|
|
212
235
|
.expect('server', /.+/)
|
|
213
236
|
.expect('x-api-version', /.+/);
|
|
214
237
|
|
|
215
|
-
expect(
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
238
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
239
|
+
{
|
|
240
|
+
err: null,
|
|
241
|
+
level: 50,
|
|
242
|
+
method: 'GET',
|
|
243
|
+
msg: 'Server error',
|
|
244
|
+
route: '/',
|
|
245
|
+
status: 500,
|
|
246
|
+
url: '/',
|
|
247
|
+
},
|
|
248
|
+
]);
|
|
222
249
|
|
|
223
250
|
metricsClient.expectTagSubset([
|
|
224
251
|
'http_method:get',
|
|
@@ -241,13 +268,17 @@ describe('createApp', () => {
|
|
|
241
268
|
.expect('server', /.+/)
|
|
242
269
|
.expect('x-api-version', /.+/);
|
|
243
270
|
|
|
244
|
-
expect(
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
271
|
+
expect(stdoutMock.calls).toMatchObject([
|
|
272
|
+
{
|
|
273
|
+
err,
|
|
274
|
+
level: 50,
|
|
275
|
+
method: 'GET',
|
|
276
|
+
msg: 'Server error',
|
|
277
|
+
route: '/',
|
|
278
|
+
status: 500,
|
|
279
|
+
url: '/',
|
|
280
|
+
},
|
|
281
|
+
]);
|
|
251
282
|
|
|
252
283
|
metricsClient.expectTagSubset([
|
|
253
284
|
'http_method:get',
|
|
@@ -43,15 +43,15 @@ describe('validate', () => {
|
|
|
43
43
|
.expect(422)
|
|
44
44
|
.expect(({ body }) =>
|
|
45
45
|
expect(body).toMatchInlineSnapshot(`
|
|
46
|
-
{
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
}
|
|
54
|
-
`),
|
|
46
|
+
{
|
|
47
|
+
"invalidFields": {
|
|
48
|
+
"~union0/id": "Invalid input: expected string, received null",
|
|
49
|
+
"~union1/id": "Invalid input: expected number, received null",
|
|
50
|
+
"~union1/summary": "Invalid input: expected string, received undefined",
|
|
51
|
+
},
|
|
52
|
+
"message": "Input validation failed",
|
|
53
|
+
}
|
|
54
|
+
`),
|
|
55
55
|
);
|
|
56
56
|
});
|
|
57
57
|
|
|
@@ -62,17 +62,17 @@ describe('validate', () => {
|
|
|
62
62
|
.expect(422)
|
|
63
63
|
.expect(({ body }) =>
|
|
64
64
|
expect(body).toMatchInlineSnapshot(`
|
|
65
|
-
{
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
}
|
|
75
|
-
`),
|
|
65
|
+
{
|
|
66
|
+
"invalidFields": {
|
|
67
|
+
"~union0/description~union0": "Invalid input: expected string, received undefined",
|
|
68
|
+
"~union0/description~union1": "Invalid input: expected object, received undefined",
|
|
69
|
+
"~union0/id": "Invalid input: expected string, received undefined",
|
|
70
|
+
"~union1/id": "Invalid input: expected number, received undefined",
|
|
71
|
+
"~union1/summary": "Invalid input: expected string, received undefined",
|
|
72
|
+
},
|
|
73
|
+
"message": "Input validation failed",
|
|
74
|
+
}
|
|
75
|
+
`),
|
|
76
76
|
));
|
|
77
77
|
|
|
78
78
|
it('blocks invalid nested union prop', () => {
|
|
@@ -89,17 +89,17 @@ describe('validate', () => {
|
|
|
89
89
|
.expect(422)
|
|
90
90
|
.expect(({ body }) =>
|
|
91
91
|
expect(body).toMatchInlineSnapshot(`
|
|
92
|
-
{
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
}
|
|
102
|
-
`),
|
|
92
|
+
{
|
|
93
|
+
"invalidFields": {
|
|
94
|
+
"~union0/description~union0": "Invalid input: expected string, received object",
|
|
95
|
+
"~union0/description~union1/content": "Invalid input: expected string, received undefined",
|
|
96
|
+
"~union0/id": "Invalid input: expected string, received null",
|
|
97
|
+
"~union1/id": "Invalid input: expected number, received null",
|
|
98
|
+
"~union1/summary": "Invalid input: expected string, received undefined",
|
|
99
|
+
},
|
|
100
|
+
"message": "Input validation failed",
|
|
101
|
+
}
|
|
102
|
+
`),
|
|
103
103
|
);
|
|
104
104
|
});
|
|
105
105
|
});
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { ErrorMiddleware } from 'seek-koala';
|
|
2
|
-
import
|
|
2
|
+
import type * as z from 'zod/v4';
|
|
3
|
+
import type * as core from 'zod/v4/core';
|
|
3
4
|
|
|
4
5
|
import type { Context } from 'src/types/koa';
|
|
5
6
|
|
|
@@ -35,42 +36,39 @@ type InvalidFields = Record<string, string>;
|
|
|
35
36
|
* @see [union error example](./validation.test.ts)
|
|
36
37
|
*/
|
|
37
38
|
const parseInvalidFieldsFromError = (err: z.ZodError): InvalidFields =>
|
|
38
|
-
Object.fromEntries(parseTuples(err
|
|
39
|
+
Object.fromEntries(parseTuples(err.issues));
|
|
39
40
|
|
|
40
41
|
const parseTuples = (
|
|
41
|
-
|
|
42
|
-
|
|
42
|
+
errors: core.$ZodIssue[],
|
|
43
|
+
basePath: Array<string | number | symbol> = [],
|
|
44
|
+
unions: Record<number, number[]> = {},
|
|
43
45
|
): Array<readonly [string, string]> =>
|
|
44
46
|
errors.flatMap((issue) => {
|
|
45
|
-
if (issue.code ===
|
|
46
|
-
return issue.
|
|
47
|
-
parseTuples(err, {
|
|
47
|
+
if (issue.code === 'invalid_union') {
|
|
48
|
+
return issue.errors.flatMap((err, idx) =>
|
|
49
|
+
parseTuples(err, issue.path, {
|
|
48
50
|
...unions,
|
|
49
51
|
[issue.path.length]: [...(unions[issue.path.length] ?? []), idx],
|
|
50
52
|
}),
|
|
51
53
|
);
|
|
52
54
|
}
|
|
53
55
|
|
|
54
|
-
const path = ['', ...issue.path]
|
|
56
|
+
const path = ['', ...basePath, ...issue.path]
|
|
55
57
|
.map((prop, idx) => [prop, ...(unions[idx] ?? [])].join('~union'))
|
|
56
58
|
.join('/');
|
|
57
59
|
|
|
58
60
|
return [[path, issue.message]] as const;
|
|
59
61
|
});
|
|
60
62
|
|
|
61
|
-
export const validate = <
|
|
62
|
-
Output,
|
|
63
|
-
Def extends z.ZodTypeDef = z.ZodTypeDef,
|
|
64
|
-
Input = Output,
|
|
65
|
-
>({
|
|
63
|
+
export const validate = <T extends z.ZodType>({
|
|
66
64
|
ctx,
|
|
67
65
|
input,
|
|
68
66
|
schema,
|
|
69
67
|
}: {
|
|
70
68
|
ctx: Context;
|
|
71
69
|
input: unknown;
|
|
72
|
-
schema:
|
|
73
|
-
}):
|
|
70
|
+
schema: T;
|
|
71
|
+
}): z.infer<T> => {
|
|
74
72
|
const parseResult = schema.safeParse(input);
|
|
75
73
|
if (parseResult.success === false) {
|
|
76
74
|
const invalidFields = parseInvalidFieldsFromError(parseResult.error);
|
|
@@ -85,15 +83,11 @@ export const validate = <
|
|
|
85
83
|
return parseResult.data;
|
|
86
84
|
};
|
|
87
85
|
|
|
88
|
-
export const validateRequestBody = <
|
|
89
|
-
Output,
|
|
90
|
-
Def extends z.ZodTypeDef = z.ZodTypeDef,
|
|
91
|
-
Input = Output,
|
|
92
|
-
>(
|
|
86
|
+
export const validateRequestBody = <T extends z.ZodType>(
|
|
93
87
|
ctx: Context,
|
|
94
|
-
schema:
|
|
95
|
-
):
|
|
96
|
-
validate
|
|
88
|
+
schema: T,
|
|
89
|
+
): z.infer<T> =>
|
|
90
|
+
validate({
|
|
97
91
|
ctx,
|
|
98
92
|
input: ctx.request.body as unknown,
|
|
99
93
|
schema,
|