Skip to content

Commit

Permalink
v19 is for Dime (#1692)
Browse files Browse the repository at this point in the history
![image](https://github.com/RobinTail/express-zod-api/assets/13189514/f090c11f-4d73-409a-8546-5c586adb0afc)

Pebbles LaDime (Dime) Doe was a black transgender woman.


https://edition.cnn.com/2024/02/25/us/dime-doe-trial-south-carolina-federal-hate-crime/index.html

https://www.nbcnews.com/feature/nbc-out/south-carolina-death-marks-14th-black-transgender-woman-killed-u-n1040971

https://www.justice.gov/opa/pr/south-carolina-man-found-guilty-hate-crime-killing-transgender-woman-because-her-gender

Transgender women suffer too frequently from transphobic violence and
cruelty, being the less protected social group. I'd like to raise an
awareness of this problem. Humans should be creators — not killers. But
most importantly, I want every transgender girl to have an opportunity
to create applications quickly and, in general, learn to write code
easily in order to receive job offers and leave dangerously transphobic
territories for more favorable and civilized ones, and live happily
there. Protect transgender women.

------------------------------

This version is focused on making `express-zod-api` a complete `zod`
plugin. In this regard `withMeta` is removed in favor of the recommended
approach on extending `zod` functionality, which opens up opportunities
for new features and simplifies the consumer experience. Another
improvement has been made for parsers: they are now applied selectively
depending on the type of expected request.

- #1693 
- #1705 
- #1707 
- Theoretically #1631 
  - In advance #1708 
- #1726 
- #1736 
- #1741 
  - #1745 
  - #1762 
  - #1756 
  - #1766 
- #1730 
  - #1747 
  - #1748 
- #1755 
- #1760 

- Not included, but comes later: #1750 

⚠️ don't squash it, to avoid conflicts with #1750
  • Loading branch information
RobinTail authored May 13, 2024
2 parents fc62184 + 02a10ef commit 60a463c
Show file tree
Hide file tree
Showing 69 changed files with 912 additions and 733 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/codeql-analysis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@ name: "CodeQL"

on:
push:
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]
pull_request:
# The branches below must be a subset of the branches above
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]
schedule:
- cron: '26 8 * * 1'

Expand Down
22 changes: 4 additions & 18 deletions .github/workflows/node.js.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,17 @@ name: Node.js CI

on:
push:
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]
pull_request:
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]

jobs:
build:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
node-version: [18.0.0, 18.x, 20.0.0, 20.x, 22.0.0, 22.x]
node-version: [18.18.0, 18.x, 20.9.0, 20.x, 22.0.0, 22.x]
# See supported Node.js release schedule at https://nodejs.org/en/about/releases/
steps:
- name: Get yarn cache dir
Expand All @@ -41,13 +41,7 @@ jobs:
timeout_seconds: 15
max_attempts: 3
on_retry_command: yarn config set registry https://registry.npmjs.org
# todo use regular "yarn install" when min Node version increased to 18.18
# @typescript/eslint group compatibility issue fixed by ignoring engines for dev dependencies only:
command: |
npm pkg delete devDependencies
yarn install
git checkout -- .
yarn install --ignore-engines
command: yarn install
- name: Lint
run: yarn lint
- name: Test
Expand Down Expand Up @@ -75,15 +69,7 @@ jobs:
max_attempts: 3
on_retry_command: yarn config set registry https://registry.npmjs.org
command: yarn test:esm
- name: Check Jest 30 compatibility
uses: madhead/semver-utils@v4
id: jest30compat
with:
version: ${{ steps.setup-node.outputs.node-version }}
satisfies: '>=18.12.0'
lenient: false # require to parse or fail
- name: Compatibility test
if: steps.jest30compat.outputs.satisfies == 'true'
uses: nick-fields/retry@v3
with:
timeout_seconds: 15
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/swagger.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,9 @@ name: OpenAPI Validation

on:
push:
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]
pull_request:
branches: [ master, v15, v16, v17 ]
branches: [ master, v16, v17, v18 ]


