@glideco/compliance-export
v0.2.0
Published
Compliance export primitives — JSON sync + PDF async pipeline + retention-tier S3 storage adapters (STANDARD / GLACIER_IR / DEEP_ARCHIVE). Schema, range validator (1-year cap, monthly shard helper), envelope builder, signed-URL re-signing helper. Operator
Maintainers
Readme
@glideco/compliance-export
Compliance export primitives for Glide's agent activity log:
- Range validator — enforces the 1-year-per-export cap.
- Monthly shard splitter — splits multi-year ranges into UTC calendar-month shards (one
compliance_exportsrow per shard). - Envelope schema + builder — JSON shape that ships in
compliance.exportJson(sync) and inside the PDF body (async). - Signed-URL refresh helper — operator brings their own S3 client; this package handles the cache-and-refresh logic.
DB-agnostic and S3-client-agnostic. The operator wires the executor / signer.
Install
npm install @glideco/compliance-exportUsage
Build a JSON envelope
import { buildEnvelope } from '@glideco/compliance-export';
const envelope = buildEnvelope({
entityId: 'entity_abc',
entityName: 'Glide Operator Co',
range: {
since: new Date('2026-01-01T00:00:00Z'),
until: new Date('2026-01-31T23:59:59Z'),
},
rows: dbRows.map((r) => ({
id: r.id,
createdAt: r.createdAt,
action: r.action,
riskVerdict: r.riskVerdict,
vendorUsed: r.vendorUsed,
onChainTx: r.onChainTx,
policyVersion: r.policyVersion,
redactedFieldsBitmap: r.redactedFieldsBitmap,
})),
});
// ↓ ships as application/json or embedded in the async PDF
return Response.json(envelope);Validate + shard a long range
import {
validateRange,
splitIntoMonthlyShards,
} from '@glideco/compliance-export';
const range = { since: parseISO(input.since), until: parseISO(input.until) };
const v = validateRange(range);
if (v.ok) {
// single-shot export
return enqueueExport(range);
}
if (v.reason === 'exceeds-one-year') {
const shards = splitIntoMonthlyShards(range);
for (const shard of shards) await enqueueExport(shard);
return { fragmented: true, count: shards.length };
}
throw new Error(v.message);Refresh signed S3 URLs
import { GetObjectCommand, S3Client } from '@aws-sdk/client-s3';
import { getSignedUrl } from '@aws-sdk/s3-request-presigner';
import { refreshSignedUrl, type Signer } from '@glideco/compliance-export';
const s3 = new S3Client({ region: 'us-east-1' });
const signer: Signer = async ({ bucket, key, expiresInSeconds }) => {
const url = await getSignedUrl(
s3,
new GetObjectCommand({ Bucket: bucket, Key: key }),
{ expiresIn: expiresInSeconds }
);
return {
url,
expiresAt: new Date(Date.now() + expiresInSeconds * 1000),
};
};
// In the admin polling endpoint:
const fresh = await refreshSignedUrl({
signer,
bucket: process.env.S3_EXPORTS_BUCKET!,
key: row.s3Key,
current: row.cachedUrl
? { url: row.cachedUrl, expiresAt: row.cachedUrlExpiresAt }
: null,
});
await db
.update(complianceExports)
.set({ cachedUrl: fresh.url, cachedUrlExpiresAt: fresh.expiresAt })
.where(eq(complianceExports.id, row.id));Quotas
The OSS plan §M4 specifies:
- Max 10 exports/tenant/day — enforced at the tRPC router layer; not in this package.
- Max range 1 year per export — enforced by
validateRange. - Long-range fragmentation — UI calls
splitIntoMonthlyShardsand enqueues one shard per row.
License
MIT.
