Skip to content

Commit

Permalink
feat: support jsdoc tags
Browse files Browse the repository at this point in the history
  • Loading branch information
ferm10n committed Jun 13, 2024
1 parent 0209f26 commit b4a1631
Show file tree
Hide file tree
Showing 7 changed files with 69 additions and 28 deletions.
46 changes: 29 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,18 +32,18 @@ See all examples in the [examples folder](/examples). Use `yarn examples` to app

## Deploy on custom schema

To generate a function to be deployed on a schema different than the default one (usually: public) decorate the function with a `//@plv8ify-schema-name <schemaname>` comment
To generate a function to be deployed on a schema different than the default one (usually: public) decorate the function with a `/** @plv8ify_schema_name <schemaname> */` jsdoc tag

```
//@plv8ify-schema-name testschema
```ts
/** @plv8ify_schema_name testschema */
export function hello() {
return 'world'
}
```

will generate

```
```sql
DROP FUNCTION IF EXISTS testschema.plv8ify_hello();
CREATE OR REPLACE FUNCTION testschema.plv8ify_hello() RETURNS text AS $plv8ify$
// input.ts
Expand All @@ -59,18 +59,18 @@ $plv8ify$ LANGUAGE plv8 IMMUTABLE STRICT;

## Trigger functions

To write a trigger function, decorate the function with the `//@plv8ify-trigger` comment, and have the function return a `testRow` type where `testRow` defines the type of the row for the trigger. You can also add a NEW parameter for insert and update triggers, and OLD for update and delete triggers.
To write a trigger function, decorate the function with a `/** @plv8ify_trigger */` jsdoc tag, and have the function return a `testRow` type where `testRow` defines the type of the row for the trigger. You can also add a NEW parameter for insert and update triggers, and OLD for update and delete triggers.
(Tip: you can add @types/pg and @types/plv8-internals to get all standard postgres types/defines and plv8 specific functions recognized by the type checker)

```
```ts
type Row = {
// Either JS or plv8 types can be used here
id: number
event_name: string
event_date_time: Date
}

