diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..23fd35f0e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,3 @@ +{ + "editor.formatOnSave": true +} \ No newline at end of file diff --git a/packages/hasura/.gitignore b/packages/hasura/.gitignore new file mode 100644 index 000000000..00d05a46b --- /dev/null +++ b/packages/hasura/.gitignore @@ -0,0 +1,7 @@ +test/__fixtures__/hasura/v2/metadata/tables.yaml +test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml + +test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml +test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml +test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml +test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml \ No newline at end of file diff --git a/packages/hasura/package.json b/packages/hasura/package.json index be0b4f6fa..bc271b37f 100644 --- a/packages/hasura/package.json +++ b/packages/hasura/package.json @@ -40,7 +40,8 @@ "@golevelup/nestjs-discovery": "^2.3.1", "@golevelup/nestjs-modules": "^0.4.2", "@hasura/metadata": "^1.0.2", - "js-yaml": "^3.14.1" + "js-yaml": "^4.1.0", + "zod": "^3.3.4" }, "jest": { "moduleFileExtensions": [ @@ -58,6 +59,7 @@ }, "gitHead": "6f97aab8ce9d65dc074750a3ee467ec5ff3b9908", "devDependencies": { - "@types/js-yaml": "^3.12.5" + "@types/js-yaml": "^3.12.5", + "ts-toolbelt": "^9.6.0" } } diff --git a/packages/hasura/src/hasura.interfaces.ts b/packages/hasura/src/hasura.interfaces.ts index 54949c459..53abf21c6 100644 --- a/packages/hasura/src/hasura.interfaces.ts +++ b/packages/hasura/src/hasura.interfaces.ts @@ -77,6 +77,10 @@ export interface ScheduledEventRetryConfig extends EventRetryConfig { } export interface TrackedHasuraEventHandlerConfig { + /** + * Only necessary to provide this value if working with Metadata V3. Defaults to 'default' + */ + databaseName?: string; schema?: string; tableName: string; triggerName: string; @@ -134,6 +138,11 @@ export interface HasuraModuleConfig { * amount of boilerplate */ managedMetaDataConfig?: { + /** + * The version of hasura metadata being targeted + */ + metadataVersion?: 'v2' | 'v3'; + /** * The ENV key in which Hasura will store the secret header value used to validate event payloads */ diff --git a/packages/hasura/src/hasura.metadata.ts b/packages/hasura/src/hasura.metadata.ts index 3320a08b6..48ec3d15c 100644 --- a/packages/hasura/src/hasura.metadata.ts +++ b/packages/hasura/src/hasura.metadata.ts @@ -5,18 +5,19 @@ import { TrackedHasuraEventHandlerConfig, TrackedHasuraScheduledEventHandlerConfig, } from './hasura.interfaces'; -import { safeLoad, safeDump } from 'js-yaml'; +import { load, dump } from 'js-yaml'; import { readFileSync, writeFileSync } from 'fs'; import { orderBy } from 'lodash'; import { TableEntry, - EventTriggerDefinition, - Columns, CronTrigger, } from './hasura-metadata-dist/HasuraMetadataV2'; +import { mergeEventHandlerConfig } from './metadata/event-triggers'; const utf8 = 'utf-8'; +const MISSING_META_CONFIG = 'No configuration for meta available'; + const defaultHasuraRetryConfig: ScheduledEventRetryConfig = { intervalInSeconds: 10, numRetries: 3, @@ -24,49 +25,20 @@ const defaultHasuraRetryConfig: ScheduledEventRetryConfig = { toleranceSeconds: 21600, }; -const convertEventTriggerDefinition = ( - configDef: TrackedHasuraEventHandlerConfig['definition'] -): EventTriggerDefinition => { - if (configDef.type === 'insert') { - return { - enable_manual: false, - insert: { - columns: Columns.Empty, - }, - }; - } - - if (configDef.type === 'delete') { - return { - enable_manual: false, - delete: { - columns: Columns.Empty, - }, - }; - } - - return { - enable_manual: false, - update: { - columns: configDef.columns ?? Columns.Empty, - }, - }; -}; - export const isTrackedHasuraEventHandlerConfig = ( eventHandlerConfig: HasuraEventHandlerConfig | TrackedHasuraEventHandlerConfig ): eventHandlerConfig is TrackedHasuraEventHandlerConfig => { return 'definition' in eventHandlerConfig; }; -export const updateEventTriggerMeta = ( +const updateEventTriggerMetaV2 = ( moduleConfig: HasuraModuleConfig, eventHandlerConfigs: TrackedHasuraEventHandlerConfig[] ) => { const { managedMetaDataConfig } = moduleConfig; if (!managedMetaDataConfig) { - throw new Error('No configuration for meta available'); + throw new Error(MISSING_META_CONFIG); } const defaultRetryConfig = @@ -75,16 +47,11 @@ export const updateEventTriggerMeta = ( const tablesYamlPath = `${managedMetaDataConfig.dirPath}/tables.yaml`; const tablesMeta = readFileSync(tablesYamlPath, utf8); - const tableEntries = safeLoad(tablesMeta) as TableEntry[]; + const tableEntries = load(tablesMeta) as TableEntry[]; orderBy(eventHandlerConfigs, (x) => x.triggerName).forEach((config) => { - const { - schema = 'public', - tableName, - triggerName, - definition, - retryConfig = defaultRetryConfig, - } = config; + const { schema = 'public', tableName } = config; + const matchingTable = tableEntries.find( (x) => x.table.schema === schema && x.table.name === tableName ); @@ -95,36 +62,71 @@ export const updateEventTriggerMeta = ( ); } - const { intervalInSeconds, numRetries, timeoutInSeconds } = retryConfig; - const eventTriggers = (matchingTable.event_triggers ?? []).filter( - (x) => x.name !== triggerName + matchingTable.event_triggers = mergeEventHandlerConfig( + config, + moduleConfig, + defaultRetryConfig, + matchingTable ); - - matchingTable.event_triggers = [ - ...eventTriggers, - { - name: triggerName, - definition: convertEventTriggerDefinition(definition), - retry_conf: { - num_retries: numRetries, - interval_sec: intervalInSeconds, - timeout_sec: timeoutInSeconds, - }, - webhook_from_env: managedMetaDataConfig.nestEndpointEnvName, - headers: [ - { - name: moduleConfig.webhookConfig.secretHeader, - value_from_env: managedMetaDataConfig.secretHeaderEnvName, - }, - ], - }, - ]; }); - const yamlString = safeDump(tableEntries); + const yamlString = dump(tableEntries); writeFileSync(tablesYamlPath, yamlString, utf8); }; +const updateEventTriggerMetaV3 = ( + moduleConfig: HasuraModuleConfig, + eventHandlerConfigs: TrackedHasuraEventHandlerConfig[] +) => { + const { managedMetaDataConfig } = moduleConfig; + + if (!managedMetaDataConfig) { + throw new Error(MISSING_META_CONFIG); + } + + const defaultRetryConfig = + managedMetaDataConfig.defaultEventRetryConfig ?? defaultHasuraRetryConfig; + + eventHandlerConfigs.forEach((config) => { + const { schema = 'public', databaseName = 'default', tableName } = config; + + const tableYamlPath = `${managedMetaDataConfig.dirPath}/databases/${databaseName}/tables/${schema}_${tableName}.yaml`; + const tableMeta = readFileSync(tableYamlPath, utf8); + const tableEntry = load(tableMeta) as TableEntry; + + tableEntry.event_triggers = mergeEventHandlerConfig( + config, + moduleConfig, + defaultRetryConfig, + tableEntry + ); + + const yamlString = dump(tableEntry); + writeFileSync(tableYamlPath, yamlString, utf8); + }); +}; + +export const updateEventTriggerMeta = ( + moduleConfig: HasuraModuleConfig, + eventHandlerConfigs: TrackedHasuraEventHandlerConfig[] +) => { + const { managedMetaDataConfig } = moduleConfig; + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const { metadataVersion = 'v2' } = moduleConfig.managedMetaDataConfig!; + + if (!managedMetaDataConfig) { + throw new Error(MISSING_META_CONFIG); + } + + if (metadataVersion === 'v2') { + updateEventTriggerMetaV2(moduleConfig, eventHandlerConfigs); + } else if (metadataVersion === 'v3') { + updateEventTriggerMetaV3(moduleConfig, eventHandlerConfigs); + } else { + throw new Error(`Invalid Hasura metadata version: ${metadataVersion}`); + } +}; + export const updateScheduledEventTriggerMeta = ( moduleConfig: HasuraModuleConfig, scheduledEventHandlerConfigs: TrackedHasuraScheduledEventHandlerConfig[] @@ -132,13 +134,13 @@ export const updateScheduledEventTriggerMeta = ( const { managedMetaDataConfig } = moduleConfig; if (!managedMetaDataConfig) { - throw new Error('No configuration for meta available'); + throw new Error(MISSING_META_CONFIG); } const cronTriggersYamlPath = `${managedMetaDataConfig.dirPath}/cron_triggers.yaml`; const cronTriggersMeta = readFileSync(cronTriggersYamlPath, utf8); - const cronEntries = (safeLoad(cronTriggersMeta) ?? []) as CronTrigger[]; + const cronEntries = (load(cronTriggersMeta) ?? []) as CronTrigger[]; const managedCronTriggerNames = scheduledEventHandlerConfigs.map( (x) => x.name @@ -183,6 +185,6 @@ export const updateScheduledEventTriggerMeta = ( ...managedCronTriggers, ]; - const yamlString = safeDump(newCronEntries); + const yamlString = dump(newCronEntries); writeFileSync(cronTriggersYamlPath, yamlString, utf8); }; diff --git a/packages/hasura/src/metadata/event-triggers.ts b/packages/hasura/src/metadata/event-triggers.ts new file mode 100644 index 000000000..64a639613 --- /dev/null +++ b/packages/hasura/src/metadata/event-triggers.ts @@ -0,0 +1,76 @@ +import { + Columns, + EventTriggerDefinition, + TableEntry, +} from '../hasura-metadata-dist/HasuraMetadataV2'; +import { + HasuraModuleConfig, + ScheduledEventRetryConfig, + TrackedHasuraEventHandlerConfig, +} from '../hasura.interfaces'; + +const convertEventTriggerDefinition = ( + configDef: TrackedHasuraEventHandlerConfig['definition'] +): EventTriggerDefinition => { + if (configDef.type === 'insert') { + return { + enable_manual: false, + insert: { + columns: Columns.Empty, + }, + }; + } + + if (configDef.type === 'delete') { + return { + enable_manual: false, + delete: { + columns: Columns.Empty, + }, + }; + } + + return { + enable_manual: false, + update: { + columns: configDef.columns ?? Columns.Empty, + }, + }; +}; + +export const mergeEventHandlerConfig = ( + config: TrackedHasuraEventHandlerConfig, + moduleConfig: HasuraModuleConfig, + defaultRetryConfig: ScheduledEventRetryConfig, + existingTableEntry: TableEntry +): TableEntry['event_triggers'] => { + const { managedMetaDataConfig } = moduleConfig; + + const { triggerName, definition, retryConfig = defaultRetryConfig } = config; + + const eventTriggers = (existingTableEntry.event_triggers ?? []).filter( + (x) => x.name !== triggerName + ); + + const { intervalInSeconds, numRetries, timeoutInSeconds } = retryConfig; + + return [ + ...eventTriggers, + { + name: triggerName, + definition: convertEventTriggerDefinition(definition), + retry_conf: { + num_retries: numRetries, + interval_sec: intervalInSeconds, + timeout_sec: timeoutInSeconds, + }, + webhook_from_env: managedMetaDataConfig?.nestEndpointEnvName, + headers: [ + { + name: moduleConfig.webhookConfig.secretHeader, + value_from_env: managedMetaDataConfig?.secretHeaderEnvName, + }, + ], + }, + ]; +}; diff --git a/packages/hasura/src/metadata/scheduled-triggers.ts b/packages/hasura/src/metadata/scheduled-triggers.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/hasura/src/tests/hasura.module.spec.ts b/packages/hasura/src/tests/hasura.event-handling.spec.ts similarity index 100% rename from packages/hasura/src/tests/hasura.module.spec.ts rename to packages/hasura/src/tests/hasura.event-handling.spec.ts diff --git a/packages/hasura/src/tests/hasura.metadata.spec-utils.ts b/packages/hasura/src/tests/hasura.metadata.spec-utils.ts new file mode 100644 index 000000000..b7899d8cf --- /dev/null +++ b/packages/hasura/src/tests/hasura.metadata.spec-utils.ts @@ -0,0 +1,100 @@ +import { Injectable } from '@nestjs/common'; +import { + TrackedHasuraEventHandler, + TrackedHasuraScheduledEventHandler, +} from '../hasura.decorators'; +import { CommonCronSchedules, HasuraModuleConfig } from '../hasura.interfaces'; +import * as path from 'path'; +import * as fs from 'fs'; + +import { z } from 'zod'; +import { load } from 'js-yaml'; + +export const metadataVersion = z.enum(['v2', 'v3']); + +export const baseConfig = { + webhookConfig: { + secretFactory: 'secret', + secretHeader: 'NESTJS_SECRET_HEADER', + }, + managedMetaDataConfig: { + secretHeaderEnvName: 'NESTJS_WEBHOOK_SECRET_HEADER_VALUE', + nestEndpointEnvName: 'NESTJS_EVENT_WEBHOOK_ENDPOINT', + defaultEventRetryConfig: { + intervalInSeconds: 15, + numRetries: 3, + timeoutInSeconds: 100, + toleranceSeconds: 21600, + }, + }, +}; + +export const yamlFileToJson = (filePath: string) => { + const fileContents = fs.readFileSync(filePath, 'utf-8'); + return load(fileContents); +}; + +export const getVersionedMetadataPathAndConfig = ( + v: string +): [string, HasuraModuleConfig] => { + const version = metadataVersion.parse(v); + const metadataPath = path.join( + __dirname, + `../../test/__fixtures__/hasura/${version}/metadata` + ); + + const { managedMetaDataConfig } = baseConfig; + return [ + metadataPath, + { + ...baseConfig, + managedMetaDataConfig: { + dirPath: metadataPath, + metadataVersion: version, + ...managedMetaDataConfig, + }, + }, + ]; +}; + +export const copyCleanTemplateYamlFile = (yamlPath: string) => { + if (fs.existsSync(yamlPath)) { + fs.unlinkSync(yamlPath); + } + fs.copyFileSync(`${yamlPath}.tmpl`, yamlPath); +}; + +@Injectable() +export class TestEventHandlerService { + @TrackedHasuraEventHandler({ + tableName: 'default_table', + triggerName: 'default_table_event_handler', + definition: { + type: 'insert', + }, + }) + public defaultHandler() { + console.log('default'); + } + + @TrackedHasuraEventHandler({ + databaseName: 'additional', + tableName: 'additional_table', + triggerName: 'additional_table_event_handler', + definition: { + type: 'delete', + }, + }) + public additionalHandler() { + console.log('additional'); + } + + @TrackedHasuraScheduledEventHandler({ + cronSchedule: CommonCronSchedules.EveryMinute, + name: 'scheduled', + payload: { message: 'hello' }, + }) + public scheduled() { + console.log('scheduled'); + } +} diff --git a/packages/hasura/src/tests/hasura.metadata.spec.ts b/packages/hasura/src/tests/hasura.metadata.spec.ts new file mode 100644 index 000000000..5320bd854 --- /dev/null +++ b/packages/hasura/src/tests/hasura.metadata.spec.ts @@ -0,0 +1,130 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { HasuraModule } from '../hasura.module'; +import * as path from 'path'; +import { INestApplication } from '@nestjs/common'; +import { + copyCleanTemplateYamlFile, + getVersionedMetadataPathAndConfig, + TestEventHandlerService, + yamlFileToJson, +} from './hasura.metadata.spec-utils'; + +const TABLES_YAML = 'tables.yaml'; +const CRON_TRIGGERS_YAML = 'cron_triggers.yaml'; + +describe('Hasura Metadata', () => { + beforeAll(() => { + const [v2Path] = getVersionedMetadataPathAndConfig('v2'); + + const [v3Path] = getVersionedMetadataPathAndConfig('v3'); + + const tables = ['default', 'additional']; + const tablePaths = tables.map((x) => + path.join(v3Path, `databases/${x}/tables/public_${x}_table.yaml`) + ); + + const testYamlFilePaths = [ + path.join(v2Path, TABLES_YAML), + path.join(v2Path, CRON_TRIGGERS_YAML), + path.join(v3Path, CRON_TRIGGERS_YAML), + ...tablePaths, + ]; + + testYamlFilePaths.forEach((x) => { + copyCleanTemplateYamlFile(x); + }); + }); + + describe.each([['v2'], ['v3']])('cron triggers %s', (v) => { + let app: INestApplication; + + const [metadataPath, moduleConfig] = getVersionedMetadataPathAndConfig(v); + + const cronTriggersYamlPath = `${metadataPath}/${CRON_TRIGGERS_YAML}`; + + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [HasuraModule.forRoot(HasuraModule, moduleConfig)], + providers: [TestEventHandlerService], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + test('generates correct yaml file', async () => { + expect(yamlFileToJson(cronTriggersYamlPath)).toEqual( + yamlFileToJson(`${cronTriggersYamlPath}.expected`) + ); + }); + }); + + describe('v2 metadata', () => { + const [metadataPath, moduleConfig] = getVersionedMetadataPathAndConfig( + 'v2' + ); + + const tablesFilePath = path.join(metadataPath, TABLES_YAML); + const expectedFilePath = `${tablesFilePath}.expected`; + + let app: INestApplication; + + // eslint-disable-next-line sonarjs/no-identical-functions + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [HasuraModule.forRoot(HasuraModule, moduleConfig)], + providers: [TestEventHandlerService], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + describe('tables', () => { + describe('event handlers', () => { + test('generates the correct metadata', async () => { + expect(yamlFileToJson(tablesFilePath)).toEqual( + yamlFileToJson(expectedFilePath) + ); + }); + }); + }); + }); + + describe('v3 metadata', () => { + let app: INestApplication; + + const [metadataPath, moduleConfig] = getVersionedMetadataPathAndConfig( + 'v3' + ); + + // eslint-disable-next-line sonarjs/no-identical-functions + beforeAll(async () => { + const moduleFixture: TestingModule = await Test.createTestingModule({ + imports: [HasuraModule.forRoot(HasuraModule, moduleConfig)], + providers: [TestEventHandlerService], + }).compile(); + + app = moduleFixture.createNestApplication(); + await app.init(); + }); + + describe('tables', () => { + describe('event handlers', () => { + it.each([['default'], ['additional']])( + 'manages event handler metadata: %s database', + (d) => { + const tablePath = path.join( + metadataPath, + `databases/${d}/tables/public_${d}_table.yaml` + ); + + expect(yamlFileToJson(tablePath)).toEqual( + yamlFileToJson(`${tablePath}.expected`) + ); + } + ); + }); + }); + }); +}); diff --git a/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.expected b/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.expected new file mode 100644 index 000000000..c86fc0f1c --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.expected @@ -0,0 +1,14 @@ +- name: scheduled + webhook: '{{NESTJS_EVENT_WEBHOOK_ENDPOINT}}' + schedule: '* * * * *' + include_in_metadata: true + payload: + message: hello + retry_conf: + num_retries: 3 + timeout_seconds: 100 + tolerance_seconds: 21600 + retry_interval_seconds: 15 + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE diff --git a/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.tmpl b/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.tmpl new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v2/metadata/cron_triggers.yaml.tmpl @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.expected b/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.expected new file mode 100644 index 000000000..358eef112 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.expected @@ -0,0 +1,34 @@ +- table: + schema: public + name: default_table + event_triggers: + - name: default_table_event_handler + definition: + enable_manual: false + insert: + columns: '*' + retry_conf: + num_retries: 3 + interval_sec: 15 + timeout_sec: 100 + webhook_from_env: NESTJS_EVENT_WEBHOOK_ENDPOINT + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE +- table: + schema: public + name: additional_table + event_triggers: + - name: additional_table_event_handler + definition: + enable_manual: false + delete: + columns: '*' + retry_conf: + num_retries: 3 + interval_sec: 15 + timeout_sec: 100 + webhook_from_env: NESTJS_EVENT_WEBHOOK_ENDPOINT + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE diff --git a/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.tmpl b/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.tmpl new file mode 100644 index 000000000..6dd26460e --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v2/metadata/tables.yaml.tmpl @@ -0,0 +1,6 @@ +- table: + schema: public + name: default_table +- table: + schema: public + name: additional_table diff --git a/packages/hasura/test/__fixtures__/hasura/v3/config.yaml b/packages/hasura/test/__fixtures__/hasura/v3/config.yaml new file mode 100644 index 000000000..a3dc7602d --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/config.yaml @@ -0,0 +1,6 @@ +version: 3 +endpoint: http://localhost:23080 +metadata_directory: metadata +actions: + kind: synchronous + handler_webhook_baseurl: http://localhost:3000 diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/actions.graphql b/packages/hasura/test/__fixtures__/hasura/v3/metadata/actions.graphql new file mode 100644 index 000000000..e69de29bb diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/actions.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/actions.yaml new file mode 100644 index 000000000..1edb4c2ff --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/actions.yaml @@ -0,0 +1,6 @@ +actions: [] +custom_types: + enums: [] + input_objects: [] + objects: [] + scalars: [] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/allow_list.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/allow_list.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/allow_list.yaml @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.expected b/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.expected new file mode 100644 index 000000000..c86fc0f1c --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.expected @@ -0,0 +1,14 @@ +- name: scheduled + webhook: '{{NESTJS_EVENT_WEBHOOK_ENDPOINT}}' + schedule: '* * * * *' + include_in_metadata: true + payload: + message: hello + retry_conf: + num_retries: 3 + timeout_seconds: 100 + tolerance_seconds: 21600 + retry_interval_seconds: 15 + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.tmpl b/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.tmpl new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/cron_triggers.yaml.tmpl @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.expected b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.expected new file mode 100644 index 000000000..88de9d74d --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.expected @@ -0,0 +1,17 @@ +table: + name: additional_table + schema: public +event_triggers: + - name: additional_table_event_handler + definition: + enable_manual: false + delete: + columns: '*' + retry_conf: + num_retries: 3 + interval_sec: 15 + timeout_sec: 100 + webhook_from_env: NESTJS_EVENT_WEBHOOK_ENDPOINT + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.tmpl b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.tmpl new file mode 100644 index 000000000..0f8b9a4d9 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/public_additional_table.yaml.tmpl @@ -0,0 +1,3 @@ +table: + name: additional_table + schema: public diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/tables.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/tables.yaml new file mode 100644 index 000000000..729fec7f6 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/additional/tables/tables.yaml @@ -0,0 +1 @@ +- "!include public_additional_table.yaml" diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/databases.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/databases.yaml new file mode 100644 index 000000000..03c4bc254 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/databases.yaml @@ -0,0 +1,18 @@ +- name: default + kind: postgres + configuration: + connection_info: + database_url: + from_env: HAUSRA_DB1_URL + isolation_level: read-committed + use_prepared_statements: false + tables: "!include default/tables/tables.yaml" +- name: additional + kind: postgres + configuration: + connection_info: + database_url: + from_env: HAUSRA_DB2_URL + isolation_level: read-committed + use_prepared_statements: false + tables: "!include additional/tables/tables.yaml" diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.expected b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.expected new file mode 100644 index 000000000..75811318c --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.expected @@ -0,0 +1,17 @@ +table: + name: default_table + schema: public +event_triggers: + - name: default_table_event_handler + definition: + enable_manual: false + insert: + columns: '*' + retry_conf: + num_retries: 3 + interval_sec: 15 + timeout_sec: 100 + webhook_from_env: NESTJS_EVENT_WEBHOOK_ENDPOINT + headers: + - name: NESTJS_SECRET_HEADER + value_from_env: NESTJS_WEBHOOK_SECRET_HEADER_VALUE diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.tmpl b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.tmpl new file mode 100644 index 000000000..3d470f6a9 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/public_default_table.yaml.tmpl @@ -0,0 +1,3 @@ +table: + name: default_table + schema: public diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/tables.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/tables.yaml new file mode 100644 index 000000000..9b2d90b15 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/databases/default/tables/tables.yaml @@ -0,0 +1 @@ +- "!include public_default_table.yaml" diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/query_collections.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/query_collections.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/query_collections.yaml @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/remote_schemas.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/remote_schemas.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/remote_schemas.yaml @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/rest_endpoints.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/rest_endpoints.yaml new file mode 100644 index 000000000..fe51488c7 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/rest_endpoints.yaml @@ -0,0 +1 @@ +[] diff --git a/packages/hasura/test/__fixtures__/hasura/v3/metadata/version.yaml b/packages/hasura/test/__fixtures__/hasura/v3/metadata/version.yaml new file mode 100644 index 000000000..0a70affa4 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/metadata/version.yaml @@ -0,0 +1 @@ +version: 3 diff --git a/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/down.sql b/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/down.sql new file mode 100644 index 000000000..b27c9248c --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."additional_table"; diff --git a/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/up.sql b/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/up.sql new file mode 100644 index 000000000..82f9c122a --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/migrations/additional/1625136276063_create_table_public_additional_table/up.sql @@ -0,0 +1 @@ +CREATE TABLE "public"."additional_table" ("id" serial NOT NULL, PRIMARY KEY ("id") ); diff --git a/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/down.sql b/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/down.sql new file mode 100644 index 000000000..dfc4c9d86 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/down.sql @@ -0,0 +1 @@ +DROP TABLE "public"."default_table"; diff --git a/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/up.sql b/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/up.sql new file mode 100644 index 000000000..3bf27b2f4 --- /dev/null +++ b/packages/hasura/test/__fixtures__/hasura/v3/migrations/default/1625136246852_create_table_public_default_table/up.sql @@ -0,0 +1 @@ +CREATE TABLE "public"."default_table" ("id" serial NOT NULL, PRIMARY KEY ("id") ); diff --git a/yarn.lock b/yarn.lock index 475b52310..69e58a6d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1932,6 +1932,11 @@ argparse@^1.0.7: dependencies: sprintf-js "~1.0.2" +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.yarnpkg.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + arr-diff@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/arr-diff/-/arr-diff-4.0.0.tgz#d6461074febfec71e7e15235761a329a5dc7c520" @@ -5512,13 +5517,12 @@ js-yaml@^3.13.1: argparse "^1.0.7" esprima "^4.0.0" -js-yaml@^3.14.1: - version "3.14.1" - resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-3.14.1.tgz#dae812fdb3825fa306609a8717383c50c36a0537" - integrity sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g== +js-yaml@^4.1.0: + version "4.1.0" + resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" + integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== dependencies: - argparse "^1.0.7" - esprima "^4.0.0" + argparse "^2.0.1" jsbn@~0.1.0: version "0.1.1" @@ -8680,6 +8684,11 @@ ts-jest@^24.0.0: semver "^5.5" yargs-parser "10.x" +ts-toolbelt@^9.6.0: + version "9.6.0" + resolved "https://registry.yarnpkg.com/ts-toolbelt/-/ts-toolbelt-9.6.0.tgz#50a25426cfed500d4a09bd1b3afb6f28879edfd5" + integrity sha512-nsZd8ZeNUzukXPlJmTBwUAuABDe/9qtVDelJeT/qW0ow3ZS3BsQJtNkan1802aM9Uf68/Y8ljw86Hu0h5IUW3w== + tslib@1.11.1: version "1.11.1" resolved "https://registry.yarnpkg.com/tslib/-/tslib-1.11.1.tgz#eb15d128827fbee2841549e171f45ed338ac7e35" @@ -9250,3 +9259,8 @@ yup@^0.26.10: property-expr "^1.5.0" synchronous-promise "^2.0.5" toposort "^2.0.2" + +zod@^3.3.4: + version "3.3.4" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.3.4.tgz#5609bb7e945945108690f87ea73454111c323259" + integrity sha512-g5V20IUn3QBpbMKjZxKp58ocUOO9/L0OlJ5+rl84QSeN6tK5ea/yQboYA59t2D8Puc/qVyz4YflhCgqR2uMtaA==