Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat!: Allow non-optional generation of params with optionalNullParams #582

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions docs-new/docs/cli.md
Original file line number Diff line number Diff line change
@@ -61,6 +61,7 @@ For a full list of options, see the [Configuration file format](#configuration-f
"failOnError": false, // Whether to fail on a file processing error and abort generation (can be omitted - default is false)
"camelCaseColumnNames": false, // convert to camelCase column names of result interface
"nonEmptyArrayParams": false, // Whether the type for an array parameter should exclude empty arrays
"optionalNullParams": true, // Whether nullable parameters are made optional
"dbUrl": "postgres://user:password@host/database", // DB URL (optional - will be merged with db if provided)
"db": {
"dbName": "testdb", // DB name
@@ -94,6 +95,7 @@ Configuration file can be also be written in CommonJS format and default exporte
| `dbUrl?` | `string` | A connection string to the database. Example: `postgres://user:password@host/database`. Overrides (merged) with `db` config. |
| `camelCaseColumnNames?` | `boolean` | Whether to convert column names to camelCase. _Note that this only coverts the types. You need to do this at runtime independently using a library like `pg-camelcase`_. |
| `nonEmptyArrayParams?` | `boolean` | Whether the types for arrays parameters exclude empty arrays. This helps prevent runtime errors when accidentally providing empty input to a query. |
| `optionalNullParams?` | `boolean` | Whether nullable parameters are automatically marked as optional. **Default:** `true` |
| `typesOverrides?` | `Record<string, string>` | A map of type overrides. Similarly to `camelCaseColumnNames`, this only affects the types. _You need to do this at runtime independently using a library like `pg-types`._ |
| `maxWorkerThreads` | `number` | The maximum number of worker threads to use for type generation. **The default is based on the number of available CPUs.** |

4 changes: 4 additions & 0 deletions packages/cli/src/config.ts
Original file line number Diff line number Diff line change
@@ -58,6 +58,7 @@ const configParser = t.type({
camelCaseColumnNames: t.union([t.boolean, t.undefined]),
hungarianNotation: t.union([t.boolean, t.undefined]),
nonEmptyArrayParams: t.union([t.boolean, t.undefined]),
optionalNullParams: t.union([t.boolean, t.undefined]),
dbUrl: t.union([t.string, t.undefined]),
db: t.union([
t.type({
@@ -101,6 +102,7 @@ export interface ParsedConfig {
camelCaseColumnNames: boolean;
hungarianNotation: boolean;
nonEmptyArrayParams: boolean;
optionalNullParams: boolean;
transforms: IConfig['transforms'];
srcDir: IConfig['srcDir'];
typesOverrides: Record<string, Partial<TypeDefinition>>;
@@ -201,6 +203,7 @@ export function parseConfig(
camelCaseColumnNames,
hungarianNotation,
nonEmptyArrayParams,
optionalNullParams,
typesOverrides,
} = configObject as IConfig;

@@ -246,6 +249,7 @@ export function parseConfig(
camelCaseColumnNames: camelCaseColumnNames ?? false,
hungarianNotation: hungarianNotation ?? true,
nonEmptyArrayParams: nonEmptyArrayParams ?? false,
optionalNullParams: optionalNullParams ?? true,
typesOverrides: parsedTypesOverrides,
maxWorkerThreads,
};
87 changes: 84 additions & 3 deletions packages/cli/src/generator.test.ts
Original file line number Diff line number Diff line change
@@ -14,7 +14,12 @@ import {
import { parseCode as parseTypeScriptFile } from './parseTypescript.js';
import { TypeAllocator, TypeMapping, TypeScope } from './types.js';

const partialConfig = { hungarianNotation: true } as ParsedConfig;
// Note: You are required to add any default values to this config to make it
// compatible with the default behavior. See cli/src/config.ts for values.
const partialConfig = {
hungarianNotation: true,
optionalNullParams: true,
} as ParsedConfig;

function parsedQuery(
mode: ProcessingMode,
@@ -311,7 +316,7 @@ export interface IDeleteUsersQuery {
parsedQuery(mode, queryString),
typeSource,
types,
{ camelCaseColumnNames: true, hungarianNotation: true } as ParsedConfig,
{ ...partialConfig, camelCaseColumnNames: true } as ParsedConfig,
);
const expectedTypes = `import { PreparedQuery } from '@pgtyped/runtime';

@@ -331,6 +336,82 @@ export interface IGetNotificationsResult {
typeCamelCase: PayloadType;
}

/** 'GetNotifications' query type */
export interface IGetNotificationsQuery {
params: IGetNotificationsParams;
result: IGetNotificationsResult;
}\n\n`;
expect(result).toEqual(expected);
});

test(`Null parameters generation as required (${mode})`, async () => {
const queryStringSQL = `
/* @name GetNotifications */
SELECT payload, type FROM notifications WHERE id = :userId;
`;
const queryStringTS = `
const getNotifications = sql\`SELECT payload, type FROM notifications WHERE id = $userId\`;
`;
const queryString =
mode === ProcessingMode.SQL ? queryStringSQL : queryStringTS;
const mockTypes: IQueryTypes = {
returnTypes: [
{
returnName: 'payload',
columnName: 'payload',
type: 'json',
nullable: false,
},
{
returnName: 'type',
columnName: 'type',
type: { name: 'PayloadType', enumValues: ['message', 'dynamite'] },
nullable: false,
},
],
paramMetadata: {
params: ['uuid'],
mapping: [
{
name: 'userId',
type: ParameterTransform.Scalar,
required: false,
assignedIndex: 1,
},
],
},
};
const typeSource = async (_: any) => mockTypes;
const types = new TypeAllocator(TypeMapping());
// Test out imports
types.use(
{ name: 'PreparedQuery', from: '@pgtyped/runtime' },
TypeScope.Return,
);
const result = await queryToTypeDeclarations(
parsedQuery(mode, queryString),
typeSource,
types,
{ ...partialConfig, optionalNullParams: false } as ParsedConfig,
);
const expectedTypes = `import { PreparedQuery } from '@pgtyped/runtime';

export type PayloadType = 'dynamite' | 'message';

export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };\n`;

expect(types.declaration('file.ts')).toEqual(expectedTypes);
const expected = `/** 'GetNotifications' parameters type */
export interface IGetNotificationsParams {
userId: string | null | void;
}

/** 'GetNotifications' return type */
export interface IGetNotificationsResult {
payload: Json;
type: PayloadType;
}

/** 'GetNotifications' query type */
export interface IGetNotificationsQuery {
params: IGetNotificationsParams;
@@ -390,7 +471,7 @@ export interface IGetNotificationsQuery {
parsedQuery(mode, queryString),
typeSource,
types,
{ camelCaseColumnNames: true, hungarianNotation: true } as ParsedConfig,
{ ...partialConfig, camelCaseColumnNames: true } as ParsedConfig,
);
const expectedTypes = `import { PreparedQuery } from '@pgtyped/runtime';

4 changes: 3 additions & 1 deletion packages/cli/src/generator.ts
Original file line number Diff line number Diff line change
@@ -210,7 +210,9 @@ export async function queryToTypeDeclarations(

// Allow optional scalar parameters to be missing from parameters object
const optional =
param.type === ParameterTransform.Scalar && !param.required;
param.type === ParameterTransform.Scalar &&
!param.required &&
config.optionalNullParams;

paramFieldTypes.push({
optional,