jobs:
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ yarn-error.log
coverage
tests/**/yarn.lock
tests/**/quick-start.ts
tests/issue952/*.d.ts
90 changes: 90 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,95 @@
# Changelog

## Version 19

### v19.0.0

- **Breaking changes**:
- Increased the minimum supported versions:
- For Node.js: 18.18.0, 20.9.0 or 22.0.0;
- For `zod`: 3.23.0;
- For `express`: [4.19.2](https://github.com/expressjs/express/security/advisories/GHSA-rv95-896h-c2vc);
- For `express-fileupload` and `@types/express-fileupload`: 1.5.0.
- Removed the deprecated method ~~`withMeta()`~~ (see [v18.5.0](#v1850) for details);
- Removed support for static options by `EndpointsFactory::addOptions()` (see [v18.6.0](#v1860) for details);
- Freezed the arrays returned by the methods or exposed by properties of `Endpoint` and `DependsOnMethod`;
- Changed interface for `ez.raw()`: additional properties should be supplied as its argument, not via `.extend()`;
- Changed the following config options:
- The function assigned to `server.upload.beforeUpload` now accepts `request` instead of `app`;
- The function assigned to `server.beforeRouting` is now called before parsing too.
- Features:
- New configurable level `info` for built-in logger (higher than `debug`, but lower than `warn`);
- Selective parsers equipped with a child logger:
- There are 3 types of endpoints depending on their input schema: having `ez.upload()`, having `ez.raw()`, others;
- Depending on that type, only the parsers needed for certain endpoint are processed;
- This makes all requests eligible for the assigned parsers and reverts changes made in [v18.5.2](#v1852);
- Specifying `rawParser` in config is no longer needed to enable the feature.
- Non-breaking significant changes:
- Request logging reflects the actual path instead of the configured route, and it's placed in front of parsing:
- The severity of those messaged reduced from `info` to `debug`;
- The debug messages from uploader are enabled by default when the logger level is set to `debug`;
- How to migrate confidently:
- Upgrade Node.js, `zod`, `express`, `express-fileupload` and `@types/express-fileupload` accordingly;
- Avoid mutating the readonly arrays;
- If you're using ~~`withMeta()`~~:
- Remove it and unwrap your schemas — you can use `.example()` method directly.
- If you're using `.addOptions()` on `EndpointsFactory` instance:
- Replace the argument with an async function returning those options;
- Or assign those options to `const` and import them where needed.
- If you're using `ez.raw().extend()` for additional properties:
- Supply them directly as an argument to `ez.raw()` — see the example below.
- If you're using `beforeUpload` in your config:
- Adjust the implementation according to the example below.
- If you're using `beforeRouting` in your config for anything that requires a parsed request body:
- Add the required parsers using `app.use()` statements to the assigned function.
- If you're having `rawParser: express.raw()` in your config:
- You can now remove this line (it's the default value now), unless you're having any customizations.

```ts
import createHttpError from "http-errors";
import { createConfig } from "express-zod-api";

const before = createConfig({
server: {
upload: {
beforeUpload: ({ app, logger }) => {
app.use((req, res, next) => {
if (req.is("multipart/form-data") && !canUpload(req)) {
return next(createHttpError(403, "Not authorized"));
}
next();
});
},
},
},
});

const after = createConfig({
server: {
upload: {
beforeUpload: ({ request, logger }) => {
if (!canUpload(request)) {
throw createHttpError(403, "Not authorized");
}
},
},
},
});
```

```ts
import { z } from "zod";
import { ez } from "express-zod-api";

const before = ez.raw().extend({
pathParameter: z.string(),
});

const after = ez.raw({
pathParameter: z.string(),
});
```

## Version 18

### v18.6.2
Expand Down
62 changes: 26 additions & 36 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,9 +54,10 @@ Start your API server with I/O schema validation and custom middlewares in minut
4. [Accepting raw data](#accepting-raw-data)
5. [Subscriptions](#subscriptions)
7. [Integration and Documentation](#integration-and-documentation)
1. [Generating a Frontend Client](#generating-a-frontend-client)
2. [Creating a documentation](#creating-a-documentation)
3. [Tagging the endpoints](#tagging-the-endpoints)
1. [Zod Plugin](#zod-plugin)
2. [Generating a Frontend Client](#generating-a-frontend-client)
3. [Creating a documentation](#creating-a-documentation)
4. [Tagging the endpoints](#tagging-the-endpoints)
8. [Caveats](#caveats)
1. [Coercive schema of Zod](#coercive-schema-of-zod)
2. [Excessive properties in endpoint output](#excessive-properties-in-endpoint-output)
Expand Down Expand Up @@ -87,7 +88,7 @@ Therefore, many basic tasks can be accomplished faster and easier, in particular

- [Typescript](https://www.typescriptlang.org/) first.
- Web server — [Express.js](https://expressjs.com/).
- Schema validation — [Zod 3.x](https://github.com/colinhacks/zod).
- Schema validation — [Zod 3.x](https://github.com/colinhacks/zod) including [Zod Plugin](#zod-plugin).
- Supports any logger having `info()`, `debug()`, `error()` and `warn()` methods;
- Built-in console logger with colorful and pretty inspections by default.
- Generators:
Expand Down Expand Up @@ -801,16 +802,8 @@ const config = createConfig({
```

Refer to [documentation](https://www.npmjs.com/package/express-fileupload#available-options) on available options.
Some options are forced in order to ensure the correct workflow:

```json5
{
abortOnLimit: false,
parseNested: true,
logger: {}, // the configured logger, using its .debug() method
}
```

Some options are forced in order to ensure the correct workflow: `abortOnLimit: false`, `parseNested: true`, `logger`
is assigned with `.debug()` method of the configured logger, and `debug` is enabled by default.
The `limitHandler` option is replaced by the `limitError` one. You can also connect an additional middleware for
restricting the ability to upload using the `beforeUpload` option. So the configuration for the limited and restricted
upload might look this way:
Expand All @@ -823,13 +816,10 @@ const config = createConfig({
upload: {
limits: { fileSize: 51200 }, // 50 KB
limitError: createHttpError(413, "The file is too large"), // handled by errorHandler in config
beforeUpload: ({ app, logger }) => {
app.use((req, res, next) => {
if (req.is("multipart/form-data") && !canUpload(req)) {
return next(createHttpError(403, "Not authorized"));
}
next();
});
beforeUpload: ({ request, logger }) => {
if (!canUpload(request)) {
throw createHttpError(403, "Not authorized");
}
},
},
},
Expand Down Expand Up @@ -1019,26 +1009,18 @@ defaultEndpointsFactory.build({
## Accepting raw data

Some APIs may require an endpoint to be able to accept and process raw data, such as streaming or uploading a binary
file as an entire body of request. In order to enable this feature you need to set the `rawParser` config feature to
`express.raw()`. See also its options [in Express.js documentation](https://expressjs.com/en/4x/api.html#express.raw).
The raw data is placed into `request.body.raw` property, having type `Buffer`. Then use the proprietary `ez.raw()`
schema (which is an alias for `z.object({ raw: ez.file("buffer") })`) as the input schema of your endpoint.
file as an entire body of request. Use the proprietary `ez.raw()` schema as the input schema of your endpoint.
The default parser in this case is `express.raw()`. You can customize it by assigning the `rawParser` option in config.
The raw data is placed into `request.body.raw` property, having type `Buffer`.

```typescript
import express from "express";
import { createConfig, defaultEndpointsFactory, ez } from "express-zod-api";

const config = createConfig({
server: {
rawParser: express.raw(), // enables the feature
},
});
import { defaultEndpointsFactory, ez } from "express-zod-api";

const rawAcceptingEndpoint = defaultEndpointsFactory.build({
method: "post",
input: ez
.raw() // accepts the featured { raw: Buffer }
.extend({}), // for additional inputs, like route params, if needed
input: ez.raw({
/* the place for additional inputs, like route params, if needed */
}),
output: z.object({ length: z.number().int().nonnegative() }),
handler: async ({ input: { raw } }) => ({
length: raw.length, // raw is Buffer
Expand All @@ -1059,6 +1041,14 @@ https://github.com/RobinTail/zod-sockets#subscriptions

# Integration and Documentation

## Zod Plugin

Express Zod API acts as a plugin for Zod, extending its functionality once you import anything from `express-zod-api`:

- Adds `.example()` method to all Zod schemas for storing examples and reflecting them in the generated documentation;
- Adds `.label()` method to `ZodDefault` for replacing the default value in documentation with a label;
- Alters the `.brand()` method on all Zod schemas by making the assigned brand available in runtime.

## Generating a Frontend Client

You can generate a Typescript file containing the IO types of your API and a client for it.
Expand Down
3 changes: 2 additions & 1 deletion SECURITY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

| Version | Release | Supported |
| ------: | :------ | :----------------: |
| 19.x.x | 05.2024 | :white_check_mark: |
| 18.x.x | 04.2024 | :white_check_mark: |
| 17.x.x | 02.2024 | :white_check_mark: |
| 16.x.x | 12.2023 | :white_check_mark: |
| 15.x.x | 12.2023 | :white_check_mark: |
| 15.x.x | 12.2023 | :x: |
| 14.x.x | 10.2023 | :x: |
| 12.x.x | 09.2023 | :x: |
| 11.x.x | 06.2023 | :x: |
Expand Down
3 changes: 0 additions & 3 deletions example/config.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import express from "express";
import { createConfig } from "../src";
import ui from "swagger-ui-express";
import yaml from "yaml";
Expand All @@ -9,12 +8,10 @@ export const config = createConfig({
server: {
listen: 8090,
upload: {
debug: true,
limits: { fileSize: 51200 },
limitError: createHttpError(413, "The file is too large"), // affects uploadAvatarEndpoint
},
compression: true, // affects sendAvatarEndpoint
rawParser: express.raw(), // required for rawAcceptingEndpoint
beforeRouting: async ({ app }) => {
// third-party middlewares serving their own routes or establishing their own routing besides the API
const documentation = yaml.parse(
Expand Down
8 changes: 4 additions & 4 deletions example/endpoints/accept-raw.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import { taggedEndpointsFactory } from "../factories";
export const rawAcceptingEndpoint = taggedEndpointsFactory.build({
method: "post",
tag: "files",
input: ez
.raw() // requires to enable rawParser option in server config
.extend({}), // additional inputs, route params for example, if needed
input: ez.raw({
/* the place for additional inputs, like route params, if needed */
}),
output: z.object({ length: z.number().int().nonnegative() }),
handler: async ({ input: { raw } }) => ({
length: raw.length, // input.raw is populated automatically when rawParser is set in config
length: raw.length, // input.raw is populated automatically by the corresponding parser
}),
});
2 changes: 1 addition & 1 deletion example/example.documentation.yaml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
openapi: 3.1.0
info:
title: Example API
version: 18.6.2
version: 19.0.0-beta.6
paths:
/v1/user/retrieve:
get:
Expand Down
Loading

0 comments on commit 60a463c

Please sign in to comment.