@openpolicy/sdk
Version:
Public API for defining privacy policies with OpenPolicy
313 lines (312 loc) • 9.32 kB
JavaScript
import { cookies, dataCollected, thirdParties } from "./auto-collected.js";
//#region ../core/dist/index.js
/**
* Helpers that build a {@link ProvisionRequirement} for `data.context[category].provision`.
* Each call carries the consequences of failing to provide the data, as
* required by GDPR Article 13(2)(e).
*/
const Statutory = (consequences) => ({
basis: "statutory",
consequences
});
const Contractual = (consequences) => ({
basis: "contractual",
consequences
});
const ContractPrerequisite = (consequences) => ({
basis: "contract-prerequisite",
consequences
});
const Voluntary = (consequences) => ({
basis: "voluntary",
consequences
});
function stableSerialize(value) {
if (value === null || typeof value !== "object") return JSON.stringify(value);
if (Array.isArray(value)) return `[${value.map(stableSerialize).join(",")}]`;
return `{${Object.entries(value).filter(([, v]) => v !== void 0).sort(([a], [b]) => a < b ? -1 : a > b ? 1 : 0).map(([k, v]) => `${JSON.stringify(k)}:${stableSerialize(v)}`).join(",")}}`;
}
function fnv1a32(str) {
let hash = 2166136261;
for (let i = 0; i < str.length; i++) {
hash ^= str.charCodeAt(i);
hash = Math.imul(hash, 16777619);
}
return (hash >>> 0).toString(16).padStart(8, "0");
}
const PRIVACY_HASH_FIELDS = [
"company",
"effectiveDate",
"jurisdictions",
"locale",
"data",
"children",
"thirdParties",
"automatedDecisionMaking",
"cookies"
];
const COOKIE_HASH_FIELDS = [
"company",
"effectiveDate",
"jurisdictions",
"locale",
"cookies",
"thirdParties",
"trackingTechnologies",
"consentMechanism"
];
function hashSlice(config, fields) {
const slice = {};
for (const field of fields) {
const value = config[field];
if (value !== void 0) slice[field] = value;
}
return fnv1a32(stableSerialize(slice));
}
function hasAnyPrivacyField$2(config) {
return config.data !== void 0 || config.children !== void 0;
}
function shouldHashPrivacy(config) {
if (config.policies) return config.policies.includes("privacy");
return hasAnyPrivacyField$2(config);
}
function shouldHashCookie(config) {
if (config.policies) return config.policies.includes("cookie");
return config.cookies !== void 0;
}
function computePrivacyVersion(config) {
if (!shouldHashPrivacy(config)) return void 0;
return hashSlice(config, PRIVACY_HASH_FIELDS);
}
function computeCookieVersion(config) {
if (!shouldHashCookie(config)) return void 0;
return hashSlice(config, COOKIE_HASH_FIELDS);
}
//#endregion
//#region src/collecting.ts
/**
* Sentinel used as a label value to explicitly exclude a field from the
* compiled privacy policy. Every key of the `value` passed to `collecting()`
* must appear in the label record — pass `Ignore` for fields that should not
* appear in the policy (e.g. `hashedPassword: Ignore`).
*
* It is a `unique symbol` so it cannot collide with a real label string and
* so the type checker treats it nominally.
*/
const Ignore = Symbol("@openpolicy/ignore");
/**
* Declares data collected at the point of storage. Returns `value` unchanged
* at runtime — the Vite plugin / CLI static analyser (OP-152) will scan calls
* to `collecting()` at build time and merge the declarations into the
* compiled privacy policy.
*
* The third argument is a plain object literal whose **keys** are field names
* matching your stored value (for convenient access without a typed callback)
* and whose **values** are the human-readable labels used in the compiled
* policy. Only the string values are used by the analyser; the object is
* never evaluated at runtime. This shape lets you:
* - keep `value` matching your ORM/table schema exactly,
* - describe fields with friendly labels for the policy,
* - exclude a field from the policy by setting its label to `Ignore`
* (imported from `@openpolicy/sdk`) — every key of `value` must appear
* in the label record, so e.g. `hashedPassword: Ignore` is how you hide
* a sensitive column.
*
* The category argument and the string values of the label record must be
* string literals — dynamic values are silently skipped by the analyser.
*
* @example
* ```ts
* import { collecting, Ignore } from "@openpolicy/sdk";
*
* export async function createUser(
* name: string,
* email: string,
* hashedPassword: string,
* ) {
* return db.insert(users).values(
* collecting(
* "Account Information",
* { name, email, hashedPassword }, // real ORM columns — returned unchanged
* { name: "Name", email: "Email address", hashedPassword: Ignore },
* ),
* );
* }
* ```
*/
function collecting(_category, value, _label) {
return value;
}
//#endregion
//#region src/compliance.ts
const Compliance = {
GDPR: { jurisdictions: ["eu"] },
UK_GDPR: { jurisdictions: ["uk"] },
CCPA: { jurisdictions: ["us-ca"] }
};
//#endregion
//#region src/data.ts
const DataCategories = {
AccountInfo: { "Account Information": ["Name", "Email address"] },
SessionData: { "Session Data": [
"IP address",
"User agent",
"Browser type"
] },
PaymentInfo: { "Payment Information": [
"Card last 4 digits",
"Billing name",
"Billing address"
] },
UsageData: { "Usage Data": [
"Pages visited",
"Features used",
"Time spent"
] },
DeviceInfo: { "Device Information": [
"Device type",
"Operating system",
"Browser version"
] },
LocationData: { "Location Data": [
"Country",
"City",
"Timezone"
] },
Communications: { Communications: ["Email content", "Support tickets"] }
};
const Retention = {
UntilAccountDeletion: "Until account deletion",
UntilSessionExpiry: "Until session expiry",
ThirtyDays: "30 days",
NinetyDays: "90 days",
OneYear: "1 year",
ThreeYears: "3 years",
AsRequiredByLaw: "As required by applicable law"
};
const LegalBases = {
Consent: "consent",
Contract: "contract",
LegalObligation: "legal_obligation",
VitalInterests: "vital_interests",
PublicTask: "public_task",
LegitimateInterests: "legitimate_interests"
};
//#endregion
//#region src/define-cookie.ts
function defineCookie(_category) {}
//#endregion
//#region src/providers.ts
const Providers = {
Stripe: {
name: "Stripe",
purpose: "Payment processing",
policyUrl: "https://stripe.com/privacy"
},
Paddle: {
name: "Paddle",
purpose: "Payment processing and subscription management",
policyUrl: "https://www.paddle.com/legal/privacy"
},
LemonSqueezy: {
name: "Lemon Squeezy",
purpose: "Payment processing and subscription management",
policyUrl: "https://www.lemonsqueezy.com/privacy"
},
PayPal: {
name: "PayPal",
purpose: "Payment processing",
policyUrl: "https://www.paypal.com/webapps/mpp/ua/privacy-full"
},
GoogleAnalytics: {
name: "Google Analytics",
purpose: "Usage analytics",
policyUrl: "https://policies.google.com/privacy"
},
PostHog: {
name: "PostHog",
purpose: "Product analytics and session recording",
policyUrl: "https://posthog.com/privacy"
},
Plausible: {
name: "Plausible Analytics",
purpose: "Privacy-friendly usage analytics",
policyUrl: "https://plausible.io/privacy"
},
Mixpanel: {
name: "Mixpanel",
purpose: "Product analytics and event tracking",
policyUrl: "https://mixpanel.com/legal/privacy-policy"
},
Vercel: {
name: "Vercel",
purpose: "Hosting and deployment infrastructure",
policyUrl: "https://vercel.com/legal/privacy-policy"
},
Cloudflare: {
name: "Cloudflare",
purpose: "CDN, DNS, and security services",
policyUrl: "https://www.cloudflare.com/privacypolicy/"
},
AWS: {
name: "Amazon Web Services",
purpose: "Cloud infrastructure and hosting",
policyUrl: "https://aws.amazon.com/privacy/"
},
Auth0: {
name: "Auth0",
purpose: "Authentication and identity management",
policyUrl: "https://auth0.com/privacy"
},
Clerk: {
name: "Clerk",
purpose: "Authentication and user management",
policyUrl: "https://clerk.com/privacy"
},
Resend: {
name: "Resend",
purpose: "Transactional email delivery",
policyUrl: "https://resend.com/legal/privacy-policy"
},
Postmark: {
name: "Postmark",
purpose: "Transactional email delivery",
policyUrl: "https://wildbit.com/privacy-policy"
},
SendGrid: {
name: "SendGrid",
purpose: "Transactional email delivery",
policyUrl: "https://www.twilio.com/en-us/legal/privacy"
},
Loops: {
name: "Loops",
purpose: "Email marketing and automation",
policyUrl: "https://loops.so/privacy"
},
Sentry: {
name: "Sentry",
purpose: "Error monitoring and performance tracking",
policyUrl: "https://sentry.io/privacy/"
},
Datadog: {
name: "Datadog",
purpose: "Infrastructure monitoring and observability",
policyUrl: "https://www.datadoghq.com/legal/privacy/"
}
};
//#endregion
//#region src/third-parties.ts
function thirdParty(_name, _purpose, _policyUrl) {}
//#endregion
//#region src/index.ts
function defineConfig(config) {
const resolved = config;
return {
...resolved,
privacyVersion: resolved.privacyVersion ?? computePrivacyVersion(resolved),
cookieVersion: resolved.cookieVersion ?? computeCookieVersion(resolved)
};
}
//#endregion
export { Compliance, ContractPrerequisite, Contractual, DataCategories, Ignore, LegalBases, Providers, Retention, Statutory, Voluntary, collecting, computeCookieVersion, computePrivacyVersion, cookies, dataCollected, defineConfig, defineCookie, thirdParties, thirdParty };
//# sourceMappingURL=index.js.map