{"_id":"@akhdigital/reap-cms-sdk","name":"@akhdigital/reap-cms-sdk","dist-tags":{"latest":"1.0.0"},"versions":{"1.0.0":{"name":"@akhdigital/reap-cms-sdk","version":"1.0.0","type":"module","description":"TypeScript SDK for Reap CMS headless API","main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","bin":{"reap":"dist/cli/index.js"},"exports":{".":{"import":{"types":"./dist/index.d.ts","default":"./dist/index.js"},"require":{"types":"./dist/index.d.cts","default":"./dist/index.cjs"}},"./react":{"import":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"require":{"types":"./dist/react.d.cts","default":"./dist/react.cjs"}},"./hooks":{"import":{"types":"./dist/react.d.ts","default":"./dist/react.js"},"require":{"types":"./dist/react.d.cts","default":"./dist/react.cjs"}},"./scanner":{"import":{"types":"./dist/scanner.d.ts","default":"./dist/scanner.js"},"require":{"types":"./dist/scanner.d.cts","default":"./dist/scanner.cjs"}}},"sideEffects":false,"scripts":{"build":"tsup","dev":"tsup --watch","typecheck":"tsc --noEmit","prepublishOnly":"npm run build"},"peerDependencies":{"react":">=17.0.0"},"peerDependenciesMeta":{"react":{"optional":true}},"dependencies":{"@astrojs/compiler":"^2.10.3","commander":"^12.1.0","glob":"^10.4.5"},"devDependencies":{"@types/node":"^20.14.0","@types/react":"^18.2.0","react":"^18.2.0","tsup":"^8.0.0","typescript":"^5.3.0"},"keywords":["cms","headless","typescript","react","astro","sdk","reap","scanner","cli"],"license":"MIT","repository":{"type":"git","url":"git+https://github.com/reap/cms-sdk.git"},"_id":"@akhdigital/reap-cms-sdk@1.0.0","gitHead":"947418dd5fbc6478f915f1ffb1aef3aa852569bf","bugs":{"url":"https://github.com/reap/cms-sdk/issues"},"homepage":"https://github.com/reap/cms-sdk#readme","_nodeVersion":"22.20.0","_npmVersion":"10.9.1","dist":{"integrity":"sha512-z10zRMIpF2VtNM6rOluNApuduKxVNygzLVV2JAPVqH+ENVIZolcgX4HkQDoZ2fBDF/ih4VE/6QR4ZVODxgFSeg==","shasum":"aa93b0a117ab4d653ec0ea104fff42965f156c18","tarball":"https://registry.npmjs.org/@akhdigital/reap-cms-sdk/-/reap-cms-sdk-1.0.0.tgz","fileCount":24,"unpackedSize":789022,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBdIG0gzLKCMF/maGhKKsI41I/7lp+psSnl9pMzTWB+TAiEAnJjItDzIt9HYywauW+JT0cDkhUFoXqRUtK9Q/9fMNB0="}]},"_npmUser":{"name":"akhdigital","email":"michael@akhdigital.com"},"directories":{},"maintainers":[{"name":"akhdigital","email":"michael@akhdigital.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/reap-cms-sdk_1.0.0_1766002878987_0.8968327154173776"},"_hasShrinkwrap":false}},"time":{"created":"2025-12-17T20:21:18.859Z","1.0.0":"2025-12-17T20:21:19.180Z","modified":"2025-12-17T20:21:19.529Z"},"maintainers":[{"name":"akhdigital","email":"michael@akhdigital.com"}],"description":"TypeScript SDK for Reap CMS headless API","homepage":"https://github.com/reap/cms-sdk#readme","keywords":["cms","headless","typescript","react","astro","sdk","reap","scanner","cli"],"repository":{"type":"git","url":"git+https://github.com/reap/cms-sdk.git"},"bugs":{"url":"https://github.com/reap/cms-sdk/issues"},"license":"MIT","readme":"# @reap/cms-sdk\r\n\r\nTypeScript SDK for Reap CMS headless API with React hooks support.\r\n\r\n## Installation\r\n\r\n```bash\r\nnpm install @reap/cms-sdk\r\n```\r\n\r\n## Quick Start\r\n\r\n### Basic Usage (Astro, Node, any JS environment)\r\n\r\n```typescript\r\nimport { ReapClient } from '@reap/cms-sdk';\r\n\r\nconst cms = new ReapClient({\r\n  baseUrl: 'https://your-project.supabase.co/functions/v1',\r\n  apiKey: 'your-api-key',\r\n});\r\n\r\n// Get all content\r\nconst content = await cms.getContent();\r\n\r\n// Get content for a specific page\r\nconst homeContent = await cms.getContentByPage('home');\r\n\r\n// Get typed content instance\r\ninterface HeroData {\r\n  title: string;\r\n  subtitle?: string;\r\n  background_image?: string;\r\n}\r\nconst hero = await cms.getContentInstance<HeroData>('sections', 'hero');\r\nconsole.log(hero.data.title);\r\n```\r\n\r\n### React Usage\r\n\r\n```tsx\r\nimport { ReapClient, ReapProvider, useContent, useGlobals } from '@reap/cms-sdk/react';\r\n\r\n// Create client\r\nconst client = new ReapClient({\r\n  baseUrl: import.meta.env.VITE_REAP_API_URL,\r\n  apiKey: import.meta.env.VITE_REAP_API_KEY,\r\n});\r\n\r\n// Wrap your app\r\nfunction App() {\r\n  return (\r\n    <ReapProvider client={client}>\r\n      <HomePage />\r\n    </ReapProvider>\r\n  );\r\n}\r\n\r\n// Use hooks in components\r\nfunction HomePage() {\r\n  const { data: globals, isLoading: globalsLoading } = useGlobals();\r\n  const { data: content, isLoading: contentLoading } = useContentByPage('home');\r\n\r\n  if (globalsLoading || contentLoading) {\r\n    return <div>Loading...</div>;\r\n  }\r\n\r\n  return (\r\n    <div>\r\n      <header>\r\n        <img src={globals?.logo_url} alt={globals?.name} />\r\n      </header>\r\n      {content?.map(section => (\r\n        <Section key={section.id} data={section} />\r\n      ))}\r\n    </div>\r\n  );\r\n}\r\n```\r\n\r\n## API Reference\r\n\r\n### ReapClient\r\n\r\n#### Constructor\r\n\r\n```typescript\r\nnew ReapClient({\r\n  baseUrl: string;   // Supabase functions URL\r\n  apiKey: string;    // Your API key\r\n  timeout?: number;  // Request timeout in ms (default: 10000)\r\n})\r\n```\r\n\r\n#### Content Methods\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `getContent<T>()` | Get all published content |\r\n| `getContentByPage<T>(pageSlug)` | Get content for a specific page |\r\n| `getContentByType<T>(typeSlug)` | Get content by type |\r\n| `getContentInstance<T>(typeSlug, instanceSlug)` | Get single content instance |\r\n| `getContentBulk(endpoints)` | Fetch multiple content items in one request |\r\n\r\n#### Forms Methods\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `getForms()` | Get all forms with fields and reCAPTCHA key |\r\n| `submitForm(formId, data, recaptchaToken)` | Submit a form |\r\n\r\n#### Other Methods\r\n\r\n| Method | Description |\r\n|--------|-------------|\r\n| `getGlobals()` | Get site configuration (branding, contact, meta tags) |\r\n| `getSpecials()` | Get active specials/promotions |\r\n\r\n### React Hooks\r\n\r\nAll hooks return a consistent state object:\r\n\r\n```typescript\r\n{\r\n  data: T | null;\r\n  error: Error | null;\r\n  isLoading: boolean;\r\n  isError: boolean;\r\n  isSuccess: boolean;\r\n  isFetching: boolean;\r\n  refetch: () => Promise<void>;\r\n}\r\n```\r\n\r\n#### Content Hooks\r\n\r\n```typescript\r\n// All content\r\nconst { data } = useContent<T>();\r\n\r\n// Content by page\r\nconst { data } = useContentByPage<T>('home');\r\n\r\n// Content by type\r\nconst { data } = useContentByType<T>('floorplans');\r\n\r\n// Single content instance\r\nconst { data } = useContentInstance<HeroData>('sections', 'hero');\r\n\r\n// Bulk fetch\r\nconst { data } = useContentBulk(['sections/hero', 'sections/amenities']);\r\n```\r\n\r\n#### Forms Hooks\r\n\r\n```typescript\r\n// All forms with submit function\r\nconst { data, submitForm, isSubmitting, submitError } = useForms();\r\n\r\n// Specific form by ID or name\r\nconst { form, submitForm, isSubmitting } = useForm('contact');\r\n```\r\n\r\n#### Globals Hooks\r\n\r\n```typescript\r\n// Full globals\r\nconst { data } = useGlobals();\r\n\r\n// Specific meta tag\r\nconst { value } = useGlobalTag('description');\r\n\r\n// Brand colors\r\nconst { colors } = useBrandColors();\r\n// colors.primary, colors.secondary, colors.tertiary\r\n\r\n// Social media links\r\nconst { links } = useSocialMedia();\r\n// links.facebook, links.instagram, etc.\r\n```\r\n\r\n#### Specials Hooks\r\n\r\n```typescript\r\n// All specials\r\nconst { data } = useSpecials();\r\n\r\n// First/featured special\r\nconst { special, hasSpecial } = useActiveSpecial();\r\n\r\n// Check if specials exist\r\nconst { hasSpecials, count } = useHasSpecials();\r\n```\r\n\r\n## TypeScript\r\n\r\nThe SDK is fully typed. Use generics for typed content data:\r\n\r\n```typescript\r\ninterface FloorplanData {\r\n  name: string;\r\n  sqft: number;\r\n  beds: number;\r\n  baths: number;\r\n  price: string;\r\n}\r\n\r\n// With client\r\nconst floorplans = await cms.getContentByType<FloorplanData>('floorplans');\r\nfloorplans.forEach(fp => {\r\n  console.log(fp.data.name);  // TypeScript knows this is a string\r\n  console.log(fp.data.sqft);  // TypeScript knows this is a number\r\n});\r\n\r\n// With hooks\r\nconst { data } = useContentByType<FloorplanData>('floorplans');\r\n```\r\n\r\n## Error Handling\r\n\r\n```typescript\r\nimport { ReapAPIError, ReapNetworkError } from '@reap/cms-sdk';\r\n\r\ntry {\r\n  const content = await cms.getContentInstance('sections', 'nonexistent');\r\n} catch (error) {\r\n  if (error instanceof ReapAPIError) {\r\n    console.log(error.statusCode);  // 404\r\n    console.log(error.endpoint);    // '/content-api/sections/nonexistent'\r\n\r\n    if (error.isAuthError()) {\r\n      // Handle 401 - invalid API key\r\n    } else if (error.isNotFoundError()) {\r\n      // Handle 404 - content not found\r\n    }\r\n  } else if (error instanceof ReapNetworkError) {\r\n    // Network failure or timeout\r\n    console.log(error.originalError);\r\n  }\r\n}\r\n```\r\n\r\n## Examples\r\n\r\n### Astro SSR\r\n\r\n```astro\r\n---\r\n// src/pages/index.astro\r\nimport { ReapClient } from '@reap/cms-sdk';\r\n\r\nconst cms = new ReapClient({\r\n  baseUrl: import.meta.env.REAP_API_URL,\r\n  apiKey: import.meta.env.REAP_API_KEY,\r\n});\r\n\r\ninterface HeroData {\r\n  title: string;\r\n  subtitle?: string;\r\n}\r\n\r\nconst hero = await cms.getContentInstance<HeroData>('sections', 'hero');\r\nconst globals = await cms.getGlobals();\r\n---\r\n\r\n<html>\r\n  <head>\r\n    <title>{globals.name}</title>\r\n  </head>\r\n  <body>\r\n    <h1>{hero.data.title}</h1>\r\n    {hero.data.subtitle && <p>{hero.data.subtitle}</p>}\r\n  </body>\r\n</html>\r\n```\r\n\r\n### React Form Submission\r\n\r\n```tsx\r\nimport { useForms } from '@reap/cms-sdk/react';\r\nimport ReCAPTCHA from 'react-google-recaptcha';\r\n\r\nfunction ContactForm() {\r\n  const { data, submitForm, isSubmitting, submitError } = useForms();\r\n  const [formData, setFormData] = useState({});\r\n  const [token, setToken] = useState('');\r\n\r\n  const form = data?.forms.find(f => f.is_default);\r\n\r\n  const handleSubmit = async (e: React.FormEvent) => {\r\n    e.preventDefault();\r\n    if (!form) return;\r\n\r\n    try {\r\n      const result = await submitForm(form.id, formData, token);\r\n      if (result.success) {\r\n        alert('Thanks for your submission!');\r\n        if (result.redirect_url) {\r\n          window.location.href = result.redirect_url;\r\n        }\r\n      }\r\n    } catch (err) {\r\n      // Error is also available via submitError\r\n    }\r\n  };\r\n\r\n  if (!form) return null;\r\n\r\n  return (\r\n    <form onSubmit={handleSubmit}>\r\n      {form.fields.map(field => (\r\n        <div key={field.id}>\r\n          <label>{field.label}</label>\r\n          <input\r\n            type={field.type === 'textarea' ? undefined : field.type}\r\n            required={field.required}\r\n            onChange={e => setFormData(prev => ({\r\n              ...prev,\r\n              [field.label]: e.target.value\r\n            }))}\r\n          />\r\n        </div>\r\n      ))}\r\n\r\n      {data?.recaptchaSiteKey && (\r\n        <ReCAPTCHA sitekey={data.recaptchaSiteKey} onChange={setToken} />\r\n      )}\r\n\r\n      <button type=\"submit\" disabled={isSubmitting || !token}>\r\n        {isSubmitting ? 'Sending...' : 'Submit'}\r\n      </button>\r\n\r\n      {submitError && <p className=\"error\">{submitError.message}</p>}\r\n    </form>\r\n  );\r\n}\r\n```\r\n\r\n## License\r\n\r\nMIT\r\n","readmeFilename":"README.md","_rev":"1-f1f65728c1eaee84363294330951851f"}