Skip to content

Commit 5256b2e

Browse files
committed
feat!: Allow non-optional generation of params with optionalNullParams
This feature allows specifying whether nullable params should be generated as optional via the config. The default behaviour stays the same, so this feature is backwards compatible.
1 parent 6a2ae96 commit 5256b2e

File tree

4 files changed

+85
-1
lines changed

4 files changed

+85
-1
lines changed

docs-new/docs/cli.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,7 @@ For a full list of options, see the [Configuration file format](#configuration-f
6060
"srcDir": "./src/", // Directory to scan or watch for query files
6161
"failOnError": false, // Whether to fail on a file processing error and abort generation (can be omitted - default is false)
6262
"camelCaseColumnNames": false, // convert to camelCase column names of result interface
63+
"optionalNullParams": true, // Whether nullable parameters are made optional
6364
"dbUrl": "postgres://user:password@host/database", // DB URL (optional - will be merged with db if provided)
6465
"db": {
6566
"dbName": "testdb", // DB name
@@ -92,6 +93,7 @@ Configuration file can be also be written in CommonJS format and default exporte
9293
| `failOnError?` | `boolean` | Whether to fail on a file processing error and abort generation. **Default:** `false` |
9394
| `dbUrl?` | `string` | A connection string to the database. Example: `postgres://user:password@host/database`. Overrides (merged) with `db` config. |
9495
| `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`_. |
96+
| `optionalNullParams?` | `boolean` | Whether nullable parameters are automatically marked as optional. **Default:** `true` |
9597
| `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`._ |
9698
| `maxWorkerThreads` | `number` | The maximum number of worker threads to use for type generation. **The default is based on the number of available CPUs.** |
9799

packages/cli/src/config.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,7 @@ const configParser = t.type({
5757
failOnError: t.union([t.boolean, t.undefined]),
5858
camelCaseColumnNames: t.union([t.boolean, t.undefined]),
5959
hungarianNotation: t.union([t.boolean, t.undefined]),
60+
optionalNullParams: t.union([t.boolean, t.undefined]),
6061
dbUrl: t.union([t.string, t.undefined]),
6162
db: t.union([
6263
t.type({
@@ -99,6 +100,7 @@ export interface ParsedConfig {
99100
failOnError: boolean;
100101
camelCaseColumnNames: boolean;
101102
hungarianNotation: boolean;
103+
optionalNullParams: boolean;
102104
transforms: IConfig['transforms'];
103105
srcDir: IConfig['srcDir'];
104106
typesOverrides: Record<string, Partial<TypeDefinition>>;
@@ -198,6 +200,7 @@ export function parseConfig(
198200
failOnError,
199201
camelCaseColumnNames,
200202
hungarianNotation,
203+
optionalNullParams,
201204
typesOverrides,
202205
} = configObject as IConfig;
203206

@@ -242,6 +245,7 @@ export function parseConfig(
242245
failOnError: failOnError ?? false,
243246
camelCaseColumnNames: camelCaseColumnNames ?? false,
244247
hungarianNotation: hungarianNotation ?? true,
248+
optionalNullParams: optionalNullParams ?? true,
245249
typesOverrides: parsedTypesOverrides,
246250
maxWorkerThreads,
247251
};

packages/cli/src/generator.test.ts

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -331,6 +331,82 @@ export interface IGetNotificationsResult {
331331
typeCamelCase: PayloadType;
332332
}
333333
334+
/** 'GetNotifications' query type */
335+
export interface IGetNotificationsQuery {
336+
params: IGetNotificationsParams;
337+
result: IGetNotificationsResult;
338+
}\n\n`;
339+
expect(result).toEqual(expected);
340+
});
341+
342+
test(`Null parameters generation as required (${mode})`, async () => {
343+
const queryStringSQL = `
344+
/* @name GetNotifications */
345+
SELECT payload, type FROM notifications WHERE id = :userId;
346+
`;
347+
const queryStringTS = `
348+
const getNotifications = sql\`SELECT payload, type FROM notifications WHERE id = $userId\`;
349+
`;
350+
const queryString =
351+
mode === ProcessingMode.SQL ? queryStringSQL : queryStringTS;
352+
const mockTypes: IQueryTypes = {
353+
returnTypes: [
354+
{
355+
returnName: 'payload',
356+
columnName: 'payload',
357+
type: 'json',
358+
nullable: false,
359+
},
360+
{
361+
returnName: 'type',
362+
columnName: 'type',
363+
type: { name: 'PayloadType', enumValues: ['message', 'dynamite'] },
364+
nullable: false,
365+
},
366+
],
367+
paramMetadata: {
368+
params: ['uuid'],
369+
mapping: [
370+
{
371+
name: 'userId',
372+
type: ParameterTransform.Scalar,
373+
required: false,
374+
assignedIndex: 1,
375+
},
376+
],
377+
},
378+
};
379+
const typeSource = async (_: any) => mockTypes;
380+
const types = new TypeAllocator(TypeMapping());
381+
// Test out imports
382+
types.use(
383+
{ name: 'PreparedQuery', from: '@pgtyped/runtime' },
384+
TypeScope.Return,
385+
);
386+
const result = await queryToTypeDeclarations(
387+
parsedQuery(mode, queryString),
388+
typeSource,
389+
types,
390+
{ optionalNullParams: false, hungarianNotation: true } as ParsedConfig,
391+
);
392+
const expectedTypes = `import { PreparedQuery } from '@pgtyped/runtime';
393+
394+
export type PayloadType = 'dynamite' | 'message';
395+
396+
export type Json = null | boolean | number | string | Json[] | { [key: string]: Json };\n`;
397+
398+
expect(types.declaration('file.ts')).toEqual(expectedTypes);
399+
const expected = `/** 'GetNotifications' parameters type */
400+
export interface IGetNotificationsParams {
401+
userId: string | null | void;
402+
}
403+
404+
/** 'GetNotifications' return type */
405+
export interface IGetNotificationsResult {
406+
payload: Json;
407+
type: PayloadType;
408+
}
409+
334410
/** 'GetNotifications' query type */
335411
export interface IGetNotificationsQuery {
336412
params: IGetNotificationsParams;

packages/cli/src/generator.ts

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,9 @@ export async function queryToTypeDeclarations(
192192

193193
// Allow optional scalar parameters to be missing from parameters object
194194
const optional =
195-
param.type === ParameterTransform.Scalar && !param.required;
195+
param.type === ParameterTransform.Scalar &&
196+
!param.required &&
197+
config.optionalNullParams;
196198

197199
paramFieldTypes.push({
198200
optional,

0 commit comments

Comments
 (0)