npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@codeluum/compliance

v0.1.1

Published

HTTP client for Codeluum compliance integration API (Node 18+)

Readme

@codeluum/compliance

npm version license

Server-side HTTP client for the Codeluum compliance integration API — consent records, data-subject requests (DSR), audit events, and webhook verification for GDPR / NDPR (Nigeria Data Protection Act) workflows.

This is the server SDK. It authenticates with your secret key (sk_…) and must never run in a browser. For browser/React widgets (cookie banner, preference center, DSAR buttons) that use the publishable key (pk_…), use @codeluum/compliance-web.

  • Zero runtime dependencies — built on the global fetch (Node 18+).
  • ESM only ("type": "module").
  • Ships TypeScript types.

Installation

npm install @codeluum/compliance

Requires Node.js ≥ 18 (uses global fetch and node:crypto).

Quick start

import { createCodeluumClient } from '@codeluum/compliance';

const codeluum = createCodeluumClient({
  baseUrl: 'https://api.codeluum.com',   // origin only, no trailing path
  apiKey: process.env.CODELUUM_SECRET_KEY!, // sk_…  — keep this server-side
});

// Record a consent decision (a write — withdrawal is `state: 'withdrawn'`)
await codeluum.consents.record({
  subject: 'user_123',
  purpose: 'newsletter',
  state: 'granted',
  basis: 'consent',
  policyVersion: '2025-01',
  evidence: { source: 'form', ip: req.ip, formId: 'signup' },
});

// Check current state before acting on it
if (await codeluum.consents.has({ subject: 'user_123', purpose: 'newsletter' })) {
  await sendNewsletter('user_123');
}

Keep the secret key secret. apiKey grants full server-side access to your compliance data. Load it from an environment variable or secrets manager — never commit it and never ship it to the browser.

Client configuration

createCodeluumClient(config) returns a CodeluumClient.

| Field | Type | Required | Description | |---|---|---|---| | baseUrl | string | ✅ | API origin only, e.g. https://api.codeluum.com (no trailing path; a trailing slash is trimmed). | | apiKey | string | ✅ | Secret integration key sk_…. Sent as Authorization: Bearer <apiKey>. | | fetchImpl | typeof fetch | | Override fetch (e.g. undici, or a mock in tests). Defaults to globalThis.fetch. |

