Repository: salsita/node-pg-migrate (ts)

Issue:
Support literal without class
I use declarative way to do migration

* object only - can be json and serialialized
* migration can be down and up for one object

This is is real code in my migrations

```ts
const migrations: DeclarativeMigration[] = [
  UserSchema,
  ...createAuditTriggers(UserSchema),
  ...createCommonEntityIndex(UserSchema),
];

export async function up(pgm: MigrationBuilder): Promise<void> {
  await buildMigrations(pgm, migrations);
}

export async function down(pgm: MigrationBuilder): Promise<void> {
  await buildReverseMigrations(pgm, migrations);
}
```

The only thing I need to handle is `PgLiterail` must be created by code,

e.g. 

```js
{
  salt: { type: 'text', default: ":RAW:gen_salt('bf', 8)" },
}
```

For salt column , I want to use literal, I have to define some prefix then replace with PgLiteral

```js
if (typeof v.default === 'string' && v.default.startsWith(LiteralPrefix)) {
    v.default = new PgLiteral(v.default.substring(LiteralPrefix.length));
}
```

Really hope this is handled by node-pg-migrate when processing literal, with some predefined syntax or pre configged replacer or placeholder



Pull request description:
PgLiteral can be serialized
Fixes #604

Patch:
diff --git a/src/index.ts b/src/index.ts
index bad8f5c..45cd5ae 100644
--- a/src/index.ts
+++ b/src/index.ts
@@ -3,6 +3,7 @@ import { Migration } from './migration'
 import { RunnerOption, MigrationBuilder, PgType } from './types'
 import PgLiteral from './operations/PgLiteral'
 import {
+  PgLiteralValue,
   Value,
   Name,
   Type,
@@ -124,6 +125,7 @@ export {
   PgType,
   MigrationBuilder,
   RunnerOption,
+  PgLiteralValue,
   Value,
   Name,
   Type,
diff --git a/src/operations/PgLiteral.ts b/src/operations/PgLiteral.ts
index 078ec5f..5977523 100644
--- a/src/operations/PgLiteral.ts
+++ b/src/operations/PgLiteral.ts
@@ -5,13 +5,12 @@ export default class PgLiteral {
     return new PgLiteral(str)
   }
 
-  private readonly _str: string
+  public readonly literal = true
 
-  constructor(str: string) {
-    this._str = str
-  }
+  // eslint-disable-next-line no-useless-constructor
+  constructor(public readonly value: string) {}
 
   toString(): string {
-    return this._str
+    return this.value
   }
 }
diff --git a/src/operations/generalTypes.ts b/src/operations/generalTypes.ts
index a8baf79..49e33d1 100644
--- a/src/operations/generalTypes.ts
+++ b/src/operations/generalTypes.ts
@@ -2,10 +2,12 @@ import PgLiteral from './PgLiteral'
 
 // eslint-disable-next-line camelcase
 export type LiteralUnion<T extends U, U = string> = T | (U & { zz_IGNORE_ME?: never })
+export type PublicPart<T> = { [K in keyof T]: T[K] }
 
 export type Nullable<T> = { [P in keyof T]: T[P] | null }
 
-export type Value = null | boolean | string | number | PgLiteral | Value[]
+export type PgLiteralValue = PublicPart<PgLiteral>
+export type Value = null | boolean | string | number | PgLiteral | PgLiteralValue | Value[]
 
 export type Type = string | { type: string }
 
diff --git a/src/utils.ts b/src/utils.ts
index d5d0a76..ee6cb43 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -3,7 +3,6 @@ import { ColumnDefinitions, ColumnDefinition } from './operations/tablesTypes'
 import { Name, Type, Value } from './operations/generalTypes'
 import { MigrationOptions, Literal, RunnerOption } from './types'
 import { FunctionParam, FunctionParamType } from './operations/functionsTypes'
-import PgLiteral from './operations/PgLiteral'
 
 const identity = <T>(v: T) => v
 const quote = (str: string) => `"${str}"`
@@ -51,8 +50,8 @@ export const escapeValue = (val: Value): string | number => {
     const arrayStr = val.map(escapeValue).join(',').replace(/ARRAY/g, '')
     return `ARRAY[${arrayStr}]`
   }
-  if (val instanceof PgLiteral) {
-    return val.toString()
+  if (typeof val === 'object' && val.literal) {
+    return val.value
   }
   return ''
 }


Test patch:
diff --git a/test/ts/migrations/test-migration.ts b/test/ts/migrations/test-migration.ts
index e869795..9d46687 100644
--- a/test/ts/migrations/test-migration.ts
+++ b/test/ts/migrations/test-migration.ts
@@ -12,4 +12,14 @@ export async function up(pgm: MigrationBuilder): Promise<void> {
       default: pgm.func('current_timestamp'),
     },
   })
+
+  pgm.createTable('t2', {
+    id: 'id',
+    string: { type: 'text', notNull: true },
+    created: {
+      type: 'timestamp',
+      notNull: true,
+      default: { literal: true, value: 'current_timestamp' },
+    },
+  })
 }
diff --git a/test/utils-test.ts b/test/utils-test.ts
index 0d21856..618bd54 100644
--- a/test/utils-test.ts
+++ b/test/utils-test.ts
@@ -2,6 +2,7 @@ import { expect } from 'chai'
 import { escapeValue, applyType } from '../src/utils'
 import { ColumnDefinitions } from '../src/operations/tablesTypes'
 import PgLiteral from '../src/operations/PgLiteral'
+import { PgLiteralValue } from '../src/operations/generalTypes'
 
 describe('lib/utils', () => {
   describe('.escapeValue', () => {
@@ -43,6 +44,19 @@ describe('lib/utils', () => {
       expect(escapeValue(value)).to.equal('@l|<e')
     })
 
+    it('parse object literal to unescaped string', () => {
+      const value: PgLiteralValue = { literal: true, value: '@l|<e' }
+
+      expect(escapeValue(value)).to.equal('@l|<e')
+    })
+
+    it('PgLiteral serialize to PgLiteralValue', () => {
+      const value = PgLiteral.create('@l|<e')
+      const literalValue = JSON.parse(JSON.stringify(value))
+
+      expect(escapeValue(literalValue)).to.equal('@l|<e')
+    })
+
     it('parse unexpected type to empty string', () => {
       const value = undefined