Skip to content

Commit

Permalink
allow override default types (#46)
Browse files Browse the repository at this point in the history
  • Loading branch information
Newbie012 committed Sep 20, 2022
1 parent 880eb72 commit 69b874e
Show file tree
Hide file tree
Showing 9 changed files with 182 additions and 71 deletions.
38 changes: 38 additions & 0 deletions .changeset/large-coins-explain.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
---
"@ts-safeql/eslint-plugin": patch
"@ts-safeql/generate": patch
"@ts-safeql/shared": patch
---

you can now override the default types (e.g. timestamp -> DateTime) by adding an `overrides` property to the config:

```ts
// safeql.config.ts
import { definedConfig } from "@ts-safeql/eslint-plugin";

export default definedConfig({
// ...
overrides: {
types: {
timestamp: "DateTime",
},
},
});
```

or

```json
// .eslintrc.json
{
// ...
"connections": {
// ...,
"overrides": {
"types": {
"timestamp": "DateTime"
}
}
}
}
```
12 changes: 11 additions & 1 deletion packages/eslint-plugin/src/rules/check-sql.rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { GenerateResult } from "@ts-safeql/generate";
import { PostgresError } from "@ts-safeql/shared";
import { objectKeysNonEmpty, PostgresError, defaultTypeMapping } from "@ts-safeql/shared";
import { ESLintUtils, ParserServices, TSESLint, TSESTree } from "@typescript-eslint/utils";
import pgParser from "libpg-query";
import { createSyncFn } from "synckit";
Expand Down Expand Up @@ -54,6 +54,16 @@ const baseSchema = z.object({
* Whether or not keep the connection alive. Change it only if you know what you're doing.
*/
keepAlive: z.boolean().optional(),

/**
* Override defaults
*/
overrides: z
.object({
types: z.record(z.enum(objectKeysNonEmpty(defaultTypeMapping)), z.string()),
})
.partial()
.optional(),
});

const identifyByNameAndOperators = z.object({
Expand Down
27 changes: 27 additions & 0 deletions packages/eslint-plugin/src/rules/check-sql.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -413,4 +413,31 @@ RuleTester.describe("check-sql", () => {
},
],
});

ruleTester.run("connection with overrides.types", rules["check-sql"], {
valid: [
{
name: 'with { int4: "Integer" }',
filename,
options: withConnection(connections.withTagName, {
overrides: { types: { int4: "Integer" } },
}),
code: "sql<{ id: Integer }>`select id from caregiver`",
},
],
invalid: [
{
name: 'with { int4: "Integer" } while { id: number }',
filename,
options: withConnection(connections.withTagName, {
overrides: { types: { int4: "Integer" } },
}),
code: "sql<{ id: number }>`select id from caregiver`",
output: "sql<{ id: Integer; }>`select id from caregiver`",
errors: [
{ messageId: "incorrectTypeAnnotations", line: 1, column: 5, endLine: 1, endColumn: 19 },
],
},
],
});
});
1 change: 1 addition & 0 deletions packages/eslint-plugin/src/rules/check-sql.worker.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ function workerHandler(params: WorkerParams): TE.TaskEither<WorkerError, WorkerR
query: params.query,
cacheKey: databaseUrl,
pgParsed: params.pgParsed,
overrides: params.connection.overrides,
});
}),
TE.chainW(TE.fromEither)
Expand Down
39 changes: 30 additions & 9 deletions packages/generate/src/generate.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,15 @@
import { DuplicateColumnsError, groupBy, PostgresError } from "@ts-safeql/shared";
import { ParsedQuery } from "@ts-safeql/shared";
import {
defaultTypeMapping,
DuplicateColumnsError,
groupBy,
ParsedQuery,
PostgresError,
} from "@ts-safeql/shared";
import { either } from "fp-ts";
import { Either } from "fp-ts/lib/Either";
import postgres, { PostgresError as OriginalPostgresError } from "postgres";
import "source-map-support/register";
import { ColType, defaultTypeMapping } from "./utils/colTypes";
import { ColType } from "./utils/colTypes";
import { getLeftJoinTablesFromParsed } from "./utils/getLeftJoinTables";

type CacheKey = string;
Expand Down Expand Up @@ -52,6 +57,9 @@ export interface GenerateParams {
pgParsed: ParsedQuery.Root;
cacheMetadata?: boolean;
cacheKey: string;
overrides?: Partial<{
types: Record<string, string>;
}>;
}

export async function generate(
Expand Down Expand Up @@ -99,8 +107,10 @@ export async function generate(
return introspected === undefined ? { described: col } : { described: col, introspected };
});

const typesMap = { ...defaultTypeMapping, ...params.overrides?.types };

