@betterinternship/components 2.4.3 → 2.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -50
- package/dist/components/ui/accordion.js.map +1 -1
- package/dist/components/ui/app-header.js.map +1 -1
- package/dist/components/ui/avatar.js.map +1 -1
- package/dist/components/ui/badge.js.map +1 -1
- package/dist/components/ui/button.js.map +1 -1
- package/dist/components/ui/card.js.map +1 -1
- package/dist/components/ui/checkbox.js.map +1 -1
- package/dist/components/ui/dialog.js.map +1 -1
- package/dist/components/ui/dropdown-menu.js.map +1 -1
- package/dist/components/ui/icons.js.map +1 -1
- package/dist/components/ui/input.js.map +1 -1
- package/dist/components/ui/label.js.map +1 -1
- package/dist/components/ui/page-header.js.map +1 -1
- package/dist/components/ui/popover.js.map +1 -1
- package/dist/components/ui/separator.js.map +1 -1
- package/dist/components/ui/skeleton.js.map +1 -1
- package/dist/components/ui/switch.js.map +1 -1
- package/dist/components/ui/table.js.map +1 -1
- package/dist/components/ui/textarea.js.map +1 -1
- package/dist/components/ui/tooltip.js.map +1 -1
- package/dist/hooks/useClientProcess.js.map +1 -1
- package/dist/hooks/useMQJob.d.ts +58 -0
- package/dist/hooks/useMQJob.js +83 -0
- package/dist/hooks/useMQJob.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js +364 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.development.js.map +1 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.production.js +45 -0
- package/dist/node_modules/react/cjs/react-jsx-runtime.production.js.map +1 -0
- package/dist/node_modules/react/jsx-runtime.js +20 -0
- package/dist/node_modules/react/jsx-runtime.js.map +1 -0
- package/dist/node_modules/style-inject/dist/style-inject.es.js +29 -0
- package/dist/node_modules/style-inject/dist/style-inject.es.js.map +1 -0
- package/dist/utils.js.map +1 -1
- package/package.json +231 -231
- package/theme.css +228 -228
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"separator.js","sources":["../../../src/components/ui/separator.tsx"],"sourcesContent":["import * as React from 'react';\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\n\nimport { cn } from '@/utils';\n\nfunction Separator({\n className,\n orientation = 'horizontal',\n decorative = true,\n ...props\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\n return (\n <SeparatorPrimitive.Root\n data-slot=\"separator\"\n decorative={decorative}\n orientation={orientation}\n className={cn(\n 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Separator };\n"],"names":["_jsx"],"mappings":";;;;AAKA,SAAS,SAAS,CAAC,EACjB,SAAS,EACT,WAAW,GAAG,YAAY,EAC1B,UAAU,GAAG,IAAI,EACjB,GAAG,KAAK,EAC6C,EAAA;AACrD,IAAA,QACEA,GAAA,CAAC,kBAAkB,CAAC,IAAI,EAAA,EAAA,WAAA,EACZ,WAAW,EACrB,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,EAAE,CACX,gKAAgK,EAChK,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
1
|
+
{"version":3,"file":"separator.js","sources":["../../../src/components/ui/separator.tsx"],"sourcesContent":["import * as React from 'react';\r\nimport * as SeparatorPrimitive from '@radix-ui/react-separator';\r\n\r\nimport { cn } from '@/utils';\r\n\r\nfunction Separator({\r\n className,\r\n orientation = 'horizontal',\r\n decorative = true,\r\n ...props\r\n}: React.ComponentProps<typeof SeparatorPrimitive.Root>) {\r\n return (\r\n <SeparatorPrimitive.Root\r\n data-slot=\"separator\"\r\n decorative={decorative}\r\n orientation={orientation}\r\n className={cn(\r\n 'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nexport { Separator };\r\n"],"names":["_jsx"],"mappings":";;;;AAKA,SAAS,SAAS,CAAC,EACjB,SAAS,EACT,WAAW,GAAG,YAAY,EAC1B,UAAU,GAAG,IAAI,EACjB,GAAG,KAAK,EAC6C,EAAA;AACrD,IAAA,QACEA,GAAA,CAAC,kBAAkB,CAAC,IAAI,EAAA,EAAA,WAAA,EACZ,WAAW,EACrB,UAAU,EAAE,UAAU,EACtB,WAAW,EAAE,WAAW,EACxB,SAAS,EAAE,EAAE,CACX,gKAAgK,EAChK,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"skeleton.js","sources":["../../../src/components/ui/skeleton.tsx"],"sourcesContent":["import { cn } from '@/utils';\n\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\n return (\n <div\n data-slot=\"skeleton\"\n className={cn('bg-accent animate-pulse rounded-md', className)}\n {...props}\n />\n );\n}\n\nexport { Skeleton };\n"],"names":["_jsx"],"mappings":";;;AAEA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAA,WAAA,EACY,UAAU,EACpB,SAAS,EAAE,EAAE,CAAC,oCAAoC,EAAE,SAAS,CAAC,EAAA,GAC1D,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
1
|
+
{"version":3,"file":"skeleton.js","sources":["../../../src/components/ui/skeleton.tsx"],"sourcesContent":["import { cn } from '@/utils';\r\n\r\nfunction Skeleton({ className, ...props }: React.ComponentProps<'div'>) {\r\n return (\r\n <div\r\n data-slot=\"skeleton\"\r\n className={cn('bg-accent animate-pulse rounded-md', className)}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nexport { Skeleton };\r\n"],"names":["_jsx"],"mappings":";;;AAEA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA+B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,KAAA,EAAA,EAAA,WAAA,EACY,UAAU,EACpB,SAAS,EAAE,EAAE,CAAC,oCAAoC,EAAE,SAAS,CAAC,EAAA,GAC1D,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"switch.js","sources":["../../../src/components/ui/switch.tsx"],"sourcesContent":["import * as React from 'react';\nimport * as SwitchPrimitive from '@radix-ui/react-switch';\n\nimport { cn } from '@/utils';\n\nfunction Switch({\n className,\n ...props\n}: React.ComponentProps<typeof SwitchPrimitive.Root>) {\n return (\n <SwitchPrimitive.Root\n data-slot=\"switch\"\n className={cn(\n 'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\n className,\n )}\n {...props}\n >\n <SwitchPrimitive.Thumb\n data-slot=\"switch-thumb\"\n className={cn(\n 'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',\n )}\n />\n </SwitchPrimitive.Root>\n );\n}\n\nexport { Switch };\n"],"names":["_jsx"],"mappings":";;;;AAKA,SAAS,MAAM,CAAC,EACd,SAAS,EACT,GAAG,KAAK,EAC0C,EAAA;AAClD,IAAA,QACEA,GAAA,CAAC,eAAe,CAAC,IAAI,EAAA,EAAA,WAAA,EACT,QAAQ,EAClB,SAAS,EAAE,EAAE,CACX,2WAA2W,EAC3W,SAAS,CACV,EAAA,GACG,KAAK,EAAA,QAAA,EAETA,IAAC,eAAe,CAAC,KAAK,EAAA,EAAA,WAAA,EACV,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,0QAA0Q,CAC3Q,EAAA,CACD,EAAA,CACmB;AAE3B;;;;"}
|
|
1
|
+
{"version":3,"file":"switch.js","sources":["../../../src/components/ui/switch.tsx"],"sourcesContent":["import * as React from 'react';\r\nimport * as SwitchPrimitive from '@radix-ui/react-switch';\r\n\r\nimport { cn } from '@/utils';\r\n\r\nfunction Switch({\r\n className,\r\n ...props\r\n}: React.ComponentProps<typeof SwitchPrimitive.Root>) {\r\n return (\r\n <SwitchPrimitive.Root\r\n data-slot=\"switch\"\r\n className={cn(\r\n 'peer data-[state=checked]:bg-primary data-[state=unchecked]:bg-input focus-visible:border-ring focus-visible:ring-ring/50 dark:data-[state=unchecked]:bg-input/80 inline-flex h-[1.15rem] w-8 shrink-0 items-center rounded-full border border-transparent shadow-xs transition-all outline-none focus-visible:ring-[3px] disabled:cursor-not-allowed disabled:opacity-50',\r\n className,\r\n )}\r\n {...props}\r\n >\r\n <SwitchPrimitive.Thumb\r\n data-slot=\"switch-thumb\"\r\n className={cn(\r\n 'bg-background dark:data-[state=unchecked]:bg-foreground dark:data-[state=checked]:bg-primary-foreground pointer-events-none block size-4 rounded-full ring-0 transition-transform data-[state=checked]:translate-x-[calc(100%-2px)] data-[state=unchecked]:translate-x-0',\r\n )}\r\n />\r\n </SwitchPrimitive.Root>\r\n );\r\n}\r\n\r\nexport { Switch };\r\n"],"names":["_jsx"],"mappings":";;;;AAKA,SAAS,MAAM,CAAC,EACd,SAAS,EACT,GAAG,KAAK,EAC0C,EAAA;AAClD,IAAA,QACEA,GAAA,CAAC,eAAe,CAAC,IAAI,EAAA,EAAA,WAAA,EACT,QAAQ,EAClB,SAAS,EAAE,EAAE,CACX,2WAA2W,EAC3W,SAAS,CACV,EAAA,GACG,KAAK,EAAA,QAAA,EAETA,IAAC,eAAe,CAAC,KAAK,EAAA,EAAA,WAAA,EACV,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,0QAA0Q,CAC3Q,EAAA,CACD,EAAA,CACmB;AAE3B;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"table.js","sources":["../../../src/components/ui/table.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '@/utils';\n\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\n return (\n <div\n data-slot=\"table-container\"\n className=\"relative w-full overflow-x-auto [scrollbar-gutter:stable]\"\n >\n <table\n data-slot=\"table\"\n className={cn('w-full caption-bottom text-sm', className)}\n {...props}\n />\n </div>\n );\n}\n\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\n return (\n <thead\n data-slot=\"table-header\"\n className={cn('[&_tr]:border-b', className)}\n {...props}\n />\n );\n}\n\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\n return (\n <tbody\n data-slot=\"table-body\"\n className={cn('[&_tr:last-child]:border-0', className)}\n {...props}\n />\n );\n}\n\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\n return (\n <tfoot\n data-slot=\"table-footer\"\n className={cn(\n 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\n return (\n <tr\n data-slot=\"table-row\"\n className={cn(\n 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\n return (\n <th\n data-slot=\"table-head\"\n className={cn(\n 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\n return (\n <td\n data-slot=\"table-cell\"\n className={cn(\n 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\n className,\n )}\n {...props}\n />\n );\n}\n\nfunction TableCaption({\n className,\n ...props\n}: React.ComponentProps<'caption'>) {\n return (\n <caption\n data-slot=\"table-caption\"\n className={cn('text-muted-foreground mt-4 text-sm', className)}\n {...props}\n />\n );\n}\n\nexport {\n Table,\n TableHeader,\n TableBody,\n TableFooter,\n TableHead,\n TableRow,\n TableCell,\n TableCaption,\n};\n"],"names":["_jsx"],"mappings":";;;AAIA,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;IACnE,QACEA,GAAA,CAAA,KAAA,EAAA,EAAA,WAAA,EACY,iBAAiB,EAC3B,SAAS,EAAC,2DAA2D,EAAA,QAAA,EAErEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,OAAO,EACjB,SAAS,EAAE,EAAE,CAAC,+BAA+B,EAAE,SAAS,CAAC,KACrD,KAAK,EAAA,CACT,EAAA,CACE;AAEV;AAEA,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,cAAc,EACxB,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,EAAA,GACvC,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACvE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CAAC,4BAA4B,EAAE,SAAS,CAAC,EAAA,GAClD,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,yDAAyD,EACzD,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACnE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,WAAW,EACrB,SAAS,EAAE,EAAE,CACX,6EAA6E,EAC7E,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CACX,oJAAoJ,EACpJ,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CACX,wGAAwG,EACxG,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACwB,EAAA;AAChC,IAAA,QACEA,GAAA,CAAA,SAAA,EAAA,EAAA,WAAA,EACY,eAAe,EACzB,SAAS,EAAE,EAAE,CAAC,oCAAoC,EAAE,SAAS,CAAC,EAAA,GAC1D,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
1
|
+
{"version":3,"file":"table.js","sources":["../../../src/components/ui/table.tsx"],"sourcesContent":["import * as React from 'react';\r\n\r\nimport { cn } from '@/utils';\r\n\r\nfunction Table({ className, ...props }: React.ComponentProps<'table'>) {\r\n return (\r\n <div\r\n data-slot=\"table-container\"\r\n className=\"relative w-full overflow-x-auto [scrollbar-gutter:stable]\"\r\n >\r\n <table\r\n data-slot=\"table\"\r\n className={cn('w-full caption-bottom text-sm', className)}\r\n {...props}\r\n />\r\n </div>\r\n );\r\n}\r\n\r\nfunction TableHeader({ className, ...props }: React.ComponentProps<'thead'>) {\r\n return (\r\n <thead\r\n data-slot=\"table-header\"\r\n className={cn('[&_tr]:border-b', className)}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableBody({ className, ...props }: React.ComponentProps<'tbody'>) {\r\n return (\r\n <tbody\r\n data-slot=\"table-body\"\r\n className={cn('[&_tr:last-child]:border-0', className)}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableFooter({ className, ...props }: React.ComponentProps<'tfoot'>) {\r\n return (\r\n <tfoot\r\n data-slot=\"table-footer\"\r\n className={cn(\r\n 'bg-muted/50 border-t font-medium [&>tr]:last:border-b-0',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableRow({ className, ...props }: React.ComponentProps<'tr'>) {\r\n return (\r\n <tr\r\n data-slot=\"table-row\"\r\n className={cn(\r\n 'hover:bg-muted/50 data-[state=selected]:bg-muted border-b transition-colors',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableHead({ className, ...props }: React.ComponentProps<'th'>) {\r\n return (\r\n <th\r\n data-slot=\"table-head\"\r\n className={cn(\r\n 'text-foreground h-10 px-2 text-left align-middle font-medium whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableCell({ className, ...props }: React.ComponentProps<'td'>) {\r\n return (\r\n <td\r\n data-slot=\"table-cell\"\r\n className={cn(\r\n 'p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction TableCaption({\r\n className,\r\n ...props\r\n}: React.ComponentProps<'caption'>) {\r\n return (\r\n <caption\r\n data-slot=\"table-caption\"\r\n className={cn('text-muted-foreground mt-4 text-sm', className)}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nexport {\r\n Table,\r\n TableHeader,\r\n TableBody,\r\n TableFooter,\r\n TableHead,\r\n TableRow,\r\n TableCell,\r\n TableCaption,\r\n};\r\n"],"names":["_jsx"],"mappings":";;;AAIA,SAAS,KAAK,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;IACnE,QACEA,GAAA,CAAA,KAAA,EAAA,EAAA,WAAA,EACY,iBAAiB,EAC3B,SAAS,EAAC,2DAA2D,EAAA,QAAA,EAErEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,OAAO,EACjB,SAAS,EAAE,EAAE,CAAC,+BAA+B,EAAE,SAAS,CAAC,KACrD,KAAK,EAAA,CACT,EAAA,CACE;AAEV;AAEA,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,cAAc,EACxB,SAAS,EAAE,EAAE,CAAC,iBAAiB,EAAE,SAAS,CAAC,EAAA,GACvC,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACvE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CAAC,4BAA4B,EAAE,SAAS,CAAC,EAAA,GAClD,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,WAAW,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAiC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,OAAA,EAAA,EAAA,WAAA,EACY,cAAc,EACxB,SAAS,EAAE,EAAE,CACX,yDAAyD,EACzD,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACnE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,WAAW,EACrB,SAAS,EAAE,EAAE,CACX,6EAA6E,EAC7E,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CACX,oJAAoJ,EACpJ,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,SAAS,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAA8B,EAAA;AACpE,IAAA,QACEA,GAAA,CAAA,IAAA,EAAA,EAAA,WAAA,EACY,YAAY,EACtB,SAAS,EAAE,EAAE,CACX,wGAAwG,EACxG,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,YAAY,CAAC,EACpB,SAAS,EACT,GAAG,KAAK,EACwB,EAAA;AAChC,IAAA,QACEA,GAAA,CAAA,SAAA,EAAA,EAAA,WAAA,EACY,eAAe,EACzB,SAAS,EAAE,EAAE,CAAC,oCAAoC,EAAE,SAAS,CAAC,EAAA,GAC1D,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"textarea.js","sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["import * as React from 'react';\n\nimport { cn } from '@/utils';\n\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\n return (\n <textarea\n data-slot=\"textarea\"\n className={cn(\n 'placeholder:text-muted-foreground/40 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-[0.33em] border border-gray-300 bg-transparent px-3 py-2 text-base shadow-xs ring-0 transition-[color,box-shadow] outline-none focus:border-gray-500 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\n className,\n )}\n {...props}\n />\n );\n}\n\nexport { Textarea };\n"],"names":["_jsx"],"mappings":";;;AAIA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAoC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,UAAA,EAAA,EAAA,WAAA,EACY,UAAU,EACpB,SAAS,EAAE,EAAE,CACX,gaAAga,EACha,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
1
|
+
{"version":3,"file":"textarea.js","sources":["../../../src/components/ui/textarea.tsx"],"sourcesContent":["import * as React from 'react';\r\n\r\nimport { cn } from '@/utils';\r\n\r\nfunction Textarea({ className, ...props }: React.ComponentProps<'textarea'>) {\r\n return (\r\n <textarea\r\n data-slot=\"textarea\"\r\n className={cn(\r\n 'placeholder:text-muted-foreground/40 aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive dark:bg-input/30 flex field-sizing-content min-h-16 w-full rounded-[0.33em] border border-gray-300 bg-transparent px-3 py-2 text-base shadow-xs ring-0 transition-[color,box-shadow] outline-none focus:border-gray-500 disabled:cursor-not-allowed disabled:opacity-50 md:text-sm',\r\n className,\r\n )}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nexport { Textarea };\r\n"],"names":["_jsx"],"mappings":";;;AAIA,SAAS,QAAQ,CAAC,EAAE,SAAS,EAAE,GAAG,KAAK,EAAoC,EAAA;AACzE,IAAA,QACEA,GAAA,CAAA,UAAA,EAAA,EAAA,WAAA,EACY,UAAU,EACpB,SAAS,EAAE,EAAE,CACX,gaAAga,EACha,SAAS,CACV,EAAA,GACG,KAAK,EAAA,CACT;AAEN;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tooltip.js","sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import * as React from 'react';\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\n\nimport { cn } from '@/utils';\n\nfunction TooltipProvider({\n delayDuration = 0,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\n return (\n <TooltipPrimitive.Provider\n data-slot=\"tooltip-provider\"\n delayDuration={delayDuration}\n {...props}\n />\n );\n}\n\nfunction Tooltip({\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\n return (\n <TooltipProvider>\n <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\n </TooltipProvider>\n );\n}\n\nfunction TooltipTrigger({\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\n return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\n}\n\nfunction TooltipContent({\n className,\n arrowClassName,\n sideOffset = 0,\n children,\n ...props\n}: React.ComponentProps<typeof TooltipPrimitive.Content> & {\n arrowClassName?: string;\n}) {\n return (\n <TooltipPrimitive.Portal>\n <TooltipPrimitive.Content\n data-slot=\"tooltip-content\"\n sideOffset={sideOffset}\n className={cn(\n 'animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-[0.15em] bg-gray-900 px-3 py-1.5 text-xs text-balance text-white',\n className,\n )}\n {...props}\n >\n {children}\n <TooltipPrimitive.Arrow\n width={10}\n height={5}\n className={cn('z-50 fill-gray-900', arrowClassName)}\n />\n </TooltipPrimitive.Content>\n </TooltipPrimitive.Portal>\n );\n}\n\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\n"],"names":["_jsx","_jsxs"],"mappings":";;;;AAKA,SAAS,eAAe,CAAC,EACvB,aAAa,GAAG,CAAC,EACjB,GAAG,KAAK,EAC+C,EAAA;AACvD,IAAA,QACEA,GAAA,CAAC,gBAAgB,CAAC,QAAQ,EAAA,EAAA,WAAA,EACd,kBAAkB,EAC5B,aAAa,EAAE,aAAa,EAAA,GACxB,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,OAAO,CAAC,EACf,GAAG,KAAK,EAC2C,EAAA;AACnD,IAAA,QACEA,GAAA,CAAC,eAAe,EAAA,EAAA,QAAA,EACdA,IAAC,gBAAgB,CAAC,IAAI,EAAA,EAAA,WAAA,EAAW,SAAS,EAAA,GAAK,KAAK,EAAA,CAAI,EAAA,CACxC;AAEtB;AAEA,SAAS,cAAc,CAAC,EACtB,GAAG,KAAK,EAC8C,EAAA;IACtD,OAAOA,GAAA,CAAC,gBAAgB,CAAC,OAAO,iBAAW,iBAAiB,EAAA,GAAK,KAAK,EAAA,CAAI;AAC5E;AAEA,SAAS,cAAc,CAAC,EACtB,SAAS,EACT,cAAc,EACd,UAAU,GAAG,CAAC,EACd,QAAQ,EACR,GAAG,KAAK,EAGT,EAAA;IACC,QACEA,GAAA,CAAC,gBAAgB,CAAC,MAAM,cACtBC,IAAA,CAAC,gBAAgB,CAAC,OAAO,EAAA,EAAA,WAAA,EACb,iBAAiB,EAC3B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,EAAE,CACX,kaAAka,EACla,SAAS,CACV,EAAA,GACG,KAAK,aAER,QAAQ,EACTD,GAAA,CAAC,gBAAgB,CAAC,KAAK,EAAA,EACrB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,CAAC,EACT,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAAE,cAAc,CAAC,EAAA,CACnD,CAAA,EAAA,CACuB,EAAA,CACH;AAE9B;;;;"}
|
|
1
|
+
{"version":3,"file":"tooltip.js","sources":["../../../src/components/ui/tooltip.tsx"],"sourcesContent":["import * as React from 'react';\r\nimport * as TooltipPrimitive from '@radix-ui/react-tooltip';\r\n\r\nimport { cn } from '@/utils';\r\n\r\nfunction TooltipProvider({\r\n delayDuration = 0,\r\n ...props\r\n}: React.ComponentProps<typeof TooltipPrimitive.Provider>) {\r\n return (\r\n <TooltipPrimitive.Provider\r\n data-slot=\"tooltip-provider\"\r\n delayDuration={delayDuration}\r\n {...props}\r\n />\r\n );\r\n}\r\n\r\nfunction Tooltip({\r\n ...props\r\n}: React.ComponentProps<typeof TooltipPrimitive.Root>) {\r\n return (\r\n <TooltipProvider>\r\n <TooltipPrimitive.Root data-slot=\"tooltip\" {...props} />\r\n </TooltipProvider>\r\n );\r\n}\r\n\r\nfunction TooltipTrigger({\r\n ...props\r\n}: React.ComponentProps<typeof TooltipPrimitive.Trigger>) {\r\n return <TooltipPrimitive.Trigger data-slot=\"tooltip-trigger\" {...props} />;\r\n}\r\n\r\nfunction TooltipContent({\r\n className,\r\n arrowClassName,\r\n sideOffset = 0,\r\n children,\r\n ...props\r\n}: React.ComponentProps<typeof TooltipPrimitive.Content> & {\r\n arrowClassName?: string;\r\n}) {\r\n return (\r\n <TooltipPrimitive.Portal>\r\n <TooltipPrimitive.Content\r\n data-slot=\"tooltip-content\"\r\n sideOffset={sideOffset}\r\n className={cn(\r\n 'animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 z-50 w-fit origin-(--radix-tooltip-content-transform-origin) rounded-[0.15em] bg-gray-900 px-3 py-1.5 text-xs text-balance text-white',\r\n className,\r\n )}\r\n {...props}\r\n >\r\n {children}\r\n <TooltipPrimitive.Arrow\r\n width={10}\r\n height={5}\r\n className={cn('z-50 fill-gray-900', arrowClassName)}\r\n />\r\n </TooltipPrimitive.Content>\r\n </TooltipPrimitive.Portal>\r\n );\r\n}\r\n\r\nexport { Tooltip, TooltipTrigger, TooltipContent, TooltipProvider };\r\n"],"names":["_jsx","_jsxs"],"mappings":";;;;AAKA,SAAS,eAAe,CAAC,EACvB,aAAa,GAAG,CAAC,EACjB,GAAG,KAAK,EAC+C,EAAA;AACvD,IAAA,QACEA,GAAA,CAAC,gBAAgB,CAAC,QAAQ,EAAA,EAAA,WAAA,EACd,kBAAkB,EAC5B,aAAa,EAAE,aAAa,EAAA,GACxB,KAAK,EAAA,CACT;AAEN;AAEA,SAAS,OAAO,CAAC,EACf,GAAG,KAAK,EAC2C,EAAA;AACnD,IAAA,QACEA,GAAA,CAAC,eAAe,EAAA,EAAA,QAAA,EACdA,IAAC,gBAAgB,CAAC,IAAI,EAAA,EAAA,WAAA,EAAW,SAAS,EAAA,GAAK,KAAK,EAAA,CAAI,EAAA,CACxC;AAEtB;AAEA,SAAS,cAAc,CAAC,EACtB,GAAG,KAAK,EAC8C,EAAA;IACtD,OAAOA,GAAA,CAAC,gBAAgB,CAAC,OAAO,iBAAW,iBAAiB,EAAA,GAAK,KAAK,EAAA,CAAI;AAC5E;AAEA,SAAS,cAAc,CAAC,EACtB,SAAS,EACT,cAAc,EACd,UAAU,GAAG,CAAC,EACd,QAAQ,EACR,GAAG,KAAK,EAGT,EAAA;IACC,QACEA,GAAA,CAAC,gBAAgB,CAAC,MAAM,cACtBC,IAAA,CAAC,gBAAgB,CAAC,OAAO,EAAA,EAAA,WAAA,EACb,iBAAiB,EAC3B,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,EAAE,CACX,kaAAka,EACla,SAAS,CACV,EAAA,GACG,KAAK,aAER,QAAQ,EACTD,GAAA,CAAC,gBAAgB,CAAC,KAAK,EAAA,EACrB,KAAK,EAAE,EAAE,EACT,MAAM,EAAE,CAAC,EACT,SAAS,EAAE,EAAE,CAAC,oBAAoB,EAAE,cAAc,CAAC,EAAA,CACnD,CAAA,EAAA,CACuB,EAAA,CACH;AAE9B;;;;"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"useClientProcess.js","sources":["../../src/hooks/useClientProcess.tsx"],"sourcesContent":["'use client';\n\nimport { useState, useRef, createContext, useContext, useEffect } from 'react';\nimport { Query, useQueries, useQueryClient } from '@tanstack/react-query';\nimport { config } from '../config';\n\ntype ProcessStatus = 'pending' | 'handled' | 'failed';\n\ninterface Process<T> {\n id: string;\n name: string;\n status: ProcessStatus;\n result?: T;\n error?: string;\n timestamp: string;\n}\n\ninterface ClientProcess<T> {\n dto?: ProcessCallbackDto;\n metadata?: ClientProcessMetadata;\n options?: ClientProcessOptions;\n result?: T;\n success?: boolean;\n pending?: boolean;\n error?: string;\n}\n\nexport interface ProcessCallbackDto {\n processId: string;\n processName: string;\n processCallbackUrl: string;\n}\n\nexport interface ClientProcessOptions {\n onLoading?: (processId: string, processName: string) => void;\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\n onFailure?: (processId: string, processName: string, error?: string) => void;\n}\n\nexport interface ClientProcessMetadata {\n filterKey: string;\n metadata?: Record<string, string>;\n}\n\nexport interface ProcessResponse {\n processId: string;\n processName: string;\n processCallbackUrl: string;\n success: boolean;\n message?: string;\n}\n\n/**\n * The orchestrator service to get the process from.\n *\n * @param processId\n * @returns\n */\nconst processStatusChecker = <T,>(processId: string) => {\n if (!processId) return { process: null };\n return fetch(`${config.orchestratorApi}/${encodeURIComponent(processId)}`, {\n method: 'GET',\n headers: {\n 'Content-Type': 'application/json',\n },\n }).then((response) => response.json()) as unknown as Promise<{\n process: Omit<Process<T>, 'result'>;\n }>;\n};\n\n/**\n * This fetches from the callback endpoint.\n *\n * @param processId\n * @returns\n */\nconst processCallbackResultFetcher = <T,>(\n processId: string,\n processName: string,\n processCallbackUrl?: string,\n) =>\n fetch(`${processCallbackUrl}`, {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n },\n body: JSON.stringify({\n processId,\n processName,\n }),\n }).then((response) => response.json()) as unknown as Promise<T>;\n\ninterface ClientProcesses {\n registerProcess: (\n processId: string,\n dto: ProcessCallbackDto,\n metadata: ClientProcessMetadata,\n options?: ClientProcessOptions,\n ) => void;\n getProcess: <T>(processId: string) => ClientProcess<T> | null;\n filterProcesses: <T>(filter: string) => ClientProcess<T>[];\n invalidateBasedOnResult: <R>(\n filter: string,\n invalidator?: (result: R) => boolean,\n ) => Promise<void>;\n}\n\n/**\n * We need a context to keep track of all client processes.\n * This helps us manage the state of the toasts as well.\n */\nconst clientProcessesContext = createContext<ClientProcesses>(\n {} as ClientProcesses,\n);\n\n/**\n * This should be at the root layout.\n *\n * @context\n */\nexport const ClientProcessesProvider = ({\n children,\n}: {\n children: React.ReactNode;\n}) => {\n const queryClient = useQueryClient();\n const clientProcessAttempts = useRef<Record<string, number>>({});\n const [clientProcessDtos, setClientProcessDtos] = useState<\n Record<\n string,\n {\n dto: ProcessCallbackDto;\n metadata: ClientProcessMetadata;\n options?: ClientProcessOptions;\n }\n >\n >({});\n\n // Populates the state object with cached data on mount\n useEffect(() => {\n setClientProcessDtos((oldData) => {\n if (Object.keys(oldData).length > 0) return oldData;\n\n const cachedClientProcesses = queryClient\n .getQueryCache()\n .findAll({ queryKey: ['.__process'], exact: false });\n const seededClientProcesses: typeof oldData = {};\n\n for (const query of cachedClientProcesses) {\n const processId = query.queryKey[2];\n if (typeof processId !== 'string') continue;\n\n const data = query.state.data as ClientProcess<any>;\n if (!data?.dto || !data?.metadata) continue;\n\n seededClientProcesses[processId] = {\n dto: data.dto,\n metadata: data.metadata,\n options: data.options,\n };\n }\n\n return { ...oldData, ...seededClientProcesses };\n });\n }, [queryClient]);\n\n // The query for all the processes, pending and sht\n const clientProcesses = useQueries({\n queries: Object.entries(clientProcessDtos).map(\n ([processId, { dto, metadata, options }]) => ({\n staleTime: Infinity,\n gcTime: 3 * 60 * 1000,\n refetchOnWindowFocus: true,\n refetchIntervalInBackground: true,\n refetchOnMount: true,\n refetchOnReconnect: true,\n queryKey: ['.__process', metadata.filterKey, processId],\n queryFn: async () => {\n if (!processId) throw new Error('No processId');\n const { process } = await processStatusChecker(processId);\n\n // Handled! Return the proper response too\n if (process?.status === 'handled') {\n const result = await processCallbackResultFetcher(\n process.id,\n process.name,\n dto.processCallbackUrl,\n );\n\n // Log and call runner\n console.warn(`Handled ${process.name} (${process.id})!`);\n options?.onSuccess?.(process.id, process.name, result);\n\n return {\n dto,\n metadata,\n options,\n success: true,\n pending: false,\n result,\n };\n }\n\n // Pending or failed\n if (process?.status === 'pending') {\n console.log(`Running process ${process.name} (${process.id})...`);\n options?.onLoading?.(process.id, process.name);\n return { dto, metadata, options, pending: true };\n } else if (process?.status === 'failed') {\n console.error(\n `Failed ${process.name} (${process.id}) - ${process.error}.`,\n );\n\n return {\n dto,\n metadata,\n options,\n success: false,\n pending: false,\n error: process.error,\n };\n }\n\n // Return null since no response\n return { dto, metadata, options, pending: true };\n },\n\n // Returns the next interval length to use to wait for fetch\n refetchInterval: <T,>(query: Query) => {\n const data = query.state.data as ClientProcess<T>;\n if (data?.success !== undefined) return false;\n\n // If not defined, init to 0\n if (!clientProcessAttempts.current[processId])\n clientProcessAttempts.current[processId] = 0;\n\n const interval = Math.min(\n 500 * 2 ** clientProcessAttempts.current[processId],\n 10000,\n );\n\n clientProcessAttempts.current[processId] += 1;\n return interval;\n },\n }),\n ),\n });\n\n /**\n * Registers a new process to handle\n *\n * @param processId\n * @param processCallbackDto\n * @param options\n */\n const registerProcess = (\n processId: string,\n processCallbackDto: ProcessCallbackDto,\n metadata: ClientProcessMetadata,\n options?: ClientProcessOptions,\n ) => {\n setClientProcessDtos({\n ...clientProcessDtos,\n [processId]: { dto: processCallbackDto, metadata, options },\n });\n };\n\n /**\n * Retrieves a process\n *\n * @param processId\n * @returns\n */\n const getProcess = <T,>(processId: string) => {\n const clientProcess =\n clientProcesses.find(\n (clientProcess) => clientProcess.data?.dto?.processId === processId,\n )?.data ?? null;\n\n return (clientProcess as ClientProcess<T>) ?? null;\n };\n\n /**\n * Filters processes by the provided attribute.\n *\n * @param filterKey\n */\n const filterProcesses = <T,>(filterKey: string) => {\n return clientProcesses\n .filter((clientProcess) => !!clientProcess.data?.dto)\n .map((clientProcess) => clientProcess.data)\n .filter(\n (clientProcessData) =>\n clientProcessData?.metadata.filterKey === filterKey,\n ) as ClientProcess<T>[];\n };\n\n /**\n * Refreshes the cache and invalidates all related processes.\n */\n const invalidateBasedOnResult = async <R,>(\n filter: string,\n invalidator?: (result: R) => boolean,\n ) => {\n await queryClient.invalidateQueries({\n queryKey: ['.__process', filter],\n exact: false,\n });\n\n const cachedClientProcesses = queryClient\n .getQueryCache()\n .findAll({ queryKey: ['.__process', filter], exact: false });\n\n for (const query of cachedClientProcesses) {\n const processId = query.queryKey[2];\n if (typeof processId !== 'string') continue;\n\n const data = query.state.data as ClientProcess<any>;\n if (!data.result) continue;\n\n // If invalidator says we gotta clear the cache, we clear it\n if (invalidator && invalidator?.(data.result as R)) {\n queryClient.removeQueries({ queryKey: query.queryKey });\n }\n }\n };\n\n return (\n <clientProcessesContext.Provider\n value={{\n registerProcess,\n getProcess,\n filterProcesses,\n invalidateBasedOnResult,\n }}\n >\n {children}\n </clientProcessesContext.Provider>\n );\n};\n\n/**\n * A simpler version, where u just pass the async function that returns process data.\n *\n * @param caller\n * @returns\n */\nexport function useClientProcess<T, R>({\n caller,\n invalidator,\n filterKey,\n onLoading,\n onSuccess,\n onFailure,\n}: {\n caller?: (inputs: T) => Promise<ProcessResponse>;\n invalidator?: (result: R) => boolean;\n filterKey: string;\n onLoading?: (processId: string, processName: string) => void;\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\n onFailure?: (processId: string, processName: string, error?: string) => void;\n}) {\n const clientProcessesApi = useContext(clientProcessesContext);\n\n // Note that the exposed function doesn't return process ids\n // Client should be oblivious to those\n // Abstraction BABY!\n const wrappedCaller = async (\n inputs: T,\n metadata?: Record<string, string>,\n ) => {\n const response = await caller?.(inputs);\n if (response) {\n clientProcessesApi.registerProcess(\n response.processId,\n response,\n { filterKey, metadata },\n {\n onLoading,\n onSuccess,\n onFailure,\n },\n );\n\n return {\n id: response.processId,\n success: response.success,\n message: response.message,\n };\n }\n\n return {\n id: null,\n success: false,\n message: 'No `caller` specified for useClientProcess().',\n };\n };\n\n return {\n run: wrappedCaller,\n get: (id: string) => clientProcessesApi.getProcess(id),\n getAll: () => clientProcessesApi.filterProcesses(filterKey),\n getAllPending: () =>\n clientProcessesApi\n .filterProcesses(filterKey)\n .filter((process) => process.pending),\n getAllHandled: () =>\n clientProcessesApi\n .filterProcesses(filterKey)\n .filter((process) => process.success),\n getAllFailed: () =>\n clientProcessesApi\n .filterProcesses(filterKey)\n .filter((process) => !process.success),\n refreshCache: () =>\n clientProcessesApi.invalidateBasedOnResult(filterKey, invalidator),\n };\n}\n"],"names":[],"mappings":";;;;;;AAoDA;;;;;AAKG;AACH;AACE;AAAgB;AAChB;AACE;AACA;AACE;AACD;AACF;AAGH;AAEA;;;;;AAKG;AACH;AAMI;AACA;AACE;AACD;AACD;;;;AAID;AAiBH;;;AAGG;AACH;AAIA;;;;AAIG;;AAMD;AACA;;;;AAcE;;AACuC;;AAGlC;AACA;;AAGH;;;;AAIE;;;;;;;;;AAUF;AACF;AACF;;;;AAMM;AACA;AACA;AACA;AACA;AACA;;;AAGE;AAAgB;;;AAIhB;AACE;;AAOA;AACA;;;;;AAME;AACA;;;;;AAMJ;AACE;AACA;;;AAEK;AACL;;;;;AAQE;AACA;;;;;;;;AAUN;AACE;AACA;AAAiC;;AAGjC;AACE;AAEF;AAKA;AACA;;AAEH;AAEJ;AAED;;;;;;AAMG;;AAOD;AACE;;AAED;AACH;AAEA;;;;;AAKG;AACH;;;AAOA;AAEA;;;;AAIG;AACH;AACE;AACG;;AAEA;AAIL;AAEA;;AAEG;;;AAMC;AACA;AACD;;AAGE;AACA;AAEH;;;;AAIE;;;;;;;;AAQJ;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAeJ;;;;;;;AAWI;;;;AAQG;;;;;;;;AAWH;AACA;AACA;;AAEJ;;AAGE;;;AAGA;;;AAIA;;;AAIA;;;;;AAOJ;;"}
|
|
1
|
+
{"version":3,"file":"useClientProcess.js","sources":["../../src/hooks/useClientProcess.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { useState, useRef, createContext, useContext, useEffect } from 'react';\r\nimport { Query, useQueries, useQueryClient } from '@tanstack/react-query';\r\nimport { config } from '../config';\r\n\r\ntype ProcessStatus = 'pending' | 'handled' | 'failed';\r\n\r\ninterface Process<T> {\r\n id: string;\r\n name: string;\r\n status: ProcessStatus;\r\n result?: T;\r\n error?: string;\r\n timestamp: string;\r\n}\r\n\r\ninterface ClientProcess<T> {\r\n dto?: ProcessCallbackDto;\r\n metadata?: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n result?: T;\r\n success?: boolean;\r\n pending?: boolean;\r\n error?: string;\r\n}\r\n\r\nexport interface ProcessCallbackDto {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n}\r\n\r\nexport interface ClientProcessOptions {\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}\r\n\r\nexport interface ClientProcessMetadata {\r\n filterKey: string;\r\n metadata?: Record<string, string>;\r\n}\r\n\r\nexport interface ProcessResponse {\r\n processId: string;\r\n processName: string;\r\n processCallbackUrl: string;\r\n success: boolean;\r\n message?: string;\r\n}\r\n\r\n/**\r\n * The orchestrator service to get the process from.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processStatusChecker = <T,>(processId: string) => {\r\n if (!processId) return { process: null };\r\n return fetch(`${config.orchestratorApi}/${encodeURIComponent(processId)}`, {\r\n method: 'GET',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n }).then((response) => response.json()) as unknown as Promise<{\r\n process: Omit<Process<T>, 'result'>;\r\n }>;\r\n};\r\n\r\n/**\r\n * This fetches from the callback endpoint.\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\nconst processCallbackResultFetcher = <T,>(\r\n processId: string,\r\n processName: string,\r\n processCallbackUrl?: string,\r\n) =>\r\n fetch(`${processCallbackUrl}`, {\r\n method: 'POST',\r\n headers: {\r\n 'Content-Type': 'application/json',\r\n },\r\n body: JSON.stringify({\r\n processId,\r\n processName,\r\n }),\r\n }).then((response) => response.json()) as unknown as Promise<T>;\r\n\r\ninterface ClientProcesses {\r\n registerProcess: (\r\n processId: string,\r\n dto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => void;\r\n getProcess: <T>(processId: string) => ClientProcess<T> | null;\r\n filterProcesses: <T>(filter: string) => ClientProcess<T>[];\r\n invalidateBasedOnResult: <R>(\r\n filter: string,\r\n invalidator?: (result: R) => boolean,\r\n ) => Promise<void>;\r\n}\r\n\r\n/**\r\n * We need a context to keep track of all client processes.\r\n * This helps us manage the state of the toasts as well.\r\n */\r\nconst clientProcessesContext = createContext<ClientProcesses>(\r\n {} as ClientProcesses,\r\n);\r\n\r\n/**\r\n * This should be at the root layout.\r\n *\r\n * @context\r\n */\r\nexport const ClientProcessesProvider = ({\r\n children,\r\n}: {\r\n children: React.ReactNode;\r\n}) => {\r\n const queryClient = useQueryClient();\r\n const clientProcessAttempts = useRef<Record<string, number>>({});\r\n const [clientProcessDtos, setClientProcessDtos] = useState<\r\n Record<\r\n string,\r\n {\r\n dto: ProcessCallbackDto;\r\n metadata: ClientProcessMetadata;\r\n options?: ClientProcessOptions;\r\n }\r\n >\r\n >({});\r\n\r\n // Populates the state object with cached data on mount\r\n useEffect(() => {\r\n setClientProcessDtos((oldData) => {\r\n if (Object.keys(oldData).length > 0) return oldData;\r\n\r\n const cachedClientProcesses = queryClient\r\n .getQueryCache()\r\n .findAll({ queryKey: ['.__process'], exact: false });\r\n const seededClientProcesses: typeof oldData = {};\r\n\r\n for (const query of cachedClientProcesses) {\r\n const processId = query.queryKey[2];\r\n if (typeof processId !== 'string') continue;\r\n\r\n const data = query.state.data as ClientProcess<any>;\r\n if (!data?.dto || !data?.metadata) continue;\r\n\r\n seededClientProcesses[processId] = {\r\n dto: data.dto,\r\n metadata: data.metadata,\r\n options: data.options,\r\n };\r\n }\r\n\r\n return { ...oldData, ...seededClientProcesses };\r\n });\r\n }, [queryClient]);\r\n\r\n // The query for all the processes, pending and sht\r\n const clientProcesses = useQueries({\r\n queries: Object.entries(clientProcessDtos).map(\r\n ([processId, { dto, metadata, options }]) => ({\r\n staleTime: Infinity,\r\n gcTime: 3 * 60 * 1000,\r\n refetchOnWindowFocus: true,\r\n refetchIntervalInBackground: true,\r\n refetchOnMount: true,\r\n refetchOnReconnect: true,\r\n queryKey: ['.__process', metadata.filterKey, processId],\r\n queryFn: async () => {\r\n if (!processId) throw new Error('No processId');\r\n const { process } = await processStatusChecker(processId);\r\n\r\n // Handled! Return the proper response too\r\n if (process?.status === 'handled') {\r\n const result = await processCallbackResultFetcher(\r\n process.id,\r\n process.name,\r\n dto.processCallbackUrl,\r\n );\r\n\r\n // Log and call runner\r\n console.warn(`Handled ${process.name} (${process.id})!`);\r\n options?.onSuccess?.(process.id, process.name, result);\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: true,\r\n pending: false,\r\n result,\r\n };\r\n }\r\n\r\n // Pending or failed\r\n if (process?.status === 'pending') {\r\n console.log(`Running process ${process.name} (${process.id})...`);\r\n options?.onLoading?.(process.id, process.name);\r\n return { dto, metadata, options, pending: true };\r\n } else if (process?.status === 'failed') {\r\n console.error(\r\n `Failed ${process.name} (${process.id}) - ${process.error}.`,\r\n );\r\n\r\n return {\r\n dto,\r\n metadata,\r\n options,\r\n success: false,\r\n pending: false,\r\n error: process.error,\r\n };\r\n }\r\n\r\n // Return null since no response\r\n return { dto, metadata, options, pending: true };\r\n },\r\n\r\n // Returns the next interval length to use to wait for fetch\r\n refetchInterval: <T,>(query: Query) => {\r\n const data = query.state.data as ClientProcess<T>;\r\n if (data?.success !== undefined) return false;\r\n\r\n // If not defined, init to 0\r\n if (!clientProcessAttempts.current[processId])\r\n clientProcessAttempts.current[processId] = 0;\r\n\r\n const interval = Math.min(\r\n 500 * 2 ** clientProcessAttempts.current[processId],\r\n 10000,\r\n );\r\n\r\n clientProcessAttempts.current[processId] += 1;\r\n return interval;\r\n },\r\n }),\r\n ),\r\n });\r\n\r\n /**\r\n * Registers a new process to handle\r\n *\r\n * @param processId\r\n * @param processCallbackDto\r\n * @param options\r\n */\r\n const registerProcess = (\r\n processId: string,\r\n processCallbackDto: ProcessCallbackDto,\r\n metadata: ClientProcessMetadata,\r\n options?: ClientProcessOptions,\r\n ) => {\r\n setClientProcessDtos({\r\n ...clientProcessDtos,\r\n [processId]: { dto: processCallbackDto, metadata, options },\r\n });\r\n };\r\n\r\n /**\r\n * Retrieves a process\r\n *\r\n * @param processId\r\n * @returns\r\n */\r\n const getProcess = <T,>(processId: string) => {\r\n const clientProcess =\r\n clientProcesses.find(\r\n (clientProcess) => clientProcess.data?.dto?.processId === processId,\r\n )?.data ?? null;\r\n\r\n return (clientProcess as ClientProcess<T>) ?? null;\r\n };\r\n\r\n /**\r\n * Filters processes by the provided attribute.\r\n *\r\n * @param filterKey\r\n */\r\n const filterProcesses = <T,>(filterKey: string) => {\r\n return clientProcesses\r\n .filter((clientProcess) => !!clientProcess.data?.dto)\r\n .map((clientProcess) => clientProcess.data)\r\n .filter(\r\n (clientProcessData) =>\r\n clientProcessData?.metadata.filterKey === filterKey,\r\n ) as ClientProcess<T>[];\r\n };\r\n\r\n /**\r\n * Refreshes the cache and invalidates all related processes.\r\n */\r\n const invalidateBasedOnResult = async <R,>(\r\n filter: string,\r\n invalidator?: (result: R) => boolean,\r\n ) => {\r\n await queryClient.invalidateQueries({\r\n queryKey: ['.__process', filter],\r\n exact: false,\r\n });\r\n\r\n const cachedClientProcesses = queryClient\r\n .getQueryCache()\r\n .findAll({ queryKey: ['.__process', filter], exact: false });\r\n\r\n for (const query of cachedClientProcesses) {\r\n const processId = query.queryKey[2];\r\n if (typeof processId !== 'string') continue;\r\n\r\n const data = query.state.data as ClientProcess<any>;\r\n if (!data.result) continue;\r\n\r\n // If invalidator says we gotta clear the cache, we clear it\r\n if (invalidator && invalidator?.(data.result as R)) {\r\n queryClient.removeQueries({ queryKey: query.queryKey });\r\n }\r\n }\r\n };\r\n\r\n return (\r\n <clientProcessesContext.Provider\r\n value={{\r\n registerProcess,\r\n getProcess,\r\n filterProcesses,\r\n invalidateBasedOnResult,\r\n }}\r\n >\r\n {children}\r\n </clientProcessesContext.Provider>\r\n );\r\n};\r\n\r\n/**\r\n * A simpler version, where u just pass the async function that returns process data.\r\n *\r\n * @param caller\r\n * @returns\r\n */\r\nexport function useClientProcess<T, R>({\r\n caller,\r\n invalidator,\r\n filterKey,\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n}: {\r\n caller?: (inputs: T) => Promise<ProcessResponse>;\r\n invalidator?: (result: R) => boolean;\r\n filterKey: string;\r\n onLoading?: (processId: string, processName: string) => void;\r\n onSuccess?: <R>(processId: string, processName: string, result: R) => void;\r\n onFailure?: (processId: string, processName: string, error?: string) => void;\r\n}) {\r\n const clientProcessesApi = useContext(clientProcessesContext);\r\n\r\n // Note that the exposed function doesn't return process ids\r\n // Client should be oblivious to those\r\n // Abstraction BABY!\r\n const wrappedCaller = async (\r\n inputs: T,\r\n metadata?: Record<string, string>,\r\n ) => {\r\n const response = await caller?.(inputs);\r\n if (response) {\r\n clientProcessesApi.registerProcess(\r\n response.processId,\r\n response,\r\n { filterKey, metadata },\r\n {\r\n onLoading,\r\n onSuccess,\r\n onFailure,\r\n },\r\n );\r\n\r\n return {\r\n id: response.processId,\r\n success: response.success,\r\n message: response.message,\r\n };\r\n }\r\n\r\n return {\r\n id: null,\r\n success: false,\r\n message: 'No `caller` specified for useClientProcess().',\r\n };\r\n };\r\n\r\n return {\r\n run: wrappedCaller,\r\n get: (id: string) => clientProcessesApi.getProcess(id),\r\n getAll: () => clientProcessesApi.filterProcesses(filterKey),\r\n getAllPending: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.pending),\r\n getAllHandled: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => process.success),\r\n getAllFailed: () =>\r\n clientProcessesApi\r\n .filterProcesses(filterKey)\r\n .filter((process) => !process.success),\r\n refreshCache: () =>\r\n clientProcessesApi.invalidateBasedOnResult(filterKey, invalidator),\r\n };\r\n}\r\n"],"names":[],"mappings":";;;;;;AAoDA;;;;;AAKG;AACH;AACE;AAAgB;AAChB;AACE;AACA;AACE;AACD;AACF;AAGH;AAEA;;;;;AAKG;AACH;AAMI;AACA;AACE;AACD;AACD;;;;AAID;AAiBH;;;AAGG;AACH;AAIA;;;;AAIG;;AAMD;AACA;;;;AAcE;;AACuC;;AAGlC;AACA;;AAGH;;;;AAIE;;;;;;;;;AAUF;AACF;AACF;;;;AAMM;AACA;AACA;AACA;AACA;AACA;;;AAGE;AAAgB;;;AAIhB;AACE;;AAOA;AACA;;;;;AAME;AACA;;;;;AAMJ;AACE;AACA;;;AAEK;AACL;;;;;AAQE;AACA;;;;;;;;AAUN;AACE;AACA;AAAiC;;AAGjC;AACE;AAEF;AAKA;AACA;;AAEH;AAEJ;AAED;;;;;;AAMG;;AAOD;AACE;;AAED;AACH;AAEA;;;;;AAKG;AACH;;;AAOA;AAEA;;;;AAIG;AACH;AACE;AACG;;AAEA;AAIL;AAEA;;AAEG;;;AAMC;AACA;AACD;;AAGE;AACA;AAEH;;;;AAIE;;;;;;;;AAQJ;AAEA;;;;;;AAYF;AAEA;;;;;AAKG;AACG;AAeJ;;;;;;;AAWI;;;;AAQG;;;;;;;;AAWH;AACA;AACA;;AAEJ;;AAGE;;;AAGA;;;AAIA;;;AAIA;;;;;AAOJ;;"}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export type MQJobStatus = 'queued' | 'processing' | 'done' | 'failed';
|
|
2
|
+
export interface MQJob<T = unknown> {
|
|
3
|
+
id: string;
|
|
4
|
+
type: string;
|
|
5
|
+
status: MQJobStatus;
|
|
6
|
+
progress?: {
|
|
7
|
+
total: number;
|
|
8
|
+
done: number;
|
|
9
|
+
failed: number;
|
|
10
|
+
};
|
|
11
|
+
result?: T;
|
|
12
|
+
error?: string;
|
|
13
|
+
}
|
|
14
|
+
interface MQJobsConfig {
|
|
15
|
+
/**
|
|
16
|
+
* Fetches the current state of a job. The package knows no URL and no
|
|
17
|
+
* response envelope — the consuming app owns both (plan §5.2). Reject/throw
|
|
18
|
+
* on a transport or not-found failure; React Query's own retry handles it.
|
|
19
|
+
*/
|
|
20
|
+
poll: (jobId: string) => Promise<MQJob>;
|
|
21
|
+
}
|
|
22
|
+
/** Mount once near the root, alongside the app's other data providers. */
|
|
23
|
+
export declare const MQJobsProvider: ({ poll, children, }: MQJobsConfig & {
|
|
24
|
+
children: React.ReactNode;
|
|
25
|
+
}) => import("react/jsx-runtime").JSX.Element;
|
|
26
|
+
/**
|
|
27
|
+
* Polls a single `internal.mq_jobs` row to completion. Replaces
|
|
28
|
+
* `useClientProcess` for anything queued through the broker: no
|
|
29
|
+
* callback-URL dance, the result comes back inline on the job itself once
|
|
30
|
+
* `status` is terminal.
|
|
31
|
+
*/
|
|
32
|
+
export declare function useMQJob<T = unknown>(jobId?: string | null): {
|
|
33
|
+
job: MQJob<T> | undefined;
|
|
34
|
+
status: MQJobStatus | undefined;
|
|
35
|
+
result: T | undefined;
|
|
36
|
+
error: string | undefined;
|
|
37
|
+
isPending: boolean;
|
|
38
|
+
isDone: boolean;
|
|
39
|
+
isFailed: boolean;
|
|
40
|
+
};
|
|
41
|
+
/**
|
|
42
|
+
* Polls a dynamic set of `internal.mq_jobs` rows to completion — for a
|
|
43
|
+
* feature that can have several jobs in flight at once (e.g. a list of
|
|
44
|
+
* pending/handled items). Built on `useQueries` directly rather than
|
|
45
|
+
* composing `useMQJob` in a loop, since the number of tracked ids changes
|
|
46
|
+
* over time and that would violate the rules of hooks.
|
|
47
|
+
*/
|
|
48
|
+
export declare function useMQJobs<T = unknown>(jobIds: readonly string[]): {
|
|
49
|
+
job: MQJob<T> | undefined;
|
|
50
|
+
status: MQJobStatus | undefined;
|
|
51
|
+
result: T | undefined;
|
|
52
|
+
error: string | undefined;
|
|
53
|
+
isPending: boolean;
|
|
54
|
+
isDone: boolean;
|
|
55
|
+
isFailed: boolean;
|
|
56
|
+
jobId: string;
|
|
57
|
+
}[];
|
|
58
|
+
export {};
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import { jsx } from 'react/jsx-runtime';
|
|
3
|
+
import { createContext, useRef, useContext } from 'react';
|
|
4
|
+
import { useQuery, useQueries } from '@tanstack/react-query';
|
|
5
|
+
|
|
6
|
+
const MQJobsContext = createContext(null);
|
|
7
|
+
/** Mount once near the root, alongside the app's other data providers. */
|
|
8
|
+
const MQJobsProvider = ({ poll, children, }) => (jsx(MQJobsContext.Provider, { value: { poll }, children: children }));
|
|
9
|
+
const useMQJobsConfig = (caller) => {
|
|
10
|
+
const ctx = useContext(MQJobsContext);
|
|
11
|
+
if (!ctx)
|
|
12
|
+
throw new Error(`${caller}() must be used within a <MQJobsProvider>.`);
|
|
13
|
+
return ctx;
|
|
14
|
+
};
|
|
15
|
+
const BASE_INTERVAL_MS = 2000;
|
|
16
|
+
const MAX_INTERVAL_MS = 10000;
|
|
17
|
+
/** Capped backoff keyed per job id, so each job restarts its own count. */
|
|
18
|
+
const nextInterval = (attempts, key, status) => {
|
|
19
|
+
if (status === 'done' || status === 'failed')
|
|
20
|
+
return false;
|
|
21
|
+
const attempt = attempts.current[key] ?? 0;
|
|
22
|
+
attempts.current[key] = attempt + 1;
|
|
23
|
+
return Math.min(BASE_INTERVAL_MS * 1.5 ** attempt, MAX_INTERVAL_MS);
|
|
24
|
+
};
|
|
25
|
+
const summarize = (job) => ({
|
|
26
|
+
job,
|
|
27
|
+
status: job?.status,
|
|
28
|
+
result: job?.result,
|
|
29
|
+
error: job?.error,
|
|
30
|
+
isPending: !job || job.status === 'queued' || job.status === 'processing',
|
|
31
|
+
isDone: job?.status === 'done',
|
|
32
|
+
isFailed: job?.status === 'failed',
|
|
33
|
+
});
|
|
34
|
+
/**
|
|
35
|
+
* Polls a single `internal.mq_jobs` row to completion. Replaces
|
|
36
|
+
* `useClientProcess` for anything queued through the broker: no
|
|
37
|
+
* callback-URL dance, the result comes back inline on the job itself once
|
|
38
|
+
* `status` is terminal.
|
|
39
|
+
*/
|
|
40
|
+
function useMQJob(jobId) {
|
|
41
|
+
const { poll } = useMQJobsConfig('useMQJob');
|
|
42
|
+
const attempts = useRef({});
|
|
43
|
+
const query = useQuery({
|
|
44
|
+
queryKey: ['.__mq_job', jobId],
|
|
45
|
+
// `poll`'s result field is caller-typed via `T`; the transport itself
|
|
46
|
+
// doesn't know or care what shape a given job's result has.
|
|
47
|
+
queryFn: () => poll(jobId),
|
|
48
|
+
enabled: !!jobId,
|
|
49
|
+
staleTime: Infinity,
|
|
50
|
+
refetchOnWindowFocus: true,
|
|
51
|
+
refetchIntervalInBackground: true,
|
|
52
|
+
refetchInterval: (query) => nextInterval(attempts, jobId ?? '', query.state.data?.status),
|
|
53
|
+
});
|
|
54
|
+
return summarize(query.data);
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Polls a dynamic set of `internal.mq_jobs` rows to completion — for a
|
|
58
|
+
* feature that can have several jobs in flight at once (e.g. a list of
|
|
59
|
+
* pending/handled items). Built on `useQueries` directly rather than
|
|
60
|
+
* composing `useMQJob` in a loop, since the number of tracked ids changes
|
|
61
|
+
* over time and that would violate the rules of hooks.
|
|
62
|
+
*/
|
|
63
|
+
function useMQJobs(jobIds) {
|
|
64
|
+
const { poll } = useMQJobsConfig('useMQJobs');
|
|
65
|
+
const attempts = useRef({});
|
|
66
|
+
const queries = useQueries({
|
|
67
|
+
queries: jobIds.map((jobId) => ({
|
|
68
|
+
queryKey: ['.__mq_job', jobId],
|
|
69
|
+
queryFn: () => poll(jobId),
|
|
70
|
+
staleTime: Infinity,
|
|
71
|
+
refetchOnWindowFocus: true,
|
|
72
|
+
refetchIntervalInBackground: true,
|
|
73
|
+
refetchInterval: (query) => nextInterval(attempts, jobId, query.state.data?.status),
|
|
74
|
+
})),
|
|
75
|
+
});
|
|
76
|
+
return jobIds.map((jobId, index) => ({
|
|
77
|
+
jobId,
|
|
78
|
+
...summarize(queries[index]?.data),
|
|
79
|
+
}));
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export { MQJobsProvider, useMQJob, useMQJobs };
|
|
83
|
+
//# sourceMappingURL=useMQJob.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"useMQJob.js","sources":["../../src/hooks/useMQJob.tsx"],"sourcesContent":["'use client';\r\n\r\nimport { createContext, useContext, useRef } from 'react';\r\nimport { useQueries, useQuery, Query } from '@tanstack/react-query';\r\n\r\nexport type MQJobStatus = 'queued' | 'processing' | 'done' | 'failed';\r\n\r\nexport interface MQJob<T = unknown> {\r\n id: string;\r\n type: string;\r\n status: MQJobStatus;\r\n progress?: { total: number; done: number; failed: number };\r\n result?: T;\r\n error?: string;\r\n}\r\n\r\ninterface MQJobsConfig {\r\n /**\r\n * Fetches the current state of a job. The package knows no URL and no\r\n * response envelope — the consuming app owns both (plan §5.2). Reject/throw\r\n * on a transport or not-found failure; React Query's own retry handles it.\r\n */\r\n poll: (jobId: string) => Promise<MQJob>;\r\n}\r\n\r\nconst MQJobsContext = createContext<MQJobsConfig | null>(null);\r\n\r\n/** Mount once near the root, alongside the app's other data providers. */\r\nexport const MQJobsProvider = ({\r\n poll,\r\n children,\r\n}: MQJobsConfig & { children: React.ReactNode }) => (\r\n <MQJobsContext.Provider value={{ poll }}>{children}</MQJobsContext.Provider>\r\n);\r\n\r\nconst useMQJobsConfig = (caller: string): MQJobsConfig => {\r\n const ctx = useContext(MQJobsContext);\r\n if (!ctx)\r\n throw new Error(`${caller}() must be used within a <MQJobsProvider>.`);\r\n return ctx;\r\n};\r\n\r\nconst BASE_INTERVAL_MS = 2000;\r\nconst MAX_INTERVAL_MS = 10000;\r\n\r\n/** Capped backoff keyed per job id, so each job restarts its own count. */\r\nconst nextInterval = (\r\n attempts: { current: Record<string, number> },\r\n key: string,\r\n status: MQJobStatus | undefined,\r\n): number | false => {\r\n if (status === 'done' || status === 'failed') return false;\r\n const attempt = attempts.current[key] ?? 0;\r\n attempts.current[key] = attempt + 1;\r\n return Math.min(BASE_INTERVAL_MS * 1.5 ** attempt, MAX_INTERVAL_MS);\r\n};\r\n\r\nconst summarize = <T,>(job: MQJob<T> | undefined) => ({\r\n job,\r\n status: job?.status,\r\n result: job?.result,\r\n error: job?.error,\r\n isPending: !job || job.status === 'queued' || job.status === 'processing',\r\n isDone: job?.status === 'done',\r\n isFailed: job?.status === 'failed',\r\n});\r\n\r\n/**\r\n * Polls a single `internal.mq_jobs` row to completion. Replaces\r\n * `useClientProcess` for anything queued through the broker: no\r\n * callback-URL dance, the result comes back inline on the job itself once\r\n * `status` is terminal.\r\n */\r\nexport function useMQJob<T = unknown>(jobId?: string | null) {\r\n const { poll } = useMQJobsConfig('useMQJob');\r\n const attempts = useRef<Record<string, number>>({});\r\n\r\n const query = useQuery<MQJob<T>>({\r\n queryKey: ['.__mq_job', jobId],\r\n // `poll`'s result field is caller-typed via `T`; the transport itself\r\n // doesn't know or care what shape a given job's result has.\r\n queryFn: () => poll(jobId as string) as Promise<MQJob<T>>,\r\n enabled: !!jobId,\r\n staleTime: Infinity,\r\n refetchOnWindowFocus: true,\r\n refetchIntervalInBackground: true,\r\n refetchInterval: (query: Query<MQJob<T>>) =>\r\n nextInterval(attempts, jobId ?? '', query.state.data?.status),\r\n });\r\n\r\n return summarize(query.data);\r\n}\r\n\r\n/**\r\n * Polls a dynamic set of `internal.mq_jobs` rows to completion — for a\r\n * feature that can have several jobs in flight at once (e.g. a list of\r\n * pending/handled items). Built on `useQueries` directly rather than\r\n * composing `useMQJob` in a loop, since the number of tracked ids changes\r\n * over time and that would violate the rules of hooks.\r\n */\r\nexport function useMQJobs<T = unknown>(jobIds: readonly string[]) {\r\n const { poll } = useMQJobsConfig('useMQJobs');\r\n const attempts = useRef<Record<string, number>>({});\r\n\r\n const queries = useQueries({\r\n queries: jobIds.map((jobId) => ({\r\n queryKey: ['.__mq_job', jobId],\r\n queryFn: () => poll(jobId) as Promise<MQJob<T>>,\r\n staleTime: Infinity,\r\n refetchOnWindowFocus: true,\r\n refetchIntervalInBackground: true,\r\n refetchInterval: (query: Query<MQJob<T>>) =>\r\n nextInterval(attempts, jobId, query.state.data?.status),\r\n })),\r\n });\r\n\r\n return jobIds.map((jobId, index) => ({\r\n jobId,\r\n ...summarize(queries[index]?.data as MQJob<T> | undefined),\r\n }));\r\n}\r\n"],"names":[],"mappings":";;;;;AAyBA;AAEA;AACO;AAOP;AACE;AACA;AACE;AACF;AACF;AAEA;AACA;AAEA;AACA;AAKE;AAA8C;;;AAG9C;AACF;AAEA;;;;;AAKE;AACA;AACA;AACD;AAED;;;;;AAKG;AACG;;AAEJ;;AAGE;;;AAGA;;AAEA;AACA;AACA;;AAGD;AAED;AACF;AAEA;;;;;;AAMG;AACG;;AAEJ;;;AAII;AACA;AACA;AACA;AACA;AACA;AAED;AACF;;;;AAKA;AACH;;"}
|
package/dist/index.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
export { config, configure } from './config.js';
|
|
2
2
|
export { cn } from './utils.js';
|
|
3
3
|
export { ClientProcessesProvider, useClientProcess } from './hooks/useClientProcess.js';
|
|
4
|
+
export { MQJobsProvider, useMQJob, useMQJobs } from './hooks/useMQJob.js';
|
|
4
5
|
export { Button, buttonVariants } from './components/ui/button.js';
|
|
5
6
|
export { Accordion, AccordionContent, AccordionItem, AccordionTrigger } from './components/ui/accordion.js';
|
|
6
7
|
export { AppHeader } from './components/ui/app-header.js';
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;"}
|