Wrappers.utils.ts 7.3 KB

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