//@plv8ify-trigger
/** @plv8ify_trigger */
export function test(NEW: Row, OLD: Row): Row {
plv8.elog(NOTICE, 'NEW = ', JSON.stringify(NEW));
plv8.elog(NOTICE, 'OLD = ', JSON.stringify(OLD));
Expand Down Expand Up @@ -112,9 +112,10 @@ typeMap = {
The custom types will be merged with the default ones at runtime and will allow using either internal postgres type or custom defined types

Example:

types.ts

```
```ts
typeMap = {
test_type: 'test_type',
'test_type[]': 'test_type[]',
Expand All @@ -123,7 +124,7 @@ typeMap = {

input.ts

```
```ts
interface test_type {
name: string
age: number
Expand All @@ -146,7 +147,7 @@ plv8ify generate input.ts --types-config-file types.ts

will generate this function:

```
```sql
DROP FUNCTION IF EXISTS plv8ify_hello(test test_type[]);
CREATE OR REPLACE FUNCTION plv8ify_hello(test test_type[]) RETURNS JSONB AS $plv8ify$
// input.ts
Expand All @@ -163,17 +164,18 @@ return hello(test)
$plv8ify$ LANGUAGE plv8 IMMUTABLE STRICT;
```

Additionally, you can decorate your functions with the `//@plv8ify-return <SQL TYPE>` comment, which allows setting the return type per function, and overrides the configuration in `typeMap`

Similarly, parameter types can be set per function and per parameter with the `//@plv8ify-param <PARAM NAME> <SQL TYPE>` comment
Additionally, you can decorate your functions with special jsdoc tags to control the type mapping of params and return types on a per function basis:

Example:

input.ts

```ts
//@plv8ify-param first_name varchar(255)
//@plv8ify-param last_name text
//@plv8ify-return char(255)
/**
* @plv8ify_param {varchar(255)} first_name
* @plv8ify_param {text} last_name
* @plv8ify_return {char(255)}
*/
export function howdy(first_name: string, last_name: string): string {
return `Howdy ${first_name} ${last_name}`
}
Expand Down Expand Up @@ -207,6 +209,16 @@ return howdy(first_name,last_name)
$plv8ify$ LANGUAGE plv8 IMMUTABLE STRICT;
```

### JSDoc Tag Reference

| Behavior | JSDoc Tag | Example |
| -------- | --------- | ------------- |
| set the volatility of the generated Postgres function | `/** @plv8ify_volatility <STABLE,IMMUTABLE,VOLATILE> */` | `/** @plv8ify_volatility STABLE */` |
| set the schema of the generated Postgres function | `/** @plv8ify_schema_name <schemaname> */` | `/** @plv8ify_schema_name my_schema */` |
| set the TS->SQL type mapping for a parameter | `/** @plv8ify_param {<my_sql_type>} <my_param> */` | `/** @plv8ify_param {timestamptz} ts */` |
| set the TS->SQL type mapping for the return type | `/** @plv8ify_returns {<SQL TYPE>} */` | `/** @plv8ify_returns {setof my_table} */` |
| designate the function is a TRIGGER | `/** @plv8ify_trigger */` | `/** @plv8ify_trigger */` |

## CLI Usage

### Version
Expand All @@ -231,7 +243,7 @@ Generate PLV8 functions for an input typescript file
| --pg-function-delimiter | String | Specify a delimiter for the generated Postgres function | `$plv8ify$` |
| --fallback-type | String | Specify a fallback type when `plv8ify` fails to map a detected Typescript type to a Postges type | `JSONB` |
| --mode | 'inline', 'bundle' or 'start_proc' | 'inline' will bundle the library in each function, both 'bundle' and 'start_proc' creates a `{prefix}_init` function that loads the library. 'bundle' adds a check to each function to call 'init' if required, whereas 'start_proc' is designed to be used with plv8.start_proc | `inline` |
| --volatility | 'IMMUTABLE' or 'STABLE' or 'VOLATILE' | Change the volatility of all the generated functions. To change volatility of a specific function use the comment format `//@plv8ify-volatility-STABLE` in the input typescript file (see `examples/turf-js/input.ts`). Note that for now only single-line comment syntax is supported. | `IMMUTABLE` |
| --volatility | 'IMMUTABLE' or 'STABLE' or 'VOLATILE' | Change the volatility of all the generated functions. To change volatility of a specific function use the jsdoc `/** @plv8ify_volatility STABLE` in the input typescript file (see `examples/turf-js/input.ts`). | `IMMUTABLE` |
| --types-config-file | String | Specify a custom types config file | types.ts |

### Deploy
Expand Down
8 changes: 5 additions & 3 deletions examples/hello-custom-type/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,11 @@ export function hello(test: test_type[]): test_type {
}
}

//@plv8ify-param first_name varchar(255)
//@plv8ify-param last_name text
//@plv8ify-return char(255)
/**
* @plv8ify_param {varchar(255)} first_name
* @plv8ify_param {text} last_name
* @plv8ify_return {char(255)}
*/
export function howdy(first_name: string, last_name: string): string {
return `Howdy ${first_name} ${last_name}`
}
2 changes: 1 addition & 1 deletion examples/trigger/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ type Row = {
event_date_time: Date
}

//@plv8ify-trigger
/** @plv8ify_trigger */
export function test(NEW: Row, OLD: Row): Row {
plv8.elog(NOTICE, 'NEW = ', JSON.stringify(NEW))
plv8.elog(NOTICE, 'OLD = ', JSON.stringify(OLD))
Expand Down
2 changes: 1 addition & 1 deletion examples/turf-js/input.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ export function point(lat: number, long: number) {
return pt
}

//@plv8ify-volatility-STABLE
/** @plv8ify_volatility STABLE */
export function stablePoint(lat: number, long: number) {
const pt = turfPoint([lat, long])
return pt
Expand Down
30 changes: 25 additions & 5 deletions src/impl/PLV8ifyCLI.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,7 @@ export class PLV8ifyCLI implements PLV8ify {
isExported: false,
parameters: [],
returnType: 'void',
jsdocTags: [],
}

if (mode === 'bundle') {
Expand Down Expand Up @@ -211,6 +212,7 @@ export class PLV8ifyCLI implements PLV8ify {
isExported: false,
parameters: [],
returnType: 'void',
jsdocTags: [],
}
const startProcSQLScript = this.getStartProcSQLScript({ scopePrefix })
const startProcFileName = this.getFileName(
Expand All @@ -228,10 +230,9 @@ export class PLV8ifyCLI implements PLV8ify {
}

/**
* handles all the processing for magic comments
* handles all the processing for jsdoc / magic comments
*/
private getFnSqlConfig (fn: TSFunction): FnSqlConfig {
// debugger
const config: FnSqlConfig = {
// defaults
paramTypeMapping: {},
Expand All @@ -246,6 +247,7 @@ export class PLV8ifyCLI implements PLV8ify {
config.paramTypeMapping[param.name] = this.getTypeFromMap(param.type) || null
}

// process magic comments (legacy format)
for (const comment of fn.comments) {
const volatilityMatch = comment.match(/^\/\/@plv8ify-volatility-(STABLE|IMMUTABLE|VOLATILE)/umi)
if (volatilityMatch) config.volatility = volatilityMatch[1] as Volatility
Expand All @@ -264,11 +266,29 @@ export class PLV8ifyCLI implements PLV8ify {
if (comment.match(/^\/\/@plv8ify-trigger/umi)) config.trigger = true
}

if (config.trigger) {
// triggers don't have return types
config.sqlReturnType = 'TRIGGER'
// process jsdoc tags
for (const tag of fn.jsdocTags) {
if (tag.name === 'plv8ify_volatility' && [ 'STABLE', 'IMMUTABLE', 'VOLATILE' ].includes(tag.commentText.toUpperCase())) {
config.volatility = tag.commentText as Volatility
}

if (tag.name === 'plv8ify_schema_name') config.customSchema = tag.commentText

// expected format: `/** @plv8ify_param {sqlParamType} paramName */`
const paramMatch = tag.commentText.match(/^\{(.+)\} ([\s\S]+)/umi) // return type should be in curly braces, similar to jsdoc @return
if (tag.name === 'plv8ify_param' && paramMatch) config.paramTypeMapping[paramMatch[2]] = paramMatch[1]

// expected format: `/** @plv8ify_return {sqlType} */`
// expected format: `/** @plv8ify_returns {sqlType} */`
const returnMatch = tag.commentText.match(/^\{(.+)\}/umi) // param type should be in curly braces, similar to jsdoc @param
if ([ 'plv8ify_return', 'plv8ify_returns' ].includes(tag.name) && returnMatch) config.sqlReturnType = returnMatch[1]

if (tag.name === 'plv8ify_trigger') config.trigger = true
}

// triggers don't have return types
if (config.trigger) config.sqlReturnType = 'TRIGGER'

return config;
}

Expand Down
8 changes: 7 additions & 1 deletion src/impl/TsMorph.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { TSCompiler } from 'src/interfaces/TSCompiler.js'
import { TSCompiler, TSFunction } from 'src/interfaces/TSCompiler.js'
import { FunctionDeclaration, Project, SourceFile } from 'ts-morph'

export class TsMorph implements TSCompiler {
Expand Down Expand Up @@ -28,6 +28,11 @@ export class TsMorph implements TSCompiler {
return comments
}

private getFunctionJsdocTags(fn: FunctionDeclaration): TSFunction['jsdocTags'] {
const jsdocTags = fn.getJsDocs().flatMap((jsdoc) => jsdoc.getTags())
return jsdocTags.map(tag => ({ name: tag.getTagName(), commentText: tag.getCommentText() || '' }))
}

getFunctions() {
const fns = this.sourceFile.getFunctions()
return fns.map((fn) => {
Expand All @@ -37,6 +42,7 @@ export class TsMorph implements TSCompiler {
parameters: this.getFunctionParameters(fn),
comments: this.getFunctionComments(fn),
returnType: this.getFunctionReturnType(fn),
jsdocTags: this.getFunctionJsdocTags(fn),
}
})
}
Expand Down
1 change: 1 addition & 0 deletions src/interfaces/TSCompiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export interface TSFunction {
parameters: TSFunctionParameter[]
comments: string[]
returnType: string
jsdocTags: { name: string, commentText: string }[]
}

export interface TSCompiler {
Expand Down

0 comments on commit b4a1631

Please sign in to comment.