UNPKG

@openpolicy/sdk

Version:

Public API for defining privacy policies with OpenPolicy

285 lines (233 loc) 7.54 kB
--- name: getting-started description: > End-to-end setup for OpenPolicy: install @openpolicy/sdk, @openpolicy/react, and @openpolicy/vite; create openpolicy.ts with defineConfig(); wire openPolicy() into vite.config.ts; wrap the React app with <OpenPolicy>; render <PrivacyPolicy>. type: lifecycle library: openpolicy library_version: "0.0.19" sources: - jamiedavenport/openpolicy:packages/sdk/README.md - jamiedavenport/openpolicy:packages/react/src/context.tsx - jamiedavenport/openpolicy:packages/vite/src/index.ts --- ## Setup Install packages: ```sh bun add @openpolicy/sdk @openpolicy/react @openpolicy/vite ``` Create `openpolicy.ts` at the project root: ```ts import { ContractPrerequisite, dataCollected, defineConfig, LegalBases, thirdParties, } from "@openpolicy/sdk"; export default defineConfig({ company: { name: "Acme", legalName: "Acme, Inc.", address: "123 Main St, San Francisco, CA 94105", contact: { email: "privacy@acme.com" }, }, effectiveDate: "2026-01-01", jurisdictions: ["eu"], data: { collected: { ...dataCollected, "Account Information": ["Email address", "Display name"], }, context: { "Account Information": { purpose: "To create and manage user accounts", lawfulBasis: LegalBases.Contract, retention: "Until account deletion", provision: ContractPrerequisite("We cannot create or operate your account."), }, }, }, thirdParties: [...thirdParties], }); ``` Add `openPolicy()` to `vite.config.ts` it must appear before any React plugin: ```ts import { defineConfig } from "vite"; import react from "@vitejs/plugin-react"; import { openPolicy } from "@openpolicy/vite"; export default defineConfig({ plugins: [openPolicy({ thirdParties: { usePackageJson: true } }), react()], }); ``` Wrap the application root with `<OpenPolicy>`: ```tsx // main.tsx or _app.tsx or layout.tsx import { OpenPolicy } from "@openpolicy/react"; import config from "./openpolicy"; export function App({ children }: { children: React.ReactNode }) { return <OpenPolicy config={config}>{children}</OpenPolicy>; } ``` Render a policy page: ```tsx import { PrivacyPolicy } from "@openpolicy/react"; export default function PrivacyPage() { return <PrivacyPolicy />; } ``` Components ship unstyled and emit `data-op-*` attributes. Hook Tailwind or your own CSS onto `data-op-policy`, `data-op-heading`, `data-op-section`, `data-op-paragraph`, `data-op-list` to theme them. ## Core Patterns ### Mark data collection inline with `collecting()` ```ts import { collecting } from "@openpolicy/sdk"; // Call next to the point of collection; openPolicy() scans for these at build time export async function createUser(name: string, email: string) { const user = collecting( "Account Information", { name, email }, { name: "Display name", email: "Email address" }, ); return db.users.create(user); } ``` The category and label arguments must be string literals dynamic variables are silently skipped by the static scanner. ### Mark third-party integrations with `thirdParty()` ```ts import { thirdParty } from "@openpolicy/sdk"; // Place next to the integration's initialisation thirdParty("Stripe", "Payment processing", "https://stripe.com/privacy"); ``` The `openPolicy({ thirdParties: { usePackageJson: true } })` option also auto-detects ~30 known packages (Stripe, Sentry, PostHog, etc.) from `package.json`. ### Spread both sentinels in `openpolicy.ts` ```ts import { ContractPrerequisite, dataCollected, defineConfig, LegalBases, thirdParties, Voluntary, } from "@openpolicy/sdk"; export default defineConfig({ company: { name: "Acme", legalName: "Acme, Inc.", address: "123 Main St, San Francisco, CA 94105", contact: { email: "privacy@acme.com" }, }, effectiveDate: "2026-01-01", jurisdictions: ["eu"], data: { collected: { ...dataCollected, // populated by openPolicy() at build time "Manual Category": ["Manually added field"], // additional hand-declared entries }, context: { "Account Information": { purpose: "To create and manage user accounts", lawfulBasis: LegalBases.Contract, retention: "Until account deletion", provision: ContractPrerequisite("We cannot create or operate your account."), }, "Manual Category": { purpose: "To enable a specific feature", lawfulBasis: LegalBases.LegitimateInterests, retention: "30 days", provision: Voluntary("None your service is unaffected."), }, }, }, thirdParties: [...thirdParties], // populated by openPolicy() at build time }); ``` `dataCollected` and `thirdParties` are placeholder objects in `@openpolicy/sdk`; `openPolicy()` replaces them via virtual module injection during the Vite build. Every category in `data.collected` (auto or manual) must have a matching entry in `data.context` `defineConfig`'s generic enforces this. ## Common Mistakes ### HIGH: Rendering policy components without `<OpenPolicy>` provider Wrong: ```tsx // privacy-page.tsx import { PrivacyPolicy } from "@openpolicy/react"; export default function PrivacyPage() { return <PrivacyPolicy />; } ``` Correct: ```tsx // layout.tsx wrap at the root import { OpenPolicy } from "@openpolicy/react"; import config from "./openpolicy"; export default function RootLayout({ children }: { children: React.ReactNode }) { return <OpenPolicy config={config}>{children}</OpenPolicy>; } // privacy-page.tsx component reads from context import { PrivacyPolicy } from "@openpolicy/react"; export default function PrivacyPage() { return <PrivacyPolicy />; } ``` `PrivacyPolicy` and `CookiePolicy` read config from React context; without the provider they silently render `null` with no visible error. Source: packages/react/src/context.tsx --- ### HIGH: Not spreading `dataCollected` and `thirdParties` sentinels into config Wrong: ```ts // openpolicy.ts import { ContractPrerequisite, defineConfig, LegalBases } from "@openpolicy/sdk"; export default defineConfig({ company: { name: "Acme", legalName: "Acme, Inc.", address: "123 Main St, San Francisco, CA 94105", contact: { email: "privacy@acme.com" }, }, effectiveDate: "2026-01-01", data: { collected: { "Account Information": ["Email address"] }, context: { "Account Information": { purpose: "Account creation", lawfulBasis: LegalBases.Contract, retention: "Until account deletion", provision: ContractPrerequisite("We cannot create or operate your account."), }, }, }, thirdParties: [], }); ``` Correct: ```ts // openpolicy.ts import { ContractPrerequisite, dataCollected, defineConfig, LegalBases, thirdParties, } from "@openpolicy/sdk"; export default defineConfig({ company: { name: "Acme", legalName: "Acme, Inc.", address: "123 Main St, San Francisco, CA 94105", contact: { email: "privacy@acme.com" }, }, effectiveDate: "2026-01-01", data: { collected: { ...dataCollected, "Account Information": ["Email address"] }, context: { "Account Information": { purpose: "Account creation", lawfulBasis: LegalBases.Contract, retention: "Until account deletion", provision: ContractPrerequisite("We cannot create or operate your account."), }, }, }, thirdParties: [...thirdParties], }); ``` Without spreading the sentinels, `openPolicy()` plugin output is discarded and the policy compiles with only the hand-declared entries all `collecting()` and `thirdParty()` call annotations are silently ignored. Source: packages/sdk/src/auto-collected.ts