Wrappers.utils.ts241 lines · main
| 1 | import * as z from 'zod' |
| 2 | |
| 3 | import { WRAPPER_HANDLERS, WRAPPERS } from './Wrappers.constants' |
| 4 | import type { Table, WrapperMeta } from './Wrappers.types' |
| 5 | import { FDW, FDWTable } from '@/data/fdw/fdws-query' |
| 6 | |
| 7 | const tableSchema = z |
| 8 | .object({ |
| 9 | index: z.number(), |
| 10 | columns: z.array(z.object({ name: z.string(), type: z.string() })), |
| 11 | is_new_schema: z.boolean(), |
| 12 | schema: z.string(), |
| 13 | schema_name: z.string(), |
| 14 | table_name: z.string(), |
| 15 | object: z.any().optional(), |
| 16 | }) |
| 17 | .passthrough() // passthrough is needed for table options |
| 18 | |
| 19 | export const getWrapperCreationFormSchema = (wrapperMeta: WrapperMeta) => { |
| 20 | let wrapperSchema = { |
| 21 | // Common validation for all wrappers |
| 22 | wrapper_name: z.string().min(1, 'Please provide a name for your wrapper'), |
| 23 | } as Record<string, any> |
| 24 | |
| 25 | // Add wrapper specific options |
| 26 | wrapperMeta.server.options.forEach((option) => { |
| 27 | if (option.required) { |
| 28 | wrapperSchema[option.name] = z.string().min(1, 'Required') |
| 29 | return |
| 30 | } |
| 31 | wrapperSchema[option.name] = z.string().optional() |
| 32 | }) |
| 33 | |
| 34 | return z.discriminatedUnion('mode', [ |
| 35 | z |
| 36 | .object({ |
| 37 | mode: z.literal('tables'), |
| 38 | tables: z |
| 39 | .array(tableSchema, { required_error: 'Please provide at least one table' }) |
| 40 | .min(1, 'Please provide at least one table'), |
| 41 | }) |
| 42 | .merge(z.object(wrapperSchema)), |
| 43 | z |
| 44 | .object({ |
| 45 | mode: z.literal('schema'), |
| 46 | source_schema: z.string().min(1, 'Please provide a source schema'), |
| 47 | target_schema: z.string().min(1, 'Please provide an unique target schema'), |
| 48 | }) |
| 49 | .merge(z.object(wrapperSchema)), |
| 50 | ]) |
| 51 | } |
| 52 | |
| 53 | export const getEditionFormSchema = (wrapperMeta: WrapperMeta) => { |
| 54 | let wrapperSchema = { |
| 55 | // Common validation for all wrappers |
| 56 | wrapper_name: z.string().min(1, 'Please provide a name for your wrapper'), |
| 57 | tables: z |
| 58 | .array(tableSchema, { required_error: 'Please provide at least one table' }) |
| 59 | .min(1, 'Please provide at least one table'), |
| 60 | } as Record<string, any> |
| 61 | |
| 62 | // Add wrapper specific options |
| 63 | wrapperMeta.server.options.forEach((option) => { |
| 64 | if (option.required) { |
| 65 | wrapperSchema[option.name] = z.string().min(1, 'Required') |
| 66 | return |
| 67 | } |
| 68 | wrapperSchema[option.name] = z.string().optional() |
| 69 | }) |
| 70 | return z.object(wrapperSchema) |
| 71 | } |
| 72 | |
| 73 | export const getTableFormSchema = (table: Table) => { |
| 74 | let tableSchema = { |
| 75 | table_name: z.string().min(1, 'Required'), |
| 76 | schema: z.string().min(1, 'Required'), |
| 77 | schema_name: z.string().optional(), |
| 78 | columns: z.array( |
| 79 | z.object({ |
| 80 | name: z.string().min(1, 'Required'), |
| 81 | type: z.string().min(1, 'Required'), |
| 82 | }) |
| 83 | ), |
| 84 | } as Record<string, any> |
| 85 | |
| 86 | table.options.forEach((option) => { |
| 87 | if (option.required) { |
| 88 | tableSchema[option.name] = z.string().min(1, 'Required') |
| 89 | return |
| 90 | } |
| 91 | tableSchema[option.name] = z.string().optional() |
| 92 | }) |
| 93 | |
| 94 | return ( |
| 95 | z |
| 96 | .object(tableSchema) |
| 97 | // passthrough is needed for table options |
| 98 | .passthrough() |
| 99 | .superRefine((values, ctx) => { |
| 100 | if (values.schema === 'custom' && !values.schema_name) { |
| 101 | ctx.addIssue({ |
| 102 | code: 'custom', |
| 103 | path: ['schema_name'], |
| 104 | message: 'Required', |
| 105 | }) |
| 106 | } |
| 107 | }) |
| 108 | ) |
| 109 | } |
| 110 | |
| 111 | export const makeValidateRequired = (options: { name: string; required: boolean }[]) => { |
| 112 | const requiredOptionsSet = new Set( |
| 113 | options.filter((option) => option.required).map((option) => option.name) |
| 114 | ) |
| 115 | |
| 116 | const requiredArrayOptionsSet = new Set( |
| 117 | Array.from(requiredOptionsSet).filter((option) => option.includes('.')) |
| 118 | ) |
| 119 | const requiredArrayOptions = Array.from(requiredArrayOptionsSet) |
| 120 | |
| 121 | return (values: Record<string, any>) => { |
| 122 | const errors = Object.fromEntries( |
| 123 | Object.entries(values) |
| 124 | .flatMap(([key, value]) => |
| 125 | Array.isArray(value) |
| 126 | ? [[key, value], ...value.map((v, i) => [`${key}.${i}`, v])] |
| 127 | : [[key, value]] |
| 128 | ) |
| 129 | .filter(([_key, value]) => { |
| 130 | const [key, idx] = _key.split('.') |
| 131 | |
| 132 | if ( |
| 133 | idx !== undefined && |
| 134 | requiredOptionsSet.has(key) && |
| 135 | Object.keys(value).some((subKey) => requiredArrayOptionsSet.has(`${key}.${subKey}`)) |
| 136 | ) { |
| 137 | const arrayOption = requiredArrayOptions.find((option) => option.startsWith(`${key}.`)) |
| 138 | if (arrayOption) { |
| 139 | const subKey = arrayOption.split('.')[1] |
| 140 | return !value[subKey] |
| 141 | } |
| 142 | |
| 143 | return false |
| 144 | } |
| 145 | |
| 146 | return requiredOptionsSet.has(key) && (Array.isArray(value) ? value.length < 1 : !value) |
| 147 | }) |
| 148 | .map(([key]) => { |
| 149 | if (key === 'table_name') return [key, 'Please provide a name for your table'] |
| 150 | else if (key === 'columns') return [key, 'Please select at least one column'] |
| 151 | else return [key, 'This field is required'] |
| 152 | }) |
| 153 | ) |
| 154 | |
| 155 | return errors |
| 156 | } |
| 157 | } |
| 158 | |
| 159 | export const NewTable = {} as FormattedWrapperTable |
| 160 | |
| 161 | export interface FormattedWrapperTable { |
| 162 | index: number |
| 163 | columns: { name: string }[] |
| 164 | is_new_schema: boolean |
| 165 | schema: string |
| 166 | schema_name: string |
| 167 | table_name: string |
| 168 | object?: string // From options object for Firebase/Stripe |
| 169 | [key: string]: any // For other dynamic options from table.options |
| 170 | } |
| 171 | |
| 172 | export const formatWrapperTables = ( |
| 173 | wrapper: { handler: string; tables?: FDWTable[] }, |
| 174 | wrapperMeta?: WrapperMeta |
| 175 | ): FormattedWrapperTable[] => { |
| 176 | const tables = wrapper?.tables ?? [] |
| 177 | |
| 178 | return tables.map((table) => { |
| 179 | let index: number = 0 |
| 180 | const options = Object.fromEntries(table.options.map((option: string) => option.split('='))) |
| 181 | |
| 182 | switch (wrapper.handler) { |
| 183 | case WRAPPER_HANDLERS.STRIPE: |
| 184 | index = |
| 185 | wrapperMeta?.tables.findIndex( |
| 186 | (x) => x.options.find((x) => x.name === 'object')?.defaultValue === options.object |
| 187 | ) ?? 0 |
| 188 | break |
| 189 | case WRAPPER_HANDLERS.FIREBASE: |
| 190 | if (options.object === 'auth/users') { |
| 191 | index = |
| 192 | wrapperMeta?.tables.findIndex((x) => |
| 193 | x.options.find((x) => x.defaultValue === 'auth/users') |
| 194 | ) ?? 0 |
| 195 | } else { |
| 196 | index = wrapperMeta?.tables.findIndex((x) => x.label === 'Firestore Collection') ?? 0 |
| 197 | } |
| 198 | break |
| 199 | case WRAPPER_HANDLERS.S3: |
| 200 | case WRAPPER_HANDLERS.AIRTABLE: |
| 201 | case WRAPPER_HANDLERS.LOGFLARE: |
| 202 | case WRAPPER_HANDLERS.BIG_QUERY: |
| 203 | case WRAPPER_HANDLERS.CLICK_HOUSE: |
| 204 | break |
| 205 | } |
| 206 | |
| 207 | return { |
| 208 | ...options, |
| 209 | index, |
| 210 | id: table.id, |
| 211 | columns: table.columns ?? [], |
| 212 | is_new_schema: false, |
| 213 | schema: table.schema, |
| 214 | schema_name: table.schema, |
| 215 | table_name: table.name, |
| 216 | } |
| 217 | }) |
| 218 | } |
| 219 | |
| 220 | export const convertKVStringArrayToJson = (values: string[]): Record<string, string> => { |
| 221 | return Object.fromEntries(values.map((value) => value.split('='))) |
| 222 | } |
| 223 | |
| 224 | export function wrapperMetaComparator( |
| 225 | wrapperMeta: Pick<WrapperMeta, 'handlerName' | 'server'>, |
| 226 | wrapper: FDW | undefined |
| 227 | ) { |
| 228 | if (wrapperMeta.handlerName === 'wasm_fdw_handler') { |
| 229 | const serverOptions = convertKVStringArrayToJson(wrapper?.server_options ?? []) |
| 230 | return ( |
| 231 | wrapperMeta.server.options.find((option) => option.name === 'fdw_package_name') |
| 232 | ?.defaultValue === serverOptions['fdw_package_name'] |
| 233 | ) |
| 234 | } |
| 235 | |
| 236 | return wrapperMeta.handlerName === wrapper?.handler |
| 237 | } |
| 238 | |
| 239 | export function getWrapperMetaForWrapper(wrapper: FDW | undefined) { |
| 240 | return WRAPPERS.find((w) => wrapperMetaComparator(w, wrapper)) |
| 241 | } |