Skip to content

Commit

Permalink
feat: move extension creation to core
Browse files Browse the repository at this point in the history
  • Loading branch information
danstarns committed Jan 17, 2025
1 parent 08b9608 commit 0feaaba
Show file tree
Hide file tree
Showing 9 changed files with 216 additions and 72 deletions.
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
'use strict';

const { TimescaleDB } = require('@timescaledb/core');

const extension = TimescaleDB.createExtension();

/** @type {import('sequelize-cli').Migration} */
module.exports = {
async up(queryInterface) {
await queryInterface.sequelize.query('CREATE EXTENSION IF NOT EXISTS timescaledb;');
const sql = extension.up().build();

await queryInterface.sequelize.query(sql);
},

async down(queryInterface) {
await queryInterface.sequelize.query('DROP EXTENSION IF EXISTS timescaledb;');
const sql = extension.down().build();

await queryInterface.sequelize.query(sql);
},
};
4 changes: 4 additions & 0 deletions packages/core/src/errors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,3 +3,7 @@ export enum HypertableErrors {
OPTIONS_REQUIRED = 'Hypertable options are required',
INVALID_OPTIONS = 'Invalid hypertable options',
}

export enum ExtensionErrors {
INVALID_OPTIONS = 'Invalid extension options',
}
57 changes: 57 additions & 0 deletions packages/core/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import { CreateExtensionOptions, CreateExtensionOptionsSchema } from '@timescaledb/schemas';
import { ExtensionErrors } from './errors';

class ExtensionUpBuilder {
private options?: CreateExtensionOptions;
private statements: string[] = [];

constructor(options?: CreateExtensionOptions) {
this.options = options;
}

public build(): string {
const stmt = `CREATE EXTENSION IF NOT EXISTS timescaledb${this?.options?.should_cascade ? ' CASCADE' : ''};`;
this.statements.push(stmt);

return this.statements.join('\n');
}
}

class ExtensionDownBuilder {
private options?: CreateExtensionOptions;
private statements: string[] = [];

constructor(options?: CreateExtensionOptions) {
this.options = options;
}

public build(): string {
const stmt = `DROP EXTENSION IF EXISTS timescaledb${this?.options?.should_cascade ? ' CASCADE' : ''};`;
this.statements.push(stmt);

return this.statements.join('\n');
}
}

export class Extension {
private options?: CreateExtensionOptions;

constructor(options?: CreateExtensionOptions) {
if (options) {
try {
this.options = CreateExtensionOptionsSchema.parse(options);
} catch (error) {
const e = error as Error;
throw new Error(ExtensionErrors.INVALID_OPTIONS + ' ' + e.message);
}
}
}

public up(): ExtensionUpBuilder {
return new ExtensionUpBuilder(this.options);
}

public down(): ExtensionDownBuilder {
return new ExtensionDownBuilder(this.options);
}
}
9 changes: 8 additions & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { CreateHypertableOptions } from '@timescaledb/schemas';
import { CreateExtensionOptions, CreateHypertableOptions } from '@timescaledb/schemas';
import { Hypertable } from './hypertable';
import { Extension } from './extension';

export const name = '@timescaledb/core';

Expand All @@ -11,6 +12,12 @@ export class TimescaleDB {

return hypertable;
}

public static createExtension(options?: CreateExtensionOptions): Extension {
const extension = new Extension(options);

return extension;
}
}

export * from './errors';
9 changes: 9 additions & 0 deletions packages/core/tests/__snapshots__/extension.test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Extension down should drop an extension 1`] = `"DROP EXTENSION IF EXISTS timescaledb;"`;

exports[`Extension down should drop an extension with cascade 1`] = `"DROP EXTENSION IF EXISTS timescaledb CASCADE;"`;

exports[`Extension up should create an extension 1`] = `"CREATE EXTENSION IF NOT EXISTS timescaledb;"`;

exports[`Extension up should create an extension with cascade 1`] = `"CREATE EXTENSION IF NOT EXISTS timescaledb CASCADE;"`;
49 changes: 49 additions & 0 deletions packages/core/tests/extension.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { describe, it } from '@jest/globals';
import { TimescaleDB, ExtensionErrors } from '../src';

describe('Extension', () => {
it('should fail when creating an extension without invalid options', () => {
expect(() => {
TimescaleDB.createExtension({
// @ts-expect-error
invalidOption: 'invalid',
});
}).toThrow(ExtensionErrors.INVALID_OPTIONS);
});

describe('up', () => {
it('should create an extension', () => {
const extension = TimescaleDB.createExtension();
const sql = extension.up().build();

expect(sql).toMatchSnapshot();
});

it('should create an extension with cascade', () => {
const extension = TimescaleDB.createExtension({
should_cascade: true,
});
const sql = extension.up().build();

expect(sql).toMatchSnapshot();
});
});

describe('down', () => {
it('should drop an extension', () => {
const extension = TimescaleDB.createExtension();
const sql = extension.down().build();

expect(sql).toMatchSnapshot();
});

it('should drop an extension with cascade', () => {
const extension = TimescaleDB.createExtension({
should_cascade: true,
});
const sql = extension.down().build();

expect(sql).toMatchSnapshot();
});
});
});
9 changes: 9 additions & 0 deletions packages/schemas/src/extension.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { z } from 'zod';

export const CreateExtensionOptionsSchema = z
.object({
should_cascade: z.boolean().optional(),
version: z.string().optional(),
})
.strict();
export type CreateExtensionOptions = z.infer<typeof CreateExtensionOptionsSchema>;
1 change: 1 addition & 0 deletions packages/schemas/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@ export * from './compression';
export * from './time-range';
export * from './hypertable';
export * from './by-range';
export * from './extension';
Loading

0 comments on commit 0feaaba

Please sign in to comment.