@krizaka/audit 0.1.4 → 0.1.5

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/testkit.js CHANGED
@@ -1,7 +1,7 @@
1
1
  // src/testkit.ts
2
2
  import { expect, it } from "vitest";
3
3
  function auditStoreContract(name, make, owner) {
4
- const tag = () => `t${Math.random().toString(36).slice(2, 10)}`;
4
+ const tag = () => `t${crypto.randomUUID().replaceAll("-", "").slice(0, 8)}`;
5
5
  const entry = (prefix, over = {}) => ({
6
6
  at: /* @__PURE__ */ new Date("2026-10-23T10:00:00Z"),
7
7
  actorId: null,
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/testkit.ts"],"sourcesContent":["import { expect, it } from \"vitest\";\n\nimport type { AuditInsert, AuditStore } from \"./port\";\n\n/**\n * What every AuditStore adapter must do: the same behaviour behind the same port. `owner(n)` gives an actor id the adapter\n * accepts (an account of the product). Each case writes under its own action prefix, so the cases share one database.\n * @param name the adapter's name, shown in the test titles\n * @param make builds the store\n * @param owner the n-th actor id\n */\nexport function auditStoreContract(name: string, make: () => AuditStore | Promise<AuditStore>, owner: (n: number) => string | Promise<string>) {\n const tag = () => `t${Math.random().toString(36).slice(2, 10)}`;\n const entry = (prefix: string, over: Partial<AuditInsert> = {}): AuditInsert => ({\n at: new Date(\"2026-10-23T10:00:00Z\"),\n actorId: null,\n actorLabel: \"system\",\n action: `${prefix}.done`,\n targetType: null,\n targetId: null,\n detail: {},\n ip: null,\n ...over,\n });\n\n it(`${name}: keeps every field and lists the newest first`, async () => {\n const store = await make();\n const p = tag();\n const actor = await owner(1);\n await store.insert(entry(p, { at: new Date(\"2026-10-23T09:00:00Z\"), action: `${p}.first` }));\n await store.insert(entry(p, { actorId: actor, actorLabel: \"Eric\", action: `${p}.second`, targetType: \"shop\", targetId: \"s1\", detail: { n: 2, tags: [\"a\"] }, ip: \"203.0.113.9\" }));\n const listed = await store.list({ actionPrefix: `${p}.` });\n expect(listed.map((e) => e.action)).toEqual([`${p}.second`, `${p}.first`]);\n expect(listed[0]).toMatchObject({ actorId: actor, actorLabel: \"Eric\", targetType: \"shop\", targetId: \"s1\", detail: { n: 2, tags: [\"a\"] }, ip: \"203.0.113.9\" });\n expect(listed[0]!.at.toISOString()).toBe(\"2026-10-23T10:00:00.000Z\");\n expect(typeof listed[0]!.id).toBe(\"string\");\n });\n\n it(`${name}: filters by actor, target and time window`, async () => {\n const store = await make();\n const p = tag();\n const [a, b] = [await owner(2), await owner(3)];\n await store.insert(entry(p, { actorId: a, targetType: \"shop\", targetId: \"x\", at: new Date(\"2026-10-01T00:00:00Z\") }));\n await store.insert(entry(p, { actorId: b, targetType: \"shop\", targetId: \"y\", at: new Date(\"2026-10-10T00:00:00Z\") }));\n expect(await store.list({ actionPrefix: p, actorId: a })).toHaveLength(1);\n expect((await store.list({ actionPrefix: p, target: { type: \"shop\", id: \"y\" } }))[0]!.actorId).toBe(b);\n expect(await store.list({ actionPrefix: p, from: new Date(\"2026-10-05T00:00:00Z\") })).toHaveLength(1);\n expect(await store.list({ actionPrefix: p, to: new Date(\"2026-10-05T00:00:00Z\") })).toHaveLength(1);\n });\n\n it(`${name}: pages with a limit and a cursor (the last id seen)`, async () => {\n const store = await make();\n const p = tag();\n for (let i = 0; i < 5; i++) await store.insert(entry(p, { action: `${p}.n${i}` }));\n const page1 = await store.list({ actionPrefix: p, limit: 2 });\n const page2 = await store.list({ actionPrefix: p, limit: 2, before: page1.at(-1)!.id });\n expect(page1.map((e) => e.action)).toEqual([`${p}.n4`, `${p}.n3`]);\n expect(page2.map((e) => e.action)).toEqual([`${p}.n2`, `${p}.n1`]);\n });\n\n it(`${name}: exports one actor's entries and redacts them (Loi 25), leaving the others`, async () => {\n const store = await make();\n const p = tag();\n const [gone, stays] = [await owner(4), await owner(5)];\n await store.insert(entry(p, { actorId: gone, actorLabel: \"Gone\", ip: \"198.51.100.1\" }));\n await store.insert(entry(p, { actorId: stays, actorLabel: \"Stays\" }));\n expect((await store.forActor(gone)).filter((e) => e.action.startsWith(p))).toHaveLength(1);\n expect(await store.redactActor(gone)).toBeGreaterThanOrEqual(1);\n const after = await store.list({ actionPrefix: p });\n expect(after.find((e) => e.actorLabel === \"Stays\")!.actorId).toBe(stays);\n expect(after.find((e) => e.actorLabel !== \"Stays\")).toMatchObject({ actorId: null, actorLabel: \"erased\", ip: null });\n expect((await store.forActor(gone)).filter((e) => e.action.startsWith(p))).toEqual([]);\n });\n\n it(`${name}: ids never repeat after a purge, so a cursor never skips an entry`, async () => {\n const store = await make();\n const p = tag();\n await store.insert(entry(p, { action: `${p}.old`, at: new Date(\"2020-01-01T00:00:00Z\") }));\n await store.insert(entry(p, { action: `${p}.kept`, at: new Date(\"2026-10-01T00:00:00Z\") }));\n await store.purgeBefore(new Date(\"2021-01-01T00:00:00Z\"));\n await store.insert(entry(p, { action: `${p}.after`, at: new Date(\"2026-10-02T00:00:00Z\") }));\n const all = await store.list({ actionPrefix: p });\n expect(new Set(all.map((e) => e.id)).size).toBe(all.length);\n const first = await store.list({ actionPrefix: p, limit: 1 });\n const rest = await store.list({ actionPrefix: p, before: first[0]!.id });\n expect([...first, ...rest].map((e) => e.action)).toEqual([`${p}.after`, `${p}.kept`]);\n });\n\n it(`${name}: the trail cannot be changed by mutating what was given or what was read`, async () => {\n const store = await make();\n const p = tag();\n const input = entry(p, { detail: { nested: { n: 1 } } });\n await store.insert(input);\n input.at.setUTCFullYear(1999);\n (input.detail.nested as { n: number }).n = 99;\n const [read] = await store.list({ actionPrefix: p });\n read!.at.setUTCFullYear(1999);\n (read!.detail.nested as { n: number }).n = 42;\n read!.actorLabel = \"forged\";\n const [again] = await store.list({ actionPrefix: p });\n expect(again).toMatchObject({ actorLabel: \"system\", detail: { nested: { n: 1 } } });\n expect(again!.at.toISOString()).toBe(\"2026-10-23T10:00:00.000Z\");\n const [exported] = await store.forActor(\"00000000-0000-4000-8000-000000000000\");\n expect(exported).toBeUndefined();\n });\n\n it(`${name}: purges only what is older than the given time, and counts it`, async () => {\n const store = await make();\n const p = tag();\n await store.insert(entry(p, { action: `${p}.old`, at: new Date(\"2020-01-01T00:00:00Z\") }));\n await store.insert(entry(p, { action: `${p}.new`, at: new Date(\"2026-10-01T00:00:00Z\") }));\n expect(await store.purgeBefore(new Date(\"2021-01-01T00:00:00Z\"))).toBeGreaterThanOrEqual(1);\n expect((await store.list({ actionPrefix: p })).map((e) => e.action)).toEqual([`${p}.new`]);\n });\n}\n"],"mappings":"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","names":[]}
1
+ {"version":3,"sources":["../src/testkit.ts"],"sourcesContent":["import { expect, it } from \"vitest\";\n\nimport type { AuditInsert, AuditStore } from \"./port\";\n\n/**\n * What every AuditStore adapter must do: the same behaviour behind the same port. `owner(n)` gives an actor id the adapter\n * accepts (an account of the product). Each case writes under its own action prefix, so the cases share one database.\n * @param name the adapter's name, shown in the test titles\n * @param make builds the store\n * @param owner the n-th actor id\n */\nexport function auditStoreContract(name: string, make: () => AuditStore | Promise<AuditStore>, owner: (n: number) => string | Promise<string>) {\n const tag = () => `t${crypto.randomUUID().replaceAll(\"-\", \"\").slice(0, 8)}`;\n const entry = (prefix: string, over: Partial<AuditInsert> = {}): AuditInsert => ({\n at: new Date(\"2026-10-23T10:00:00Z\"),\n actorId: null,\n actorLabel: \"system\",\n action: `${prefix}.done`,\n targetType: null,\n targetId: null,\n detail: {},\n ip: null,\n ...over,\n });\n\n it(`${name}: keeps every field and lists the newest first`, async () => {\n const store = await make();\n const p = tag();\n const actor = await owner(1);\n await store.insert(entry(p, { at: new Date(\"2026-10-23T09:00:00Z\"), action: `${p}.first` }));\n await store.insert(entry(p, { actorId: actor, actorLabel: \"Eric\", action: `${p}.second`, targetType: \"shop\", targetId: \"s1\", detail: { n: 2, tags: [\"a\"] }, ip: \"203.0.113.9\" }));\n const listed = await store.list({ actionPrefix: `${p}.` });\n expect(listed.map((e) => e.action)).toEqual([`${p}.second`, `${p}.first`]);\n expect(listed[0]).toMatchObject({ actorId: actor, actorLabel: \"Eric\", targetType: \"shop\", targetId: \"s1\", detail: { n: 2, tags: [\"a\"] }, ip: \"203.0.113.9\" });\n expect(listed[0]!.at.toISOString()).toBe(\"2026-10-23T10:00:00.000Z\");\n expect(typeof listed[0]!.id).toBe(\"string\");\n });\n\n it(`${name}: filters by actor, target and time window`, async () => {\n const store = await make();\n const p = tag();\n const [a, b] = [await owner(2), await owner(3)];\n await store.insert(entry(p, { actorId: a, targetType: \"shop\", targetId: \"x\", at: new Date(\"2026-10-01T00:00:00Z\") }));\n await store.insert(entry(p, { actorId: b, targetType: \"shop\", targetId: \"y\", at: new Date(\"2026-10-10T00:00:00Z\") }));\n expect(await store.list({ actionPrefix: p, actorId: a })).toHaveLength(1);\n expect((await store.list({ actionPrefix: p, target: { type: \"shop\", id: \"y\" } }))[0]!.actorId).toBe(b);\n expect(await store.list({ actionPrefix: p, from: new Date(\"2026-10-05T00:00:00Z\") })).toHaveLength(1);\n expect(await store.list({ actionPrefix: p, to: new Date(\"2026-10-05T00:00:00Z\") })).toHaveLength(1);\n });\n\n it(`${name}: pages with a limit and a cursor (the last id seen)`, async () => {\n const store = await make();\n const p = tag();\n for (let i = 0; i < 5; i++) await store.insert(entry(p, { action: `${p}.n${i}` }));\n const page1 = await store.list({ actionPrefix: p, limit: 2 });\n const page2 = await store.list({ actionPrefix: p, limit: 2, before: page1.at(-1)!.id });\n expect(page1.map((e) => e.action)).toEqual([`${p}.n4`, `${p}.n3`]);\n expect(page2.map((e) => e.action)).toEqual([`${p}.n2`, `${p}.n1`]);\n });\n\n it(`${name}: exports one actor's entries and redacts them (Loi 25), leaving the others`, async () => {\n const store = await make();\n const p = tag();\n const [gone, stays] = [await owner(4), await owner(5)];\n await store.insert(entry(p, { actorId: gone, actorLabel: \"Gone\", ip: \"198.51.100.1\" }));\n await store.insert(entry(p, { actorId: stays, actorLabel: \"Stays\" }));\n expect((await store.forActor(gone)).filter((e) => e.action.startsWith(p))).toHaveLength(1);\n expect(await store.redactActor(gone)).toBeGreaterThanOrEqual(1);\n const after = await store.list({ actionPrefix: p });\n expect(after.find((e) => e.actorLabel === \"Stays\")!.actorId).toBe(stays);\n expect(after.find((e) => e.actorLabel !== \"Stays\")).toMatchObject({ actorId: null, actorLabel: \"erased\", ip: null });\n expect((await store.forActor(gone)).filter((e) => e.action.startsWith(p))).toEqual([]);\n });\n\n it(`${name}: ids never repeat after a purge, so a cursor never skips an entry`, async () => {\n const store = await make();\n const p = tag();\n await store.insert(entry(p, { action: `${p}.old`, at: new Date(\"2020-01-01T00:00:00Z\") }));\n await store.insert(entry(p, { action: `${p}.kept`, at: new Date(\"2026-10-01T00:00:00Z\") }));\n await store.purgeBefore(new Date(\"2021-01-01T00:00:00Z\"));\n await store.insert(entry(p, { action: `${p}.after`, at: new Date(\"2026-10-02T00:00:00Z\") }));\n const all = await store.list({ actionPrefix: p });\n expect(new Set(all.map((e) => e.id)).size).toBe(all.length);\n const first = await store.list({ actionPrefix: p, limit: 1 });\n const rest = await store.list({ actionPrefix: p, before: first[0]!.id });\n expect([...first, ...rest].map((e) => e.action)).toEqual([`${p}.after`, `${p}.kept`]);\n });\n\n it(`${name}: the trail cannot be changed by mutating what was given or what was read`, async () => {\n const store = await make();\n const p = tag();\n const input = entry(p, { detail: { nested: { n: 1 } } });\n await store.insert(input);\n input.at.setUTCFullYear(1999);\n (input.detail.nested as { n: number }).n = 99;\n const [read] = await store.list({ actionPrefix: p });\n read!.at.setUTCFullYear(1999);\n (read!.detail.nested as { n: number }).n = 42;\n read!.actorLabel = \"forged\";\n const [again] = await store.list({ actionPrefix: p });\n expect(again).toMatchObject({ actorLabel: \"system\", detail: { nested: { n: 1 } } });\n expect(again!.at.toISOString()).toBe(\"2026-10-23T10:00:00.000Z\");\n const [exported] = await store.forActor(\"00000000-0000-4000-8000-000000000000\");\n expect(exported).toBeUndefined();\n });\n\n it(`${name}: purges only what is older than the given time, and counts it`, async () => {\n const store = await make();\n const p = tag();\n await store.insert(entry(p, { action: `${p}.old`, at: new Date(\"2020-01-01T00:00:00Z\") }));\n await store.insert(entry(p, { action: `${p}.new`, at: new Date(\"2026-10-01T00:00:00Z\") }));\n expect(await store.purgeBefore(new Date(\"2021-01-01T00:00:00Z\"))).toBeGreaterThanOrEqual(1);\n expect((await store.list({ actionPrefix: p })).map((e) => e.action)).toEqual([`${p}.new`]);\n });\n}\n"],"mappings":";AAAA,SAAS,QAAQ,UAAU;AAWpB,SAAS,mBAAmB,MAAc,MAA8C,OAAgD;AAC7I,QAAM,MAAM,MAAM,IAAI,OAAO,WAAW,EAAE,WAAW,KAAK,EAAE,EAAE,MAAM,GAAG,CAAC,CAAC;AACzE,QAAM,QAAQ,CAAC,QAAgB,OAA6B,CAAC,OAAoB;AAAA,IAC/E,IAAI,oBAAI,KAAK,sBAAsB;AAAA,IACnC,SAAS;AAAA,IACT,YAAY;AAAA,IACZ,QAAQ,GAAG,MAAM;AAAA,IACjB,YAAY;AAAA,IACZ,UAAU;AAAA,IACV,QAAQ,CAAC;AAAA,IACT,IAAI;AAAA,IACJ,GAAG;AAAA,EACL;AAEA,KAAG,GAAG,IAAI,kDAAkD,YAAY;AACtE,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,QAAQ,MAAM,MAAM,CAAC;AAC3B,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,IAAI,oBAAI,KAAK,sBAAsB,GAAG,QAAQ,GAAG,CAAC,SAAS,CAAC,CAAC;AAC3F,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,SAAS,OAAO,YAAY,QAAQ,QAAQ,GAAG,CAAC,WAAW,YAAY,QAAQ,UAAU,MAAM,QAAQ,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,cAAc,CAAC,CAAC;AAChL,UAAM,SAAS,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,CAAC,IAAI,CAAC;AACzD,WAAO,OAAO,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,WAAW,GAAG,CAAC,QAAQ,CAAC;AACzE,WAAO,OAAO,CAAC,CAAC,EAAE,cAAc,EAAE,SAAS,OAAO,YAAY,QAAQ,YAAY,QAAQ,UAAU,MAAM,QAAQ,EAAE,GAAG,GAAG,MAAM,CAAC,GAAG,EAAE,GAAG,IAAI,cAAc,CAAC;AAC5J,WAAO,OAAO,CAAC,EAAG,GAAG,YAAY,CAAC,EAAE,KAAK,0BAA0B;AACnE,WAAO,OAAO,OAAO,CAAC,EAAG,EAAE,EAAE,KAAK,QAAQ;AAAA,EAC5C,CAAC;AAED,KAAG,GAAG,IAAI,8CAA8C,YAAY;AAClE,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,CAAC,GAAG,CAAC,IAAI,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC;AAC9C,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,SAAS,GAAG,YAAY,QAAQ,UAAU,KAAK,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AACpH,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,SAAS,GAAG,YAAY,QAAQ,UAAU,KAAK,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AACpH,WAAO,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,SAAS,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC;AACxE,YAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,QAAQ,EAAE,MAAM,QAAQ,IAAI,IAAI,EAAE,CAAC,GAAG,CAAC,EAAG,OAAO,EAAE,KAAK,CAAC;AACrG,WAAO,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,MAAM,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC;AACpG,WAAO,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC,EAAE,aAAa,CAAC;AAAA,EACpG,CAAC;AAED,KAAG,GAAG,IAAI,wDAAwD,YAAY;AAC5E,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,aAAS,IAAI,GAAG,IAAI,GAAG,IAAK,OAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;AACjF,UAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,OAAO,EAAE,CAAC;AAC5D,UAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,OAAO,GAAG,QAAQ,MAAM,GAAG,EAAE,EAAG,GAAG,CAAC;AACtF,WAAO,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC;AACjE,WAAO,MAAM,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,OAAO,GAAG,CAAC,KAAK,CAAC;AAAA,EACnE,CAAC;AAED,KAAG,GAAG,IAAI,+EAA+E,YAAY;AACnG,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,MAAM,CAAC,CAAC;AACrD,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,SAAS,MAAM,YAAY,QAAQ,IAAI,eAAe,CAAC,CAAC;AACtF,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,SAAS,OAAO,YAAY,QAAQ,CAAC,CAAC;AACpE,YAAQ,MAAM,MAAM,SAAS,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,WAAW,CAAC,CAAC,CAAC,EAAE,aAAa,CAAC;AACzF,WAAO,MAAM,MAAM,YAAY,IAAI,CAAC,EAAE,uBAAuB,CAAC;AAC9D,UAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,EAAE,CAAC;AAClD,WAAO,MAAM,KAAK,CAAC,MAAM,EAAE,eAAe,OAAO,EAAG,OAAO,EAAE,KAAK,KAAK;AACvE,WAAO,MAAM,KAAK,CAAC,MAAM,EAAE,eAAe,OAAO,CAAC,EAAE,cAAc,EAAE,SAAS,MAAM,YAAY,UAAU,IAAI,KAAK,CAAC;AACnH,YAAQ,MAAM,MAAM,SAAS,IAAI,GAAG,OAAO,CAAC,MAAM,EAAE,OAAO,WAAW,CAAC,CAAC,CAAC,EAAE,QAAQ,CAAC,CAAC;AAAA,EACvF,CAAC;AAED,KAAG,GAAG,IAAI,sEAAsE,YAAY;AAC1F,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,QAAQ,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AACzF,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,SAAS,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AAC1F,UAAM,MAAM,YAAY,oBAAI,KAAK,sBAAsB,CAAC;AACxD,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,UAAU,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AAC3F,UAAM,MAAM,MAAM,MAAM,KAAK,EAAE,cAAc,EAAE,CAAC;AAChD,WAAO,IAAI,IAAI,IAAI,IAAI,CAAC,MAAM,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,IAAI,MAAM;AAC1D,UAAM,QAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,OAAO,EAAE,CAAC;AAC5D,UAAM,OAAO,MAAM,MAAM,KAAK,EAAE,cAAc,GAAG,QAAQ,MAAM,CAAC,EAAG,GAAG,CAAC;AACvE,WAAO,CAAC,GAAG,OAAO,GAAG,IAAI,EAAE,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,UAAU,GAAG,CAAC,OAAO,CAAC;AAAA,EACtF,CAAC;AAED,KAAG,GAAG,IAAI,6EAA6E,YAAY;AACjG,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,QAAQ,MAAM,GAAG,EAAE,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AACvD,UAAM,MAAM,OAAO,KAAK;AACxB,UAAM,GAAG,eAAe,IAAI;AAC5B,IAAC,MAAM,OAAO,OAAyB,IAAI;AAC3C,UAAM,CAAC,IAAI,IAAI,MAAM,MAAM,KAAK,EAAE,cAAc,EAAE,CAAC;AACnD,SAAM,GAAG,eAAe,IAAI;AAC5B,IAAC,KAAM,OAAO,OAAyB,IAAI;AAC3C,SAAM,aAAa;AACnB,UAAM,CAAC,KAAK,IAAI,MAAM,MAAM,KAAK,EAAE,cAAc,EAAE,CAAC;AACpD,WAAO,KAAK,EAAE,cAAc,EAAE,YAAY,UAAU,QAAQ,EAAE,QAAQ,EAAE,GAAG,EAAE,EAAE,EAAE,CAAC;AAClF,WAAO,MAAO,GAAG,YAAY,CAAC,EAAE,KAAK,0BAA0B;AAC/D,UAAM,CAAC,QAAQ,IAAI,MAAM,MAAM,SAAS,sCAAsC;AAC9E,WAAO,QAAQ,EAAE,cAAc;AAAA,EACjC,CAAC;AAED,KAAG,GAAG,IAAI,kEAAkE,YAAY;AACtF,UAAM,QAAQ,MAAM,KAAK;AACzB,UAAM,IAAI,IAAI;AACd,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,QAAQ,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AACzF,UAAM,MAAM,OAAO,MAAM,GAAG,EAAE,QAAQ,GAAG,CAAC,QAAQ,IAAI,oBAAI,KAAK,sBAAsB,EAAE,CAAC,CAAC;AACzF,WAAO,MAAM,MAAM,YAAY,oBAAI,KAAK,sBAAsB,CAAC,CAAC,EAAE,uBAAuB,CAAC;AAC1F,YAAQ,MAAM,MAAM,KAAK,EAAE,cAAc,EAAE,CAAC,GAAG,IAAI,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,QAAQ,CAAC,GAAG,CAAC,MAAM,CAAC;AAAA,EAC3F,CAAC;AACH;","names":[]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@krizaka/audit",
3
- "version": "0.1.4",
3
+ "version": "0.1.5",
4
4
  "description": "Who did what, for every Krizaka product: an append-only audit trail in its own schema, written inside the caller's transaction or quietly beside it, filtered for the admin, exported and redacted for Loi 25, purged after a retention set by the admin. No database client, no wall clock.",
5
5
  "keywords": [
6
6
  "krizaka",
@@ -51,12 +51,12 @@
51
51
  "typescript": "^5.9.3",
52
52
  "vitest": "^4.1.11",
53
53
  "zod": "^4.6.7",
54
- "@krizaka/config": "^0.8.1"
54
+ "@krizaka/config": "^0.14.1"
55
55
  },
56
56
  "dependencies": {
57
57
  "date-fns": "^4.4.0",
58
- "@krizaka/db": "^0.2.0",
59
- "@krizaka/settings": "^0.5.0"
58
+ "@krizaka/settings": "^0.5.1",
59
+ "@krizaka/db": "^0.3.0"
60
60
  },
61
61
  "peerDependencies": {
62
62
  "vitest": ">=4",
@@ -71,7 +71,7 @@
71
71
  "build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsup",
72
72
  "lint": "eslint . --max-warnings 0 && tsc --noEmit",
73
73
  "typecheck": "tsc --noEmit",
74
- "test": "vitest run",
74
+ "test": "vitest run --coverage --coverage.provider=v8 --coverage.reporter=lcov --coverage.reporter=text-summary \"--coverage.include=src/**/*.ts\"",
75
75
  "check": "pnpm run lint && pnpm run test && pnpm run build"
76
76
  }
77
77
  }