{"_id":"@absolutejs/audit-s3","_rev":"4-701f53fa71ce6d98890c7628675cdeb2","name":"@absolutejs/audit-s3","dist-tags":{"latest":"0.1.1"},"versions":{"0.0.1":{"name":"@absolutejs/audit-s3","version":"0.0.1","keywords":["audit","s3","r2","cloudflare","minio","backblaze","absolutejs","audit-log","compliance","worm"],"author":"Alex Kahn","license":"Apache-2.0","_id":"@absolutejs/audit-s3@0.0.1","maintainers":[{"name":"alexkahndev","email":"alexkahn2019@gmail.com"}],"dist":{"shasum":"9908847279c76c8dd2afde63400ffd483c42a037","tarball":"https://registry.npmjs.org/@absolutejs/audit-s3/-/audit-s3-0.0.1.tgz","fileCount":6,"integrity":"sha512-caFKsj2LhCnCdVjWCmjCUhLkJyuL4+VVfobTzMB0yNIF0KvRA4syc+1/IayRMFrNLrcBjb6c9k8PphK22Dh/GA==","signatures":[{"sig":"MEUCIQC/IZVsE4vl5Pv3b20bWVRJeJfy0DbXSc9IeKIJzeQJGQIgV5UikLWBC/5bJ0/OV4dUDbOUI9G5PLyW6gZ0SEl5hkE=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":28762},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","shasum":"9908847279c76c8dd2afde63400ffd483c42a037","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"}},"scripts":{"test":"bun test","build":"rm -rf dist && bun build src/index.ts --outdir dist --sourcemap --target=bun --external @absolutejs/audit && tsc --project tsconfig.build.json","format":"prettier --write \"./**/*.{ts,json,md}\"","release":"bun run format && bun run build && bun publish","typecheck":"tsc --noEmit"},"_npmUser":{"name":"alexkahndev","email":"alexkahn2019@gmail.com"},"_integrity":"sha512-caFKsj2LhCnCdVjWCmjCUhLkJyuL4+VVfobTzMB0yNIF0KvRA4syc+1/IayRMFrNLrcBjb6c9k8PphK22Dh/GA==","repository":{"url":"https://github.com/absolutejs/audit-adapters.git","type":"git","directory":"s3"},"_npmVersion":"10.8.3","description":"S3-compatible AuditSink for @absolutejs/audit. Buffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO. Time-sortable object keys; WORM-bucket-friendly for compliance retention.","directories":{},"_nodeVersion":"24.3.0","publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"prettier":"3.5.3","@types/bun":"1.3.14","typescript":"5.8.3","@absolutejs/audit":"^0.0.1"},"peerDependencies":{"@absolutejs/audit":">= 0.0.1"},"_npmOperationalInternal":{"tmp":"tmp/audit-s3_0.0.1_1780164967752_0.30260205141056784","host":"s3://npm-registry-packages-npm-production"}},"0.0.2":{"name":"@absolutejs/audit-s3","version":"0.0.2","keywords":["audit","s3","r2","cloudflare","minio","backblaze","absolutejs","audit-log","compliance","worm"],"author":"Alex Kahn","license":"Apache-2.0","_id":"@absolutejs/audit-s3@0.0.2","maintainers":[{"name":"alexkahndev","email":"alexkahn2019@gmail.com"}],"homepage":"https://github.com/absolutejs/audit-adapters/tree/main/s3","bugs":{"url":"https://github.com/absolutejs/audit-adapters/issues"},"dist":{"shasum":"9de67d3097dd653e93c78e1a6d5c40f3369964a4","tarball":"https://registry.npmjs.org/@absolutejs/audit-s3/-/audit-s3-0.0.2.tgz","fileCount":11,"integrity":"sha512-pwQpmjAuYbv/4nv4dsV8X0NMAo8i5f33U3cRCQloE9RP4V5i96QsFazCG6XlXQ5lSxArJASNl1FoqUw9/CaWKQ==","signatures":[{"sig":"MEUCICCypUH9mKW1ClkIInw7/1fRWeaBgXCBXRryMUNXexNCAiEApSi+Svmqnp5GX/lEScdSRdSsUVO1A9rVI8Wkv+ZKF5s=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":1128515},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","shasum":"9de67d3097dd653e93c78e1a6d5c40f3369964a4","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./manifest":{"types":"./dist/manifest.d.ts","import":"./dist/manifest.js","default":"./dist/manifest.js"},"./manifest.json":"./dist/manifest.json"},"scripts":{"test":"bun test","build":"rm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --sourcemap --target=bun --external @absolutejs/audit && tsc --project tsconfig.build.json && absolute-manifest emit","format":"prettier --write \"./**/*.{ts,json,md}\"","release":"bun run format && bun run build && bun publish","typecheck":"tsc --noEmit"},"_npmUser":{"name":"alexkahndev","email":"alexkahn2019@gmail.com"},"_integrity":"sha512-pwQpmjAuYbv/4nv4dsV8X0NMAo8i5f33U3cRCQloE9RP4V5i96QsFazCG6XlXQ5lSxArJASNl1FoqUw9/CaWKQ==","absolutejs":{"manifestContract":1},"repository":{"url":"https://github.com/absolutejs/audit-adapters.git","type":"git","directory":"s3"},"_npmVersion":"10.8.3","description":"S3-compatible AuditSink for @absolutejs/audit. Buffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO. Time-sortable object keys; WORM-bucket-friendly for compliance retention.","directories":{},"_nodeVersion":"24.3.0","dependencies":{"@sinclair/typebox":"^0.34.0","@absolutejs/manifest":"^0.1.0"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"prettier":"3.5.3","@types/bun":"1.3.14","typescript":"5.8.3","@absolutejs/audit":"^0.0.1"},"peerDependencies":{"@absolutejs/audit":">= 0.0.1"},"_npmOperationalInternal":{"tmp":"tmp/audit-s3_0.0.2_1783965320456_0.062083746280320984","host":"s3://npm-registry-packages-npm-production"}},"0.1.0":{"name":"@absolutejs/audit-s3","version":"0.1.0","keywords":["audit","s3","r2","cloudflare","minio","backblaze","absolutejs","audit-log","compliance","worm"],"author":"Alex Kahn","license":"Apache-2.0","_id":"@absolutejs/audit-s3@0.1.0","maintainers":[{"name":"alexkahndev","email":"alexkahn2019@gmail.com"}],"homepage":"https://github.com/absolutejs/audit-adapters/tree/main/s3","bugs":{"url":"https://github.com/absolutejs/audit-adapters/issues"},"dist":{"shasum":"26f913c3fd9e72dc151d8d0fbac1133f4cd12384","tarball":"https://registry.npmjs.org/@absolutejs/audit-s3/-/audit-s3-0.1.0.tgz","fileCount":11,"integrity":"sha512-azf9QzKEpeiPoAYTgbmvGZRShRds+eGoe7RShRLJSzamtMgEc156QzcxDgva63OTRCAxkIeSYJ4i30tmvteDgw==","signatures":[{"sig":"MEQCIHmMAFDgnoq6xWpyPH4fnVjdjwSnXUwLdu1qmruR3FNpAiBy5nocechkJJapydAZpXZmkl9QxxYBY7cnp7ezm1cHqw==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":491243},"main":"./dist/index.js","type":"module","types":"./dist/index.d.ts","module":"./dist/index.js","shasum":"26f913c3fd9e72dc151d8d0fbac1133f4cd12384","exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./manifest":{"types":"./dist/manifest.d.ts","import":"./dist/manifest.js","default":"./dist/manifest.js"},"./manifest.json":"./dist/manifest.json"},"scripts":{"test":"bun test","build":"rm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --sourcemap --target=bun --external @absolutejs/audit && tsc --project tsconfig.build.json && absolute-manifest emit","format":"prettier --write \"./**/*.{ts,json,md}\"","release":"bun run format && bun run build && bun publish","typecheck":"tsc --noEmit"},"_npmUser":{"name":"alexkahndev","email":"alexkahn2019@gmail.com"},"_integrity":"sha512-azf9QzKEpeiPoAYTgbmvGZRShRds+eGoe7RShRLJSzamtMgEc156QzcxDgva63OTRCAxkIeSYJ4i30tmvteDgw==","absolutejs":{"runtimePeers":{"@absolutejs/audit":{"range":">=0.2.1 <0.3.0","tested":"0.2.1","buildExternals":["@absolutejs/audit"],"artifactImports":[]}},"manifestContract":2},"repository":{"url":"https://github.com/absolutejs/audit-adapters.git","type":"git","directory":"s3"},"_npmVersion":"10.8.3","description":"S3-compatible AuditSink for @absolutejs/audit. Buffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO. Time-sortable object keys; WORM-bucket-friendly for compliance retention.","directories":{},"_nodeVersion":"24.3.0","dependencies":{"@sinclair/typebox":"^0.34.0","@absolutejs/manifest":"^0.7.1"},"publishConfig":{"access":"public"},"_hasShrinkwrap":false,"devDependencies":{"prettier":"3.5.3","@types/bun":"1.3.14","typescript":"5.8.3","@absolutejs/audit":"0.2.1"},"peerDependencies":{"@absolutejs/audit":">=0.2.1 <0.3.0"},"_npmOperationalInternal":{"tmp":"tmp/audit-s3_0.1.0_1784914982670_0.03279605349661985","host":"s3://npm-registry-packages-npm-production"}},"0.1.1":{"name":"@absolutejs/audit-s3","version":"0.1.1","description":"S3-compatible AuditSink for @absolutejs/audit. Buffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO. Time-sortable object keys; WORM-bucket-friendly for compliance retention.","repository":{"type":"git","url":"git+https://github.com/absolutejs/audit-adapters.git","directory":"s3"},"homepage":"https://github.com/absolutejs/audit-adapters/tree/main/s3","bugs":{"url":"https://github.com/absolutejs/audit-adapters/issues"},"main":"./dist/index.js","module":"./dist/index.js","types":"./dist/index.d.ts","type":"module","license":"Apache-2.0","author":{"name":"Alex Kahn"},"absolutejs":{"manifestContract":2,"runtimePeers":{"@absolutejs/audit":{"artifactImports":[],"buildExternals":["@absolutejs/audit"],"range":">=0.2.1 <0.3.0","tested":"0.2.2"}}},"publishConfig":{"access":"public"},"keywords":["audit","s3","r2","cloudflare","minio","backblaze","absolutejs","audit-log","compliance","worm"],"scripts":{"build":"rm -rf dist && bun build src/index.ts src/manifest.ts --outdir dist --sourcemap --target=bun --external @absolutejs/audit && tsc --project tsconfig.build.json && absolute-manifest emit","test":"bun test","typecheck":"tsc --noEmit","format":"prettier --write \"./**/*.{ts,json,md}\"","verify-package":"absolute-manifest verify-package","check:package":"bun run typecheck && bun run verify-package && bun run build && bun run verify-package --artifacts && bun run test","release":"bun run format && bun run check:package && bun publish"},"peerDependencies":{"@absolutejs/audit":">=0.2.1 <0.3.0"},"devDependencies":{"@absolutejs/audit":"0.2.2","@types/bun":"1.3.14","prettier":"3.5.3","typescript":"5.8.3"},"exports":{".":{"types":"./dist/index.d.ts","import":"./dist/index.js","default":"./dist/index.js"},"./manifest":{"types":"./dist/manifest.d.ts","import":"./dist/manifest.js","default":"./dist/manifest.js"},"./manifest.json":"./dist/manifest.json"},"dependencies":{"@absolutejs/manifest":"^0.7.2","@sinclair/typebox":"^0.34.0"},"_id":"@absolutejs/audit-s3@0.1.1","gitHead":"45ff7db566b6b521b02230e94bcebb830ef36258","_nodeVersion":"22.14.0","_npmVersion":"10.9.2","dist":{"integrity":"sha512-T1R3uMNIVcfKQFgVYPGxDY2rwMjwNo6gg4LhUiiGxi2Wc4sIJAKUJ5Tdk06KRQlLVDeBEXdKj+KUOoNfisHJKg==","shasum":"a1c1e8eaa03f077203af981e8f56d823d1e29f9f","tarball":"https://registry.npmjs.org/@absolutejs/audit-s3/-/audit-s3-0.1.1.tgz","fileCount":11,"unpackedSize":492312,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEQCIDG8eTagQ/kypbX6uyeHIznZ9hVSWK9vwDZqTaqLtaqXAiAstUUfGqrj5Hr4/XzHWzJ5QHyoRY49gR+8rGxn9CUNcA=="}]},"_npmUser":{"name":"alexkahndev","email":"alexkahn2019@gmail.com"},"directories":{},"maintainers":[{"name":"alexkahndev","email":"alexkahn2019@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/audit-s3_0.1.1_1784962377173_0.42041948981308264"},"_hasShrinkwrap":false}},"time":{"created":"2026-05-30T18:16:07.547Z","modified":"2026-07-25T06:52:57.510Z","0.0.1":"2026-05-30T18:16:07.916Z","0.0.2":"2026-07-13T17:55:20.626Z","0.1.0":"2026-07-24T17:43:02.817Z","0.1.1":"2026-07-25T06:52:57.309Z"},"bugs":{"url":"https://github.com/absolutejs/audit-adapters/issues"},"author":{"name":"Alex Kahn"},"license":"Apache-2.0","homepage":"https://github.com/absolutejs/audit-adapters/tree/main/s3","keywords":["audit","s3","r2","cloudflare","minio","backblaze","absolutejs","audit-log","compliance","worm"],"repository":{"type":"git","url":"git+https://github.com/absolutejs/audit-adapters.git","directory":"s3"},"description":"S3-compatible AuditSink for @absolutejs/audit. Buffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO. Time-sortable object keys; WORM-bucket-friendly for compliance retention.","maintainers":[{"name":"alexkahndev","email":"alexkahn2019@gmail.com"}],"readme":"# @absolutejs/audit-s3\n\nS3-compatible `AuditSink` for [@absolutejs/audit](https://github.com/absolutejs/audit).\n\nBuffered JSONL writes to AWS S3 / Cloudflare R2 / Backblaze B2 / MinIO — any\nstore with a \"put a string at a key\" API.\n\n## Why S3 for audit logs\n\n- **WORM (write-once-read-many) buckets** give legal hold for compliance\n  retention (SOC2, HIPAA, FedRAMP). The hash-chain in\n  `@absolutejs/audit`'s `withIntegrity()` gives tamper-evidence; WORM\n  prevents deletion even by an admin.\n- **Lifecycle policies** handle retention windows without a cron job.\n  \"Move to Glacier after 90 days, delete after 7 years\" is one bucket\n  policy.\n- **Cheap.** Cold-tier storage costs cents/GB-month.\n- **Queryable later** via Athena, DuckDB, or `s3 ls | xargs cat`.\n\nS3 objects are immutable, so the sink buffers events and flushes as JSONL\nfiles keyed by time. Object keys are lexically sortable; `s3 ls audit/`\nreturns events in chronological order.\n\n## Install\n\n```sh\nbun add @absolutejs/audit @absolutejs/audit-s3\n# Bring whichever S3 client you already use — no SDK lock-in:\nbun add @aws-sdk/client-s3      # OR\n# (Cloudflare R2 Workers binding — no install)\n```\n\n## Usage\n\n### AWS SDK v3\n\n```ts\nimport { S3Client, PutObjectCommand } from '@aws-sdk/client-s3';\nimport { createAudit, withIntegrity, memorySink } from '@absolutejs/audit';\nimport { createS3AuditSink } from '@absolutejs/audit-s3';\n\nconst s3 = new S3Client({ region: 'us-east-1' });\n\nconst audit = createAudit({\n  sinks: [\n    memorySink({ max: 1000 }),                          // hot tail for queries\n    withIntegrity(                                       // tamper-evident\n      createS3AuditSink({\n        put: async (key, body, contentType) => {\n          await s3.send(new PutObjectCommand({\n            Bucket: 'my-audit-bucket',\n            Key: key,\n            Body: body,\n            ContentType: contentType,\n          }));\n        },\n        prefix: 'audit/prod/',\n        flushIntervalMs: 5_000,\n      }),\n      { secret: process.env.AUDIT_SECRET, writerId: 'shard-A' }\n    ),\n  ],\n});\n\nawait audit.append({\n  kind: 'auth.login',\n  actor: 'user-123',\n  metadata: { ip: '10.0.0.1' },\n});\n\n// On graceful shutdown:\nawait audit.close();\n```\n\n### Cloudflare R2 (Workers)\n\n```ts\nimport { createS3AuditSink } from '@absolutejs/audit-s3';\n\nconst sink = createS3AuditSink({\n  put: async (key, body, contentType) => {\n    await env.AUDIT_BUCKET.put(key, body, { httpMetadata: { contentType } });\n  },\n});\n```\n\n### MinIO\n\nSame as AWS SDK — MinIO speaks S3 protocol. Point the `S3Client` at your\nMinIO endpoint and the adapter doesn't care.\n\n## Object key layout\n\nDefault `keyFor` produces:\n\n```\naudit/2026-05-30/19-42-15.123-abcd1234.jsonl\n```\n\n- **Date prefix** (`2026-05-30/`) — lifecycle policies key off this.\n- **Time component** (`19-42-15.123-`) — UTC `HH-MM-SS.mmm`. Lexical sort\n  = chronological order.\n- **8 hex chars random tail** — collision-resistant for two flushes at\n  the same millisecond.\n- **`.jsonl`** — one JSON-encoded event per line, trailing newline.\n\nOverride via the `keyFor` option for tenant-fan-out or hourly partitions.\n\n## Flush triggers\n\nWhichever fires first:\n\n| Trigger | Default | Option |\n|---|---|---|\n| Buffer reaches event count | 1000 | `maxBatchSize` |\n| Buffer reaches byte count | 5_000_000 (5 MB) | `maxBatchBytes` |\n| Time since last flush | 5_000 ms | `flushIntervalMs` |\n| Manual | (caller) | `await sink.flush()` |\n| Close | (caller) | `await sink.close()` |\n\nSet `flushIntervalMs: 0` to disable the periodic timer (size-only flushing).\n\n## Crash safety\n\nUnflushed events are **lost** on process kill. For stricter durability,\npair the S3 sink with a synchronous sink (Postgres) for critical events\n— S3 is the long-term archive, not the source of truth between flushes.\nLower `flushIntervalMs` to shrink the loss window at the cost of more\nS3 PUTs.\n\n## What this sink does NOT do\n\n- **`list` / `prune`** — not implemented. Read audit logs out of S3 via\n  Athena / `s3 ls` / DuckDB; enforce retention via S3 lifecycle policies.\n  The sink is write-only.\n- **Retry on PUT failure** — `onPutError` callback fires once; the batch\n  is dropped. Wire your own retry queue if you need at-least-once.\n- **Multipart upload** — every batch is one PUT. If your batches grow\n  past S3's 5GB PutObject limit you have other problems.\n\n## Integrity across batches\n\nThe tamper-evident chain from `withIntegrity()` works across batch\nboundaries automatically. Each event is hashed at append time against\nthe prior event's hash; the S3 sink only buffers + flushes — it doesn't\ntouch the chain. To verify a chain that spans multiple S3 objects:\n\n```ts\nimport { verifyChain } from '@absolutejs/audit';\n\n// Pull every JSONL object back, sort lexically (= chronologically), flatten:\nconst allEvents = orderedJsonlBodies.flatMap(body =>\n  body.split('\\n').filter(Boolean).map(line => JSON.parse(line))\n);\nconst result = await verifyChain(allEvents, secret);\n// { ok: true } or { ok: false, brokenAt: <index> }\n```\n\n## License\n\n[Apache 2.0](../LICENSE). Substrate-adjacent — rides `@absolutejs/audit`\n(BSL Tier A).\n","readmeFilename":"README.md"}