return either.right({
result: mapColumnAnalysisResultsToTypeLiteral({ columns, pgTypes, leftTables }),
result: mapColumnAnalysisResultsToTypeLiteral({ columns, pgTypes, leftTables, typesMap }),
stmt: result,
query: query,
});
Expand Down Expand Up @@ -128,12 +138,14 @@ function mapColumnAnalysisResultsToTypeLiteral(params: {
columns: ColumnAnalysisResult[];
pgTypes: PgTypeRow[];
leftTables: number[];
typesMap: Record<string, string>;
}) {
const properties = params.columns.map((col) => {
const propertySignature = mapColumnAnalysisResultToPropertySignature({
col,
pgTypes: params.pgTypes,
leftTables: params.leftTables,
typesMap: params.typesMap,
});

return `${propertySignature};`;
Expand All @@ -150,9 +162,10 @@ function mapColumnAnalysisResultToPropertySignature(params: {
col: ColumnAnalysisResult;
pgTypes: PgTypeRow[];
leftTables: number[];
typesMap: Record<string, string>;
}) {
if ("introspected" in params.col) {
const tsType = defaultTypeMapping[params.col.introspected.colType];
const tsType = params.typesMap[params.col.introspected.colType];
const value = params.col.introspected.colNotNull ? tsType : `Nullable<${tsType}>`;
const isFromLeftJoin = params.leftTables.includes(params.col.introspected.tableOid);

Expand All @@ -166,6 +179,7 @@ function mapColumnAnalysisResultToPropertySignature(params: {
const nonTableColumnType = getTsTypeFromPgTypeOid({
pgTypeOid: params.col.described.type,
pgTypes: params.pgTypes,
typesMap: params.typesMap,
});

return buildInterfacePropertyValue({
Expand All @@ -175,19 +189,26 @@ function mapColumnAnalysisResultToPropertySignature(params: {
});
}

function getTsTypeFromPgTypeOid(params: { pgTypes: PgTypeRow[]; pgTypeOid: number }) {
function getTsTypeFromPgTypeOid(params: {
pgTypes: PgTypeRow[];
pgTypeOid: number;
typesMap: Record<string, string>;
}) {
const pgType = params.pgTypes.find((type) => type.oid === params.pgTypeOid);

if (pgType === undefined) {
return "unknown";
}

return getTsTypeFromPgType({ pgTypeName: pgType.name });
return getTsTypeFromPgType({ pgTypeName: pgType.name, typesMap: params.typesMap });
}

function getTsTypeFromPgType(params: { pgTypeName: ColType | `_${ColType}` }) {
function getTsTypeFromPgType(params: {
pgTypeName: ColType | `_${ColType}`;
typesMap: Record<string, string>;
}) {
const { isArray, pgType } = parsePgType(params.pgTypeName);
const tsType = defaultTypeMapping[pgType] ?? "any";
const tsType = params.typesMap[pgType] ?? "any";

return isArray ? `Array<${tsType}>` : tsType;
}
Expand Down
61 changes: 0 additions & 61 deletions packages/generate/src/utils/colTypes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,64 +41,3 @@ export const colTypes = [
] as const;

export type ColType = typeof colTypes[number];

export const defaultTypeMapping = {
// Integer types
int2: "number",
int4: "number",
int8: "string",
smallint: "number",
int: "number",
bigint: "string",

// Precision types
real: "number",
float4: "number",
float: "number",
float8: "number",
numeric: "number",
decimal: "number",

// Serial types
smallserial: "number",
serial: "number",
bigserial: "string",

// Common string types
uuid: "string",
text: "string",
varchar: "string",
char: "string",
bpchar: "string",
citext: "string",

// Bool types
bit: "boolean",
bool: "boolean",
boolean: "boolean",

// Dates and times
date: "Date",
timestamp: "Date",
timestamptz: "Date",
time: "Date",
timetz: "Date",
interval: "string",

// Network address types
inet: "string",
cidr: "string",
macaddr: "string",
macaddr8: "string",

// Extra types
money: "number",
void: "void",

// JSON types
json: "any",
jsonb: "any",

// Bytes
bytea: "any",
} as const;
14 changes: 14 additions & 0 deletions packages/shared/src/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -104,3 +104,17 @@ type NonEmptyArray<T> = readonly [T, ...ReadonlyArray<T>];
export function isNonEmpty<T>(array: ReadonlyArray<T> | undefined): array is NonEmptyArray<T> {
return array !== undefined && array.length > 0;
}

export function objectKeys<T extends object>(obj: T): (keyof T)[] {
return Object.keys(obj) as (keyof T)[];
}

export function objectKeysNonEmpty<T extends object>(obj: T): [keyof T, ...(keyof T)[]] {
const keys = objectKeys(obj);

if (keys.length === 0) {
throw new Error("expected non-empty object");
}

return keys as [keyof T, ...(keyof T)[]];
}
1 change: 1 addition & 0 deletions packages/shared/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from "./common";
export * from "./errors";
export * from "./parsedQuery";
export * from "./pg";
60 changes: 60 additions & 0 deletions packages/shared/src/pg.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
export const defaultTypeMapping = {
// Integer types
int2: "number",
int4: "number",
int8: "string",
smallint: "number",
int: "number",
bigint: "string",

// Precision types
real: "number",
float4: "number",
float: "number",
float8: "number",
numeric: "number",
decimal: "number",

// Serial types
smallserial: "number",
serial: "number",
bigserial: "string",

// Common string types
uuid: "string",
text: "string",
varchar: "string",
char: "string",
bpchar: "string",
citext: "string",

// Bool types
bit: "boolean",
bool: "boolean",
boolean: "boolean",

// Dates and times
date: "Date",
timestamp: "Date",
timestamptz: "Date",
time: "Date",
timetz: "Date",
interval: "string",

// Network address types
inet: "string",
cidr: "string",
macaddr: "string",
macaddr8: "string",

// Extra types
money: "number",
void: "void",

// JSON types
json: "any",
jsonb: "any",

// Bytes
bytea: "any",
} as const;

0 comments on commit 69b874e

Please sign in to comment.