@fonderie/audit 5.0.1 → 5.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +7 -21
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +3 -17
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.cjs
CHANGED
|
@@ -25,10 +25,11 @@ __export(index_exports, {
|
|
|
25
25
|
module.exports = __toCommonJS(index_exports);
|
|
26
26
|
|
|
27
27
|
// src/routes.ts
|
|
28
|
-
var
|
|
28
|
+
var import_core2 = require("@fonderie/core");
|
|
29
29
|
var import_middlewares = require("@fonderie/core/middlewares");
|
|
30
30
|
|
|
31
31
|
// src/dtos/audit.ts
|
|
32
|
+
var import_core = require("@fonderie/core");
|
|
32
33
|
function toAuditEventDTO(e) {
|
|
33
34
|
return {
|
|
34
35
|
id: e.id,
|
|
@@ -39,26 +40,11 @@ function toAuditEventDTO(e) {
|
|
|
39
40
|
createdAt: e.createdAt.toISOString()
|
|
40
41
|
};
|
|
41
42
|
}
|
|
42
|
-
var CURSOR_TS_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,6})?(Z|[+-]\d{2}(:?\d{2})?)?$/;
|
|
43
|
-
var CURSOR_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
44
43
|
function encodeCursor(createdAt, id) {
|
|
45
44
|
const ts = typeof createdAt === "string" ? createdAt : createdAt.toISOString();
|
|
46
|
-
return
|
|
47
|
-
}
|
|
48
|
-
function decodeCursor(cursor) {
|
|
49
|
-
if (cursor.length > 256) return null;
|
|
50
|
-
try {
|
|
51
|
-
const raw = Buffer.from(cursor, "base64").toString();
|
|
52
|
-
const commaIdx = raw.indexOf(",");
|
|
53
|
-
if (commaIdx === -1) return null;
|
|
54
|
-
const createdAt = raw.slice(0, commaIdx);
|
|
55
|
-
const id = raw.slice(commaIdx + 1);
|
|
56
|
-
if (!CURSOR_TS_RE.test(createdAt) || !CURSOR_ID_RE.test(id)) return null;
|
|
57
|
-
return { createdAt, id };
|
|
58
|
-
} catch {
|
|
59
|
-
return null;
|
|
60
|
-
}
|
|
45
|
+
return (0, import_core.encodeKeysetCursor)(ts, id);
|
|
61
46
|
}
|
|
47
|
+
var decodeCursor = import_core.decodeKeysetCursor;
|
|
62
48
|
|
|
63
49
|
// src/models/event.model.ts
|
|
64
50
|
var MAX_LIMIT = 200;
|
|
@@ -123,8 +109,8 @@ function buildAuditRoutes(store) {
|
|
|
123
109
|
import_middlewares.requireAuth,
|
|
124
110
|
async (ctx) => {
|
|
125
111
|
if (!ctx.workspace)
|
|
126
|
-
return (0,
|
|
127
|
-
|
|
112
|
+
return (0, import_core2.setApiResponse)(
|
|
113
|
+
import_core2.HTTP.UNPROCESSABLE,
|
|
128
114
|
"MISSING_WORKSPACE",
|
|
129
115
|
"Workspace context required"
|
|
130
116
|
);
|
|
@@ -149,7 +135,7 @@ function buildAuditRoutes(store) {
|
|
|
149
135
|
const { events, hasMore } = await new AuditEventModel(store).list(query);
|
|
150
136
|
const lastEvent = events[events.length - 1];
|
|
151
137
|
const nextCursor = hasMore && lastEvent ? encodeCursor(lastEvent.createdAtRaw ?? lastEvent.createdAt, lastEvent.id) : null;
|
|
152
|
-
return (0,
|
|
138
|
+
return (0, import_core2.setApiResponse)(import_core2.HTTP.OK, "AUDIT_FETCHED", "Audit events retrieved.", {
|
|
153
139
|
events: events.map(toAuditEventDTO),
|
|
154
140
|
nextCursor
|
|
155
141
|
});
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/routes.ts","../src/dtos/audit.ts","../src/models/event.model.ts","../src/module.ts"],"sourcesContent":["export { AuditModule } from './module';\nexport type { IAuditEvent, IAuditQuery } from './types';\nexport type { IAuditEventDTO, IAuditPageDTO } from './dtos/audit';\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { AuditEventModel } from './models/event.model';\nimport { toAuditEventDTO, encodeCursor } from './dtos/audit';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildAuditRoutes(store: IStoreAdapter): Route[] {\n\treturn [\n\t\t[\n\t\t\t'GET',\n\t\t\t'/audit',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst url = new URL(ctx.request.url);\n\t\t\t\tconst params = url.searchParams;\n\n\t\t\t\tconst rawLimit = Number(params.get('limit') ?? 50);\n\t\t\t\tconst limit = Number.isFinite(rawLimit)\n\t\t\t\t\t? Math.min(Math.max(Math.trunc(rawLimit), 1), 200)\n\t\t\t\t\t: 50;\n\t\t\t\tconst type = params.get('type') ?? undefined;\n\t\t\t\tconst actor = params.get('actorId') ?? undefined;\n\t\t\t\tconst from = params.get('from') ? new Date(params.get('from')!) : undefined;\n\t\t\t\tconst to = params.get('to') ? new Date(params.get('to')!) : undefined;\n\t\t\t\tconst cursor = params.get('cursor') ?? undefined;\n\n\t\t\t\tconst query: Parameters<AuditEventModel['list']>[0] = {\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\tlimit,\n\t\t\t\t};\n\t\t\t\tif (type) query.type = type;\n\t\t\t\tif (actor) query.actorId = actor;\n\t\t\t\tif (from) query.from = from;\n\t\t\t\tif (to) query.to = to;\n\t\t\t\tif (cursor) query.cursor = cursor;\n\n\t\t\t\t// The model over-fetches internally to detect a next page; the\n\t\t\t\t// cursor carries the row's created_at::text at full microsecond\n\t\t\t\t// precision so same-millisecond events can't be skipped.\n\t\t\t\tconst { events, hasMore } = await new AuditEventModel(store).list(query);\n\t\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\t\tconst nextCursor =\n\t\t\t\t\thasMore && lastEvent\n\t\t\t\t\t\t? encodeCursor(lastEvent.createdAtRaw ?? lastEvent.createdAt, lastEvent.id)\n\t\t\t\t\t\t: null;\n\n\t\t\t\treturn setApiResponse(HTTP.OK, 'AUDIT_FETCHED', 'Audit events retrieved.', {\n\t\t\t\t\tevents: events.map(toAuditEventDTO),\n\t\t\t\t\tnextCursor,\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\t];\n}\n","import type { IAuditEvent } from '../types';\n\nexport interface IAuditEventDTO {\n\tid: string;\n\ttype: string;\n\tactorId: string | null;\n\trequestId: string | null;\n\tpayload: Record<string, unknown>;\n\tcreatedAt: string;\n}\n\nexport interface IAuditPageDTO {\n\tevents: IAuditEventDTO[];\n\tnextCursor: string | null;\n}\n\nexport function toAuditEventDTO(e: IAuditEvent): IAuditEventDTO {\n\treturn {\n\t\tid: e.id,\n\t\ttype: e.type,\n\t\tactorId: (e.payload['userId'] as string | undefined) ?? null,\n\t\trequestId: (e.meta['requestId'] as string | undefined) ?? null,\n\t\tpayload: e.payload,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\n// Cursors accept ISO timestamps and Postgres' own text format (microsecond\n// precision, e.g. '2026-09-05 00:38:31.123456+00') — the keyset carries the\n// latter, because round-tripping through a millisecond JS Date truncates\n// sub-millisecond digits and silently skips same-millisecond events (rows\n// created in one transaction all share now() exactly) between pages.\nconst CURSOR_TS_RE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?(Z|[+-]\\d{2}(:?\\d{2})?)?$/;\nconst CURSOR_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n\nexport function encodeCursor(createdAt: Date | string, id: string): string {\n\tconst ts = typeof createdAt === 'string' ? createdAt : createdAt.toISOString();\n\treturn Buffer.from(`${ts},${id}`).toString('base64');\n}\n\nexport function decodeCursor(cursor: string): { createdAt: string; id: string } | null {\n\tif (cursor.length > 256) return null;\n\ttry {\n\t\tconst raw = Buffer.from(cursor, 'base64').toString();\n\t\tconst commaIdx = raw.indexOf(',');\n\t\tif (commaIdx === -1) return null;\n\t\tconst createdAt = raw.slice(0, commaIdx);\n\t\tconst id = raw.slice(commaIdx + 1);\n\t\t// Both halves feed ::timestamptz / ::uuid casts — validate here so a\n\t\t// crafted cursor yields an empty page, not a Postgres cast error.\n\t\tif (!CURSOR_TS_RE.test(createdAt) || !CURSOR_ID_RE.test(id)) return null;\n\t\treturn { createdAt, id };\n\t} catch {\n\t\treturn null;\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IAuditEvent, IAuditQuery } from '../types';\nimport { decodeCursor } from '../dtos/audit';\n\nconst MAX_LIMIT = 200;\n\nexport type IAuditEventRow = IAuditEvent & {\n\t// created_at::text — full microsecond precision for the keyset cursor\n\t// (node-pg parses timestamptz into a millisecond Date, which would make\n\t// the cursor skip same-millisecond rows between pages).\n\tcreatedAtRaw: string;\n};\n\nexport interface IAuditEventPage {\n\tevents: IAuditEventRow[];\n\thasMore: boolean;\n}\n\nexport class AuditEventModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\t// `query.limit` is the CLIENT-facing page size. The +1 over-fetch that\n\t// detects a next page happens inside this method — keeping it here means\n\t// no outer clamp can shave it off (which previously made nextCursor\n\t// unreachable at the max page size).\n\tasync list(query: IAuditQuery): Promise<IAuditEventPage> {\n\t\tconst limit = Math.min(query.limit ?? 50, MAX_LIMIT);\n\t\tconst params: unknown[] = [query.workspaceId];\n\t\tconst where: string[] = [`payload->>'workspaceId' = $1`];\n\n\t\tif (query.type) {\n\t\t\tparams.push(query.type);\n\t\t\twhere.push(`type = $${params.length}`);\n\t\t}\n\n\t\tif (query.actorId) {\n\t\t\tparams.push(query.actorId);\n\t\t\twhere.push(`payload->>'userId' = $${params.length}`);\n\t\t}\n\n\t\tif (query.from) {\n\t\t\tparams.push(query.from);\n\t\t\twhere.push(`created_at >= $${params.length}`);\n\t\t}\n\n\t\tif (query.to) {\n\t\t\tparams.push(query.to);\n\t\t\twhere.push(`created_at <= $${params.length}`);\n\t\t}\n\n\t\tif (query.cursor) {\n\t\t\tconst decoded = decodeCursor(query.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tparams.push(decoded.createdAt, decoded.id);\n\t\t\t\twhere.push(\n\t\t\t\t\t`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tparams.push(limit + 1);\n\n\t\tconst rows = await this.store.query<IAuditEventRow>(\n\t\t\t`SELECT id, type, payload, meta, created_at as \"createdAt\",\n\t\t\t created_at::text as \"createdAtRaw\"\n\t\t\t FROM fonderie_events\n\t\t\t WHERE ${where.join(' AND ')}\n\t\t\t ORDER BY created_at DESC, id DESC\n\t\t\t LIMIT $${params.length}`,\n\t\t\tparams,\n\t\t);\n\n\t\treturn { events: rows.slice(0, limit), hasMore: rows.length > limit };\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { buildAuditRoutes } from './routes';\n\nexport class AuditModule implements IFonderieModule {\n\treadonly name = '@fonderie/audit';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tfor (const [method, path, ...handlers] of buildAuditRoutes(this.store)) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/routes.ts","../src/dtos/audit.ts","../src/models/event.model.ts","../src/module.ts"],"sourcesContent":["export { AuditModule } from './module';\nexport type { IAuditEvent, IAuditQuery } from './types';\nexport type { IAuditEventDTO, IAuditPageDTO } from './dtos/audit';\n","import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { AuditEventModel } from './models/event.model';\nimport { toAuditEventDTO, encodeCursor } from './dtos/audit';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildAuditRoutes(store: IStoreAdapter): Route[] {\n\treturn [\n\t\t[\n\t\t\t'GET',\n\t\t\t'/audit',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst url = new URL(ctx.request.url);\n\t\t\t\tconst params = url.searchParams;\n\n\t\t\t\tconst rawLimit = Number(params.get('limit') ?? 50);\n\t\t\t\tconst limit = Number.isFinite(rawLimit)\n\t\t\t\t\t? Math.min(Math.max(Math.trunc(rawLimit), 1), 200)\n\t\t\t\t\t: 50;\n\t\t\t\tconst type = params.get('type') ?? undefined;\n\t\t\t\tconst actor = params.get('actorId') ?? undefined;\n\t\t\t\tconst from = params.get('from') ? new Date(params.get('from')!) : undefined;\n\t\t\t\tconst to = params.get('to') ? new Date(params.get('to')!) : undefined;\n\t\t\t\tconst cursor = params.get('cursor') ?? undefined;\n\n\t\t\t\tconst query: Parameters<AuditEventModel['list']>[0] = {\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\tlimit,\n\t\t\t\t};\n\t\t\t\tif (type) query.type = type;\n\t\t\t\tif (actor) query.actorId = actor;\n\t\t\t\tif (from) query.from = from;\n\t\t\t\tif (to) query.to = to;\n\t\t\t\tif (cursor) query.cursor = cursor;\n\n\t\t\t\t// The model over-fetches internally to detect a next page; the\n\t\t\t\t// cursor carries the row's created_at::text at full microsecond\n\t\t\t\t// precision so same-millisecond events can't be skipped.\n\t\t\t\tconst { events, hasMore } = await new AuditEventModel(store).list(query);\n\t\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\t\tconst nextCursor =\n\t\t\t\t\thasMore && lastEvent\n\t\t\t\t\t\t? encodeCursor(lastEvent.createdAtRaw ?? lastEvent.createdAt, lastEvent.id)\n\t\t\t\t\t\t: null;\n\n\t\t\t\treturn setApiResponse(HTTP.OK, 'AUDIT_FETCHED', 'Audit events retrieved.', {\n\t\t\t\t\tevents: events.map(toAuditEventDTO),\n\t\t\t\t\tnextCursor,\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\t];\n}\n","import { encodeKeysetCursor, decodeKeysetCursor } from '@fonderie/core';\n\nimport type { IAuditEvent } from '../types';\n\nexport interface IAuditEventDTO {\n\tid: string;\n\ttype: string;\n\tactorId: string | null;\n\trequestId: string | null;\n\tpayload: Record<string, unknown>;\n\tcreatedAt: string;\n}\n\nexport interface IAuditPageDTO {\n\tevents: IAuditEventDTO[];\n\tnextCursor: string | null;\n}\n\nexport function toAuditEventDTO(e: IAuditEvent): IAuditEventDTO {\n\treturn {\n\t\tid: e.id,\n\t\ttype: e.type,\n\t\tactorId: (e.payload['userId'] as string | undefined) ?? null,\n\t\trequestId: (e.meta['requestId'] as string | undefined) ?? null,\n\t\tpayload: e.payload,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\n// Cursors accept ISO timestamps and Postgres' own text format (microsecond\n// precision, e.g. '2026-09-05 00:38:31.123456+00') — the keyset carries the\n// latter, because round-tripping through a millisecond JS Date truncates\n// sub-millisecond digits and silently skips same-millisecond events (rows\n// created in one transaction all share now() exactly) between pages.\n// The audit event log's keyset cursor is the shared @fonderie/core primitive.\n// decodeKeysetCursor RANGE-checks the timestamp, so a crafted in-shape-but-out-\n// of-range cursor now decodes to null — the model drops the keyset predicate and\n// returns the first (newest) page — instead of reaching the ::timestamptz cast\n// as a 500, the bug the old lax local regex had.\nexport function encodeCursor(createdAt: Date | string, id: string): string {\n\tconst ts = typeof createdAt === 'string' ? createdAt : createdAt.toISOString();\n\treturn encodeKeysetCursor(ts, id);\n}\n\nexport const decodeCursor = decodeKeysetCursor;\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IAuditEvent, IAuditQuery } from '../types';\nimport { decodeCursor } from '../dtos/audit';\n\nconst MAX_LIMIT = 200;\n\nexport type IAuditEventRow = IAuditEvent & {\n\t// created_at::text — full microsecond precision for the keyset cursor\n\t// (node-pg parses timestamptz into a millisecond Date, which would make\n\t// the cursor skip same-millisecond rows between pages).\n\tcreatedAtRaw: string;\n};\n\nexport interface IAuditEventPage {\n\tevents: IAuditEventRow[];\n\thasMore: boolean;\n}\n\nexport class AuditEventModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\t// `query.limit` is the CLIENT-facing page size. The +1 over-fetch that\n\t// detects a next page happens inside this method — keeping it here means\n\t// no outer clamp can shave it off (which previously made nextCursor\n\t// unreachable at the max page size).\n\tasync list(query: IAuditQuery): Promise<IAuditEventPage> {\n\t\tconst limit = Math.min(query.limit ?? 50, MAX_LIMIT);\n\t\tconst params: unknown[] = [query.workspaceId];\n\t\tconst where: string[] = [`payload->>'workspaceId' = $1`];\n\n\t\tif (query.type) {\n\t\t\tparams.push(query.type);\n\t\t\twhere.push(`type = $${params.length}`);\n\t\t}\n\n\t\tif (query.actorId) {\n\t\t\tparams.push(query.actorId);\n\t\t\twhere.push(`payload->>'userId' = $${params.length}`);\n\t\t}\n\n\t\tif (query.from) {\n\t\t\tparams.push(query.from);\n\t\t\twhere.push(`created_at >= $${params.length}`);\n\t\t}\n\n\t\tif (query.to) {\n\t\t\tparams.push(query.to);\n\t\t\twhere.push(`created_at <= $${params.length}`);\n\t\t}\n\n\t\tif (query.cursor) {\n\t\t\tconst decoded = decodeCursor(query.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tparams.push(decoded.createdAt, decoded.id);\n\t\t\t\twhere.push(\n\t\t\t\t\t`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tparams.push(limit + 1);\n\n\t\tconst rows = await this.store.query<IAuditEventRow>(\n\t\t\t`SELECT id, type, payload, meta, created_at as \"createdAt\",\n\t\t\t created_at::text as \"createdAtRaw\"\n\t\t\t FROM fonderie_events\n\t\t\t WHERE ${where.join(' AND ')}\n\t\t\t ORDER BY created_at DESC, id DESC\n\t\t\t LIMIT $${params.length}`,\n\t\t\tparams,\n\t\t);\n\n\t\treturn { events: rows.slice(0, limit), hasMore: rows.length > limit };\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { buildAuditRoutes } from './routes';\n\nexport class AuditModule implements IFonderieModule {\n\treadonly name = '@fonderie/audit';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tfor (const [method, path, ...handlers] of buildAuditRoutes(this.store)) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":["import_core"]}
|
package/dist/index.js
CHANGED
|
@@ -3,6 +3,7 @@ import { setApiResponse, HTTP } from "@fonderie/core";
|
|
|
3
3
|
import { requireAuth } from "@fonderie/core/middlewares";
|
|
4
4
|
|
|
5
5
|
// src/dtos/audit.ts
|
|
6
|
+
import { encodeKeysetCursor, decodeKeysetCursor } from "@fonderie/core";
|
|
6
7
|
function toAuditEventDTO(e) {
|
|
7
8
|
return {
|
|
8
9
|
id: e.id,
|
|
@@ -13,26 +14,11 @@ function toAuditEventDTO(e) {
|
|
|
13
14
|
createdAt: e.createdAt.toISOString()
|
|
14
15
|
};
|
|
15
16
|
}
|
|
16
|
-
var CURSOR_TS_RE = /^\d{4}-\d{2}-\d{2}[T ]\d{2}:\d{2}:\d{2}(\.\d{1,6})?(Z|[+-]\d{2}(:?\d{2})?)?$/;
|
|
17
|
-
var CURSOR_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;
|
|
18
17
|
function encodeCursor(createdAt, id) {
|
|
19
18
|
const ts = typeof createdAt === "string" ? createdAt : createdAt.toISOString();
|
|
20
|
-
return
|
|
21
|
-
}
|
|
22
|
-
function decodeCursor(cursor) {
|
|
23
|
-
if (cursor.length > 256) return null;
|
|
24
|
-
try {
|
|
25
|
-
const raw = Buffer.from(cursor, "base64").toString();
|
|
26
|
-
const commaIdx = raw.indexOf(",");
|
|
27
|
-
if (commaIdx === -1) return null;
|
|
28
|
-
const createdAt = raw.slice(0, commaIdx);
|
|
29
|
-
const id = raw.slice(commaIdx + 1);
|
|
30
|
-
if (!CURSOR_TS_RE.test(createdAt) || !CURSOR_ID_RE.test(id)) return null;
|
|
31
|
-
return { createdAt, id };
|
|
32
|
-
} catch {
|
|
33
|
-
return null;
|
|
34
|
-
}
|
|
19
|
+
return encodeKeysetCursor(ts, id);
|
|
35
20
|
}
|
|
21
|
+
var decodeCursor = decodeKeysetCursor;
|
|
36
22
|
|
|
37
23
|
// src/models/event.model.ts
|
|
38
24
|
var MAX_LIMIT = 200;
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/routes.ts","../src/dtos/audit.ts","../src/models/event.model.ts","../src/module.ts"],"sourcesContent":["import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { AuditEventModel } from './models/event.model';\nimport { toAuditEventDTO, encodeCursor } from './dtos/audit';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildAuditRoutes(store: IStoreAdapter): Route[] {\n\treturn [\n\t\t[\n\t\t\t'GET',\n\t\t\t'/audit',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst url = new URL(ctx.request.url);\n\t\t\t\tconst params = url.searchParams;\n\n\t\t\t\tconst rawLimit = Number(params.get('limit') ?? 50);\n\t\t\t\tconst limit = Number.isFinite(rawLimit)\n\t\t\t\t\t? Math.min(Math.max(Math.trunc(rawLimit), 1), 200)\n\t\t\t\t\t: 50;\n\t\t\t\tconst type = params.get('type') ?? undefined;\n\t\t\t\tconst actor = params.get('actorId') ?? undefined;\n\t\t\t\tconst from = params.get('from') ? new Date(params.get('from')!) : undefined;\n\t\t\t\tconst to = params.get('to') ? new Date(params.get('to')!) : undefined;\n\t\t\t\tconst cursor = params.get('cursor') ?? undefined;\n\n\t\t\t\tconst query: Parameters<AuditEventModel['list']>[0] = {\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\tlimit,\n\t\t\t\t};\n\t\t\t\tif (type) query.type = type;\n\t\t\t\tif (actor) query.actorId = actor;\n\t\t\t\tif (from) query.from = from;\n\t\t\t\tif (to) query.to = to;\n\t\t\t\tif (cursor) query.cursor = cursor;\n\n\t\t\t\t// The model over-fetches internally to detect a next page; the\n\t\t\t\t// cursor carries the row's created_at::text at full microsecond\n\t\t\t\t// precision so same-millisecond events can't be skipped.\n\t\t\t\tconst { events, hasMore } = await new AuditEventModel(store).list(query);\n\t\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\t\tconst nextCursor =\n\t\t\t\t\thasMore && lastEvent\n\t\t\t\t\t\t? encodeCursor(lastEvent.createdAtRaw ?? lastEvent.createdAt, lastEvent.id)\n\t\t\t\t\t\t: null;\n\n\t\t\t\treturn setApiResponse(HTTP.OK, 'AUDIT_FETCHED', 'Audit events retrieved.', {\n\t\t\t\t\tevents: events.map(toAuditEventDTO),\n\t\t\t\t\tnextCursor,\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\t];\n}\n","import type { IAuditEvent } from '../types';\n\nexport interface IAuditEventDTO {\n\tid: string;\n\ttype: string;\n\tactorId: string | null;\n\trequestId: string | null;\n\tpayload: Record<string, unknown>;\n\tcreatedAt: string;\n}\n\nexport interface IAuditPageDTO {\n\tevents: IAuditEventDTO[];\n\tnextCursor: string | null;\n}\n\nexport function toAuditEventDTO(e: IAuditEvent): IAuditEventDTO {\n\treturn {\n\t\tid: e.id,\n\t\ttype: e.type,\n\t\tactorId: (e.payload['userId'] as string | undefined) ?? null,\n\t\trequestId: (e.meta['requestId'] as string | undefined) ?? null,\n\t\tpayload: e.payload,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\n// Cursors accept ISO timestamps and Postgres' own text format (microsecond\n// precision, e.g. '2026-09-05 00:38:31.123456+00') — the keyset carries the\n// latter, because round-tripping through a millisecond JS Date truncates\n// sub-millisecond digits and silently skips same-millisecond events (rows\n// created in one transaction all share now() exactly) between pages.\nconst CURSOR_TS_RE = /^\\d{4}-\\d{2}-\\d{2}[T ]\\d{2}:\\d{2}:\\d{2}(\\.\\d{1,6})?(Z|[+-]\\d{2}(:?\\d{2})?)?$/;\nconst CURSOR_ID_RE = /^[0-9a-fA-F]{8}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{4}-[0-9a-fA-F]{12}$/;\n\nexport function encodeCursor(createdAt: Date | string, id: string): string {\n\tconst ts = typeof createdAt === 'string' ? createdAt : createdAt.toISOString();\n\treturn Buffer.from(`${ts},${id}`).toString('base64');\n}\n\nexport function decodeCursor(cursor: string): { createdAt: string; id: string } | null {\n\tif (cursor.length > 256) return null;\n\ttry {\n\t\tconst raw = Buffer.from(cursor, 'base64').toString();\n\t\tconst commaIdx = raw.indexOf(',');\n\t\tif (commaIdx === -1) return null;\n\t\tconst createdAt = raw.slice(0, commaIdx);\n\t\tconst id = raw.slice(commaIdx + 1);\n\t\t// Both halves feed ::timestamptz / ::uuid casts — validate here so a\n\t\t// crafted cursor yields an empty page, not a Postgres cast error.\n\t\tif (!CURSOR_TS_RE.test(createdAt) || !CURSOR_ID_RE.test(id)) return null;\n\t\treturn { createdAt, id };\n\t} catch {\n\t\treturn null;\n\t}\n}\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IAuditEvent, IAuditQuery } from '../types';\nimport { decodeCursor } from '../dtos/audit';\n\nconst MAX_LIMIT = 200;\n\nexport type IAuditEventRow = IAuditEvent & {\n\t// created_at::text — full microsecond precision for the keyset cursor\n\t// (node-pg parses timestamptz into a millisecond Date, which would make\n\t// the cursor skip same-millisecond rows between pages).\n\tcreatedAtRaw: string;\n};\n\nexport interface IAuditEventPage {\n\tevents: IAuditEventRow[];\n\thasMore: boolean;\n}\n\nexport class AuditEventModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\t// `query.limit` is the CLIENT-facing page size. The +1 over-fetch that\n\t// detects a next page happens inside this method — keeping it here means\n\t// no outer clamp can shave it off (which previously made nextCursor\n\t// unreachable at the max page size).\n\tasync list(query: IAuditQuery): Promise<IAuditEventPage> {\n\t\tconst limit = Math.min(query.limit ?? 50, MAX_LIMIT);\n\t\tconst params: unknown[] = [query.workspaceId];\n\t\tconst where: string[] = [`payload->>'workspaceId' = $1`];\n\n\t\tif (query.type) {\n\t\t\tparams.push(query.type);\n\t\t\twhere.push(`type = $${params.length}`);\n\t\t}\n\n\t\tif (query.actorId) {\n\t\t\tparams.push(query.actorId);\n\t\t\twhere.push(`payload->>'userId' = $${params.length}`);\n\t\t}\n\n\t\tif (query.from) {\n\t\t\tparams.push(query.from);\n\t\t\twhere.push(`created_at >= $${params.length}`);\n\t\t}\n\n\t\tif (query.to) {\n\t\t\tparams.push(query.to);\n\t\t\twhere.push(`created_at <= $${params.length}`);\n\t\t}\n\n\t\tif (query.cursor) {\n\t\t\tconst decoded = decodeCursor(query.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tparams.push(decoded.createdAt, decoded.id);\n\t\t\t\twhere.push(\n\t\t\t\t\t`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tparams.push(limit + 1);\n\n\t\tconst rows = await this.store.query<IAuditEventRow>(\n\t\t\t`SELECT id, type, payload, meta, created_at as \"createdAt\",\n\t\t\t created_at::text as \"createdAtRaw\"\n\t\t\t FROM fonderie_events\n\t\t\t WHERE ${where.join(' AND ')}\n\t\t\t ORDER BY created_at DESC, id DESC\n\t\t\t LIMIT $${params.length}`,\n\t\t\tparams,\n\t\t);\n\n\t\treturn { events: rows.slice(0, limit), hasMore: rows.length > limit };\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { buildAuditRoutes } from './routes';\n\nexport class AuditModule implements IFonderieModule {\n\treadonly name = '@fonderie/audit';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tfor (const [method, path, ...handlers] of buildAuditRoutes(this.store)) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
|
1
|
+
{"version":3,"sources":["../src/routes.ts","../src/dtos/audit.ts","../src/models/event.model.ts","../src/module.ts"],"sourcesContent":["import type { Middleware } from '@fonderie/core';\nimport { setApiResponse, HTTP } from '@fonderie/core';\nimport { requireAuth } from '@fonderie/core/middlewares';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { AuditEventModel } from './models/event.model';\nimport { toAuditEventDTO, encodeCursor } from './dtos/audit';\n\ntype Route = [string, string, ...Middleware[]];\n\nexport function buildAuditRoutes(store: IStoreAdapter): Route[] {\n\treturn [\n\t\t[\n\t\t\t'GET',\n\t\t\t'/audit',\n\t\t\trequireAuth,\n\t\t\tasync (ctx) => {\n\t\t\t\tif (!ctx.workspace)\n\t\t\t\t\treturn setApiResponse(\n\t\t\t\t\t\tHTTP.UNPROCESSABLE,\n\t\t\t\t\t\t'MISSING_WORKSPACE',\n\t\t\t\t\t\t'Workspace context required',\n\t\t\t\t\t);\n\n\t\t\t\tconst url = new URL(ctx.request.url);\n\t\t\t\tconst params = url.searchParams;\n\n\t\t\t\tconst rawLimit = Number(params.get('limit') ?? 50);\n\t\t\t\tconst limit = Number.isFinite(rawLimit)\n\t\t\t\t\t? Math.min(Math.max(Math.trunc(rawLimit), 1), 200)\n\t\t\t\t\t: 50;\n\t\t\t\tconst type = params.get('type') ?? undefined;\n\t\t\t\tconst actor = params.get('actorId') ?? undefined;\n\t\t\t\tconst from = params.get('from') ? new Date(params.get('from')!) : undefined;\n\t\t\t\tconst to = params.get('to') ? new Date(params.get('to')!) : undefined;\n\t\t\t\tconst cursor = params.get('cursor') ?? undefined;\n\n\t\t\t\tconst query: Parameters<AuditEventModel['list']>[0] = {\n\t\t\t\t\tworkspaceId: ctx.workspace.id,\n\t\t\t\t\tlimit,\n\t\t\t\t};\n\t\t\t\tif (type) query.type = type;\n\t\t\t\tif (actor) query.actorId = actor;\n\t\t\t\tif (from) query.from = from;\n\t\t\t\tif (to) query.to = to;\n\t\t\t\tif (cursor) query.cursor = cursor;\n\n\t\t\t\t// The model over-fetches internally to detect a next page; the\n\t\t\t\t// cursor carries the row's created_at::text at full microsecond\n\t\t\t\t// precision so same-millisecond events can't be skipped.\n\t\t\t\tconst { events, hasMore } = await new AuditEventModel(store).list(query);\n\t\t\t\tconst lastEvent = events[events.length - 1];\n\t\t\t\tconst nextCursor =\n\t\t\t\t\thasMore && lastEvent\n\t\t\t\t\t\t? encodeCursor(lastEvent.createdAtRaw ?? lastEvent.createdAt, lastEvent.id)\n\t\t\t\t\t\t: null;\n\n\t\t\t\treturn setApiResponse(HTTP.OK, 'AUDIT_FETCHED', 'Audit events retrieved.', {\n\t\t\t\t\tevents: events.map(toAuditEventDTO),\n\t\t\t\t\tnextCursor,\n\t\t\t\t});\n\t\t\t},\n\t\t],\n\t];\n}\n","import { encodeKeysetCursor, decodeKeysetCursor } from '@fonderie/core';\n\nimport type { IAuditEvent } from '../types';\n\nexport interface IAuditEventDTO {\n\tid: string;\n\ttype: string;\n\tactorId: string | null;\n\trequestId: string | null;\n\tpayload: Record<string, unknown>;\n\tcreatedAt: string;\n}\n\nexport interface IAuditPageDTO {\n\tevents: IAuditEventDTO[];\n\tnextCursor: string | null;\n}\n\nexport function toAuditEventDTO(e: IAuditEvent): IAuditEventDTO {\n\treturn {\n\t\tid: e.id,\n\t\ttype: e.type,\n\t\tactorId: (e.payload['userId'] as string | undefined) ?? null,\n\t\trequestId: (e.meta['requestId'] as string | undefined) ?? null,\n\t\tpayload: e.payload,\n\t\tcreatedAt: e.createdAt.toISOString(),\n\t};\n}\n\n// Cursors accept ISO timestamps and Postgres' own text format (microsecond\n// precision, e.g. '2026-09-05 00:38:31.123456+00') — the keyset carries the\n// latter, because round-tripping through a millisecond JS Date truncates\n// sub-millisecond digits and silently skips same-millisecond events (rows\n// created in one transaction all share now() exactly) between pages.\n// The audit event log's keyset cursor is the shared @fonderie/core primitive.\n// decodeKeysetCursor RANGE-checks the timestamp, so a crafted in-shape-but-out-\n// of-range cursor now decodes to null — the model drops the keyset predicate and\n// returns the first (newest) page — instead of reaching the ::timestamptz cast\n// as a 500, the bug the old lax local regex had.\nexport function encodeCursor(createdAt: Date | string, id: string): string {\n\tconst ts = typeof createdAt === 'string' ? createdAt : createdAt.toISOString();\n\treturn encodeKeysetCursor(ts, id);\n}\n\nexport const decodeCursor = decodeKeysetCursor;\n","import type { IStoreAdapter } from '@fonderie/store';\n\nimport type { IAuditEvent, IAuditQuery } from '../types';\nimport { decodeCursor } from '../dtos/audit';\n\nconst MAX_LIMIT = 200;\n\nexport type IAuditEventRow = IAuditEvent & {\n\t// created_at::text — full microsecond precision for the keyset cursor\n\t// (node-pg parses timestamptz into a millisecond Date, which would make\n\t// the cursor skip same-millisecond rows between pages).\n\tcreatedAtRaw: string;\n};\n\nexport interface IAuditEventPage {\n\tevents: IAuditEventRow[];\n\thasMore: boolean;\n}\n\nexport class AuditEventModel {\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\t// `query.limit` is the CLIENT-facing page size. The +1 over-fetch that\n\t// detects a next page happens inside this method — keeping it here means\n\t// no outer clamp can shave it off (which previously made nextCursor\n\t// unreachable at the max page size).\n\tasync list(query: IAuditQuery): Promise<IAuditEventPage> {\n\t\tconst limit = Math.min(query.limit ?? 50, MAX_LIMIT);\n\t\tconst params: unknown[] = [query.workspaceId];\n\t\tconst where: string[] = [`payload->>'workspaceId' = $1`];\n\n\t\tif (query.type) {\n\t\t\tparams.push(query.type);\n\t\t\twhere.push(`type = $${params.length}`);\n\t\t}\n\n\t\tif (query.actorId) {\n\t\t\tparams.push(query.actorId);\n\t\t\twhere.push(`payload->>'userId' = $${params.length}`);\n\t\t}\n\n\t\tif (query.from) {\n\t\t\tparams.push(query.from);\n\t\t\twhere.push(`created_at >= $${params.length}`);\n\t\t}\n\n\t\tif (query.to) {\n\t\t\tparams.push(query.to);\n\t\t\twhere.push(`created_at <= $${params.length}`);\n\t\t}\n\n\t\tif (query.cursor) {\n\t\t\tconst decoded = decodeCursor(query.cursor);\n\t\t\tif (decoded) {\n\t\t\t\tparams.push(decoded.createdAt, decoded.id);\n\t\t\t\twhere.push(\n\t\t\t\t\t`(created_at, id) < ($${params.length - 1}::timestamptz, $${params.length}::uuid)`,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tparams.push(limit + 1);\n\n\t\tconst rows = await this.store.query<IAuditEventRow>(\n\t\t\t`SELECT id, type, payload, meta, created_at as \"createdAt\",\n\t\t\t created_at::text as \"createdAtRaw\"\n\t\t\t FROM fonderie_events\n\t\t\t WHERE ${where.join(' AND ')}\n\t\t\t ORDER BY created_at DESC, id DESC\n\t\t\t LIMIT $${params.length}`,\n\t\t\tparams,\n\t\t);\n\n\t\treturn { events: rows.slice(0, limit), hasMore: rows.length > limit };\n\t}\n}\n","import type { IFonderieModule, IFonderieApp } from '@fonderie/core';\nimport type { IStoreAdapter } from '@fonderie/store';\n\nimport { buildAuditRoutes } from './routes';\n\nexport class AuditModule implements IFonderieModule {\n\treadonly name = '@fonderie/audit';\n\treadonly deps = ['@fonderie/auth', '@fonderie/workspaces'];\n\n\tconstructor(private readonly store: IStoreAdapter) {}\n\n\tinstall(app: IFonderieApp): void {\n\t\tfor (const [method, path, ...handlers] of buildAuditRoutes(this.store)) {\n\t\t\tapp.addRoute(method, path, ...handlers);\n\t\t}\n\t}\n}\n"],"mappings":"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","names":[]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@fonderie/audit",
|
|
3
|
-
"version": "5.0
|
|
3
|
+
"version": "5.1.0",
|
|
4
4
|
"description": "Workspace-scoped audit log — query fonderie_events for a human-readable activity trail.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"fonderiejs",
|
|
@@ -34,7 +34,7 @@
|
|
|
34
34
|
"check": "biome check --write src"
|
|
35
35
|
},
|
|
36
36
|
"peerDependencies": {
|
|
37
|
-
"@fonderie/core": "^0.
|
|
37
|
+
"@fonderie/core": "^0.7.0",
|
|
38
38
|
"@fonderie/store": "^0.2.0"
|
|
39
39
|
},
|
|
40
40
|
"devDependencies": {
|