All methods call …/compliance/integration/*, unwrap the { success, data } envelope, and return data. Non-2xx responses (or { success: false }) throw a CodeluumApiError.

Consent API — client.consents

Event-log-backed consent primitives. Every call appends to an immutable log; the "current state" is the projection of the latest event per (subject, purpose).

record(params): Promise<RecordConsentResult>

Append a consent event. Idempotent on idempotencyKey when provided.

const result = await codeluum.consents.record({
  subject: 'user_123',
  purpose: 'analytics',
  state: 'granted',          // 'granted' | 'withdrawn' | 'acknowledged'
  basis: 'consent',          // see ConsentBasis below
  policyVersion: '2025-01',
  dataFlowVersion: 3,
  evidence: { source: 'preference_center' },
  idempotencyKey: 'evt_abc123',
});
// → { id, subject, purpose, state, recordedAt }

RecordConsentParams fields:

| Field | Type | Required | Notes | |---|---|---|---| | subject | string | ✅ | Stable subject identifier. | | purpose | string | ✅ | Purpose id from your purpose registry. | | state | ConsentState | ✅ | 'granted' \| 'withdrawn' \| 'acknowledged'. | | basis | ConsentBasis | ✅ | Lawful basis — see below. | | policyVersion | string | | Version of the policy the subject saw. | | dataFlowVersion | number | | Version of the data-flow disclosure. | | evidence | ConsentEvidence | | Capture context (source, ip, userAgent, referrer, formId, pageUrl, …). | | idempotencyKey | string | | De-dupes retries. |

ConsentBasis: 'consent' | 'contract' | 'pre_contract' | 'legal_obligation' | 'legitimate_interest' | 'vital' | 'public_task' | 'employment_safeguard'.

get({ subject }): Promise<Record<string, ConsentProjectionEntry>>

Current state for every purpose the subject has touched, keyed by purpose id.

const states = await codeluum.consents.get({ subject: 'user_123' });
// → { newsletter: { state: 'granted', basis: 'consent', policyVersion, dataFlowVersion, evidence, asOf }, … }

has({ subject, purpose }): Promise<boolean>

Convenience — true only when the latest state for that purpose is 'granted'.

await codeluum.consents.has({ subject: 'user_123', purpose: 'newsletter' });

history(params): Promise<ConsentEvent[]>

Newest-first event log for a subject. Server-only — the browser SDK deliberately omits this (a publishable-key call shouldn't enumerate audit-grade history).

const events = await codeluum.consents.history({
  subject: 'user_123',
  since: '2025-01-01T00:00:00Z', // optional ISO timestamp
  limit: 50,
  offset: 0,
});

Data-subject requests (DSR)

createDataSubjectRequest(params)

Open an export or delete request on behalf of a subject.

await codeluum.createDataSubjectRequest({
  externalUserId: 'user_123',
  type: 'export',           // 'export' | 'delete'
  idempotencyKey: 'dsr_001', // optional
});

dsr.acknowledge(params): Promise<DsrAcknowledgeResult>

Call this from your webhook handler after you fulfil (or refuse) a dsar.requested event. Codeluum updates the DSR row and fires dsar.completed / dsar.failed for downstream subscribers; the failureReason surfaces on the data subject's status endpoint.

// On success:
await codeluum.dsr.acknowledge({
  requestId: 'req_789',
  status: 'completed',
  fulfillment: { exportBundleUrl: 'https://…/bundle.zip', deletedRecordCount: 42 },
});

// On failure (failureReason is required):
await codeluum.dsr.acknowledge({
  requestId: 'req_789',
  status: 'failed',
  failureReason: 'Subject not found in CRM',
});

DsrFulfillment supports exportBundleUrl, deletedRecordCount, rectifiedFields, notes, plus any extra keys your audit exporter reads.

Webhook delivery acknowledgement

webhookDeliveries.acknowledge(params): Promise<AckDeliveryResult>

Generic acknowledgement for required-callback events other than DSR (e.g. consent.withdrawn, incident.reported). DSR uses dsr.acknowledge instead because its richer payload also resolves the DSR row.

await codeluum.webhookDeliveries.acknowledge({
  deliveryId: 'dlv_456',
  notes: 'Suppressed user in mailing platform', // optional, operator + audit-log visible
});

Audit events

appendAudit(params)

Append a compliance audit event.

await codeluum.appendAudit({
  action: 'data_exported',
  resourceType: 'user',
  resourceId: 'user_123',
  actorExternalUserId: 'admin_7',
  metadata: { format: 'json', records: 42 },
});

Verifying inbound webhooks

Codeluum signs every webhook with HMAC-SHA256 over <unix-ts>.<rawBody>, sent in the Codeluum-Signature header as t=<unix>,v1=<hex>. Verify it against the raw request body (before JSON parsing).

import express from 'express';
import { verifyWebhookSignature } from '@codeluum/compliance';

const app = express();

// Use express.raw so the body is the exact bytes that were signed.
app.post('/webhooks/codeluum', express.raw({ type: 'application/json' }), (req, res) => {
  const result = verifyWebhookSignature({
    headers: req.headers,
    rawBody: req.body,                       // string | Buffer
    secret: process.env.CODELUUM_WEBHOOK_SECRET!,
    toleranceSeconds: 300,                   // default 300; rejects stale timestamps
  });

  if (!result.isValid) {
    // reason: 'missing' | 'malformed' | 'stale' | 'mismatch'
    return res.status(400).json({ error: result.reason });
  }

  const event = result.event; // parsed JSON payload
  // … handle event.type …
  res.json({ received: true });
});

verifyWebhookSignature returns { isValid: true, event } or { isValid: false, reason }:

| reason | Meaning | |---|---| | 'missing' | No Codeluum-Signature header. | | 'malformed' | Header isn't t=…,v1=<hex>. | | 'stale' | Timestamp drift exceeds toleranceSeconds. | | 'mismatch' | HMAC mismatch (tampered payload or wrong secret). |

signWebhookPayload({ secret, body, now? }) produces the matching { timestamp, signature, header } — mainly a testing convenience; production code shouldn't need it.

Express middleware — requireConsent

Gate a route on a granted purpose. It's a thin coordinator over client.consents.has(...).

import { requireConsent } from '@codeluum/compliance';

app.post(
  '/api/newsletter/subscribe',
  requireConsent({ purpose: 'newsletter', client: codeluum }),
  newsletterHandler
);

Behaviour:

  • 401 { error: 'subject_unresolved' } — no subject id could be resolved from the request.
  • 403 { error: 'consent_required' } — current state is not 'granted' (withdrawn / acknowledged / never-recorded all fail).
  • next() — granted.
  • next(err) — an upstream lookup error (network/5xx) is deferred to your Express error middleware, not treated as a consent decision.

The subject defaults to req.user?.user_id. Override it for other auth shapes:

requireConsent({
  purpose: 'newsletter',
  client: codeluum,
  subjectFrom: (req) => req.session?.userId ?? null, // return null/undefined → 401
});

Error handling

Failed requests throw CodeluumApiError:

import { CodeluumApiError } from '@codeluum/compliance';

try {
  await codeluum.consents.record(/* … */);
} catch (err) {
  if (err instanceof CodeluumApiError) {
    console.error(err.status, err.message, err.body);
  } else {
    throw err;
  }
}

CodeluumApiError carries status: number, message: string, and the raw body: unknown.

Exported types

CodeluumClient, CodeluumClientConfig, CodeluumApiError, ConsentState, ConsentBasis, ConsentEvidence, RecordConsentParams, RecordConsentResult, ConsentProjectionEntry, HistoryParams, ConsentEvent, CreateDataSubjectRequestParams, AppendAuditParams, DsrAcknowledgeParams, DsrAcknowledgeResult, DsrFulfillment, AckDeliveryParams, AckDeliveryResult, VerifyWebhookSignatureParams, VerifyResult, RequireConsentOptions.

License

MIT