Skip to content

Commit

Permalink
Slonik/interceptor/camelize result (#184)
Browse files Browse the repository at this point in the history
* feat(slonik): add fieldNameCaseConverter interceptor

* feat(slonik)!: camelize query result

BREAKING CHANGE: SqlFactory arguments have changed.

* fix(multi-tenant): update service factory

* chore(slonik): cleanup configuration
  • Loading branch information
opichon committed Jan 28, 2023
1 parent 40c8676 commit c42649d
Show file tree
Hide file tree
Showing 14 changed files with 1,120 additions and 233 deletions.
28 changes: 28 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
name: "Test suite"

on:
push

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [16.x, 18.x]
steps:
- uses: actions/checkout@v3
name: Use node ${{ matrix.node-version }}

- uses: pnpm/action-setup@v2
name: Install pnpm
with:
version: 7

- name: Install Dependencies
run: pnpm install --frozen-lockfile

- name: Build packages
run: pnpm build

- name: Run tests
run: pnpm test
4 changes: 3 additions & 1 deletion packages/config/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@
"main": "./dist/dzangolab-fastify-config.umd.cjs",
"module": "./dist/dzangolab-fastify-config.js",
"types": "./dist/types/index.d.ts",
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly && mv dist/src dist/types",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
Expand Down
6 changes: 3 additions & 3 deletions packages/multi-tenant/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,15 +32,15 @@
"@types/pg": "8.6.6",
"@typescript-eslint/eslint-plugin": "5.49.0",
"@typescript-eslint/parser": "5.49.0",
"eslint": "8.31.0",
"eslint": "8.32.0",
"eslint-config-custom": "0.14.1",
"fastify": "4.10.2",
"fastify-plugin": "4.4.0",
"prettier": "2.8.1",
"prettier": "2.8.3",
"slonik": "30.3.1",
"tsconfig": "0.14.1",
"typescript": "4.9.4",
"vite": "4.0.3"
"vite": "4.0.4"
},
"peerDependencies": {
"@dzangolab/fastify-config": "0.14.1",
Expand Down
13 changes: 10 additions & 3 deletions packages/slonik/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,22 +13,28 @@
"main": "./dist/dzangolab-fastify-slonik.umd.cjs",
"module": "./dist/dzangolab-fastify-slonik.js",
"types": "./dist/types/index.d.ts",
"files": ["dist"],
"files": [
"dist"
],
"scripts": {
"build": "vite build && tsc --emitDeclarationOnly && mv dist/src dist/types",
"lint": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --ignore-path .gitignore",
"lint:fix": "eslint . --ext .js,.jsx,.cjs,.mjs,.ts,.tsx,.cts,.mts --fix --ignore-path .gitignore",
"sort-package": "npx sort-package-json",
"test": "vitest run --coverage",
"typecheck": "tsc --noEmit -p tsconfig.json --composite false"
},
"dependencies": {
"@dzangolab/postgres-migrations": "5.4.1"
"@dzangolab/postgres-migrations": "5.4.1",
"humps": "2.0.1"
},
"devDependencies": {
"@dzangolab/fastify-config": "0.14.1",
"@types/humps": "2.0.2",
"@types/node": "18.11.18",
"@typescript-eslint/eslint-plugin": "5.49.0",
"@typescript-eslint/parser": "5.49.0",
"@vitest/coverage-istanbul": "0.28.1",
"eslint": "8.32.0",
"eslint-config-custom": "0.14.1",
"fastify": "4.10.2",
Expand All @@ -37,7 +43,8 @@
"slonik": "30.3.1",
"tsconfig": "0.14.1",
"typescript": "4.9.4",
"vite": "4.0.4"
"vite": "4.0.4",
"vitest": "0.28.3"
},
"peerDependencies": {
"@dzangolab/fastify-config": "0.14.1",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import { describe, expect, it } from "vitest";

import fieldNameCaseConverter from "../../interceptors/fieldNameCaseConverter";
import createClientConfiguration from "../createClientConfiguration";

import type { Query, QueryContext } from "slonik";

describe("createClientConfiguration helper", () => {
const defaultConfiguration = {
captureStackTrace: false,
connectionRetryLimit: 3,
connectionTimeout: 5000,
idleInTransactionSessionTimeout: 60000,
idleTimeout: 5000,
interceptors: [fieldNameCaseConverter],
maximumPoolSize: 10,
queryRetryLimit: 5,
statementTimeout: 60000,
transactionRetryLimit: 5,
};

it("creates default configuration", () => {
const configuration = createClientConfiguration();

expect(configuration).toEqual(defaultConfiguration);
});

it("includes fieldNameCaseConvertor interceptor", () => {
const interceptor = {
transformQuery: (context: QueryContext, query: Query): Query => {
return query;
},
};

const configuration = createClientConfiguration({
interceptors: [interceptor],
});

expect(configuration.interceptors).toContain(fieldNameCaseConverter);
});
});
31 changes: 31 additions & 0 deletions packages/slonik/src/factories/createClientConfiguration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
import fieldNameCaseConverter from "../interceptors/fieldNameCaseConverter";

import type { ClientConfigurationInput } from "slonik/dist/src/types";

const createClientConfiguration = (
config?: ClientConfigurationInput
): ClientConfigurationInput => {
const configuration = {
captureStackTrace: false,
connectionRetryLimit: 3,
connectionTimeout: 5000,
idleInTransactionSessionTimeout: 60000,
idleTimeout: 5000,
interceptors: [],
maximumPoolSize: 10,
queryRetryLimit: 5,
statementTimeout: 60000,
transactionRetryLimit: 5,

...config,
};

configuration.interceptors = [
fieldNameCaseConverter,
...(config?.interceptors ?? []),
];

return configuration;
};

export default createClientConfiguration;
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
import { describe, expect, it } from "vitest";

import createQueryContext from "./helpers/createQueryContext";
import fieldNameCaseConverter from "../fieldNameCaseConverter";

describe("fielNameCaseConverter interceptor", () => {
const interceptor = fieldNameCaseConverter;

it("transforms result field names to camelcase", () => {
const { transformRow } = interceptor;

if (!transformRow) {
throw new Error("Unexpected state.");
}

const result = transformRow(
createQueryContext(),
{
sql: "SELECT 1",
values: [],
},
{
created_at: "2022-01-01 00:00:01",
foo_bar: 1,
is_enabled: true,
is_enabled_some_of_the_time: false,
/* [OP 2023-01-28] See https://github.com/gajus/slonik/issues/428
object_child: {
user_id: 1,
},
some_children: [
{
user_id: 1,
},
{
user_id: 2,
},
],
*/
updated_at: "2022-01-01 00:00:01",
},
[
{
dataTypeId: 1,
name: "foo_bar",
},
]
);

const expected = {
createdAt: "2022-01-01 00:00:01",
fooBar: 1,
isEnabled: true,
isEnabledSomeOfTheTime: false,
/* [OP 2023-01-28] See https://github.com/gajus/slonik/issues/428
firstChild: {
userId: 1,
},
someChildren: [
{
userId: 1,
},
{
userId: 2,
},
],
*/
updatedAt: "2022-01-01 00:00:01",
};

expect(expected).toEqual(result);
});
});
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { QueryContext } from "slonik";

const helper = (): QueryContext => {
return {
connectionId: "1",
log: {
getContext: () => {
return {
connectionId: "1",
poolId: "1",
};
},
},
poolId: "1",
sandbox: {},
// eslint-disable-next-line @typescript-eslint/no-explicit-any
} as any;
};

export default helper;
24 changes: 24 additions & 0 deletions packages/slonik/src/interceptors/fieldNameCaseConverter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import humps from "humps";

import type {
Field,
Interceptor,
Query,
QueryContext,
QueryResultRow,
} from "slonik";

const fieldNameCaseConverter: Interceptor = {
transformRow: (
/* eslint-disable @typescript-eslint/no-unused-vars */
queryContext: QueryContext,
query: Query,
row: QueryResultRow,
fields: readonly Field[]
/* eslint-enable */
): QueryResultRow => {
return humps.camelizeKeys(row) as QueryResultRow;
},
};

export default fieldNameCaseConverter;
3 changes: 2 additions & 1 deletion packages/slonik/src/plugin.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import FastifyPlugin from "fastify-plugin";
import { stringifyDsn } from "slonik";

import createClientConfiguration from "./factories/createClientConfiguration";
import migrate from "./migrate";
import { fastifySlonik } from "./slonik";

Expand All @@ -17,7 +18,7 @@ const plugin = async (

fastify.register(fastifySlonik, {
connectionString: stringifyDsn(config.db),
clientConfiguration: config?.clientConfiguration,
clientConfiguration: createClientConfiguration(config?.clientConfiguration),
});

fastify.log.info("Running database migrations");
Expand Down
2 changes: 2 additions & 0 deletions packages/slonik/src/sqlFactory.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { sql } from "slonik";

import {
createFilterFragment,
createLimitFragment,
Expand Down
4 changes: 4 additions & 0 deletions packages/slonik/tsconfig.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
{
"extends": "tsconfig/base.json",
"exclude": [
"src/**/__test__/**/*",
],
"compilerOptions": {
"baseUrl": "./",
"outDir": "./dist",
},
"include": [
Expand Down
13 changes: 13 additions & 0 deletions packages/slonik/vite.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export default defineConfig(({ mode }) => {
"@dzangolab/postgres-migrations",
"fastify",
"fastify-plugin",
"humps",
"slonik",
],
output: {
Expand All @@ -29,11 +30,23 @@ export default defineConfig(({ mode }) => {
"@dzangolab/postgres-migrations": "DzangolabPostgresMigrations",
fastify: "Fastify",
"fastify-plugin": "FastifyPlugin",
humps: "Humps",
slonik: "Slonik",
},
},
},
target: "es2022",
},
resolve: {
alias: {
"@/": new URL("src/", import.meta.url).pathname,
},
},
test: {
coverage: {
provider: "istanbul",
reporter: ["text", "json", "html"],
},
},
};
});
Loading

0 comments on commit c42649d

Please sign in to comment.