@wtfalch/audit 0.1.0 → 0.2.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/README.md +19 -0
- package/dist/ledger.d.ts +16 -5
- package/dist/ledger.js +13 -4
- package/dist/schema.d.ts +4 -2
- package/dist/schema.js +3 -2
- package/dist/tables.d.ts +159 -0
- package/dist/tables.js +31 -12
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -70,6 +70,25 @@ const touched = await ledger.erase(tx, { subject: personId, pseudonym, email });
|
|
|
70
70
|
uses: `tenant.invoice_paid` is out, `invoice.paid` is in. The core's
|
|
71
71
|
namespaces are the trusted base's.
|
|
72
72
|
|
|
73
|
+
## A host's own columns
|
|
74
|
+
|
|
75
|
+
A host that needs a column beside the ledger's (a team, a region) declares
|
|
76
|
+
its table over the package's builders and hands it to the ledger:
|
|
77
|
+
|
|
78
|
+
```ts
|
|
79
|
+
import { AUDIT_COLUMNS, auditIndexes, createLedger } from '@wtfalch/audit';
|
|
80
|
+
|
|
81
|
+
export const auditEvents = pgTable('audit_events', { ...AUDIT_COLUMNS, teamId: uuid('team_id') }, (t) =>
|
|
82
|
+
auditIndexes(t),
|
|
83
|
+
);
|
|
84
|
+
const ledger = createLedger({ vocabulary, table: auditEvents, schemaVersion: core.version });
|
|
85
|
+
await ledger.sign(tx, { ...input, extra: { teamId } });
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
`extra` takes host columns only: a ledger column there is refused, and so is a
|
|
89
|
+
key the table does not declare. The host's own migration adds the column; the
|
|
90
|
+
package's migration never learns of it.
|
|
91
|
+
|
|
73
92
|
## Tests
|
|
74
93
|
|
|
75
94
|
```sh
|
package/dist/ledger.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import type { PgDatabase, PgQueryResultHKT } from 'drizzle-orm/pg-core';
|
|
2
|
-
import { type AuditEventRow,
|
|
2
|
+
import { type AuditEventRow, type AuditTable } from './tables.js';
|
|
3
3
|
import type { LedgerVocabulary } from './vocabulary.js';
|
|
4
4
|
/**
|
|
5
5
|
* The host's drizzle handle or a transaction open on it. postgres-js in the
|
|
@@ -42,6 +42,12 @@ export interface SignInput {
|
|
|
42
42
|
readonly id: string;
|
|
43
43
|
} | null;
|
|
44
44
|
readonly occurredAt?: Date;
|
|
45
|
+
/**
|
|
46
|
+
* Values for the host's own columns, by the drizzle key the host's table
|
|
47
|
+
* declares them under (`{ teamId }`), written beside the ledger's. Never a
|
|
48
|
+
* ledger column: those are validated above and refused here.
|
|
49
|
+
*/
|
|
50
|
+
readonly extra?: Readonly<Record<string, unknown>>;
|
|
45
51
|
}
|
|
46
52
|
export interface PageOptions {
|
|
47
53
|
/** One tenant's rows; `null` for rows with no tenant; omit for every tenant. */
|
|
@@ -79,10 +85,17 @@ export interface EraseInput {
|
|
|
79
85
|
* or reads a session: the guarantee that a row's actor is real is the host's,
|
|
80
86
|
* and it holds only while the signer stays private.
|
|
81
87
|
*/
|
|
88
|
+
export interface LedgerOptions {
|
|
89
|
+
readonly vocabulary: LedgerVocabulary;
|
|
90
|
+
/** The host's own table over `AUDIT_COLUMNS`, when it has columns beside the ledger's. Defaults to the package's `auditEvents`. */
|
|
91
|
+
readonly table?: AuditTable;
|
|
92
|
+
/** What `schema_version` every row carries. Defaults to 1; a host whose shared audit words are versioned passes theirs. */
|
|
93
|
+
readonly schemaVersion?: number;
|
|
94
|
+
}
|
|
82
95
|
export interface Ledger {
|
|
83
96
|
readonly vocabulary: LedgerVocabulary;
|
|
84
97
|
readonly tables: {
|
|
85
|
-
readonly events:
|
|
98
|
+
readonly events: AuditTable;
|
|
86
99
|
};
|
|
87
100
|
/** Validates against the vocabulary and inserts, on the handle given: a transaction when the row must commit with the change it records. */
|
|
88
101
|
sign(handle: Handle, input: SignInput): Promise<void>;
|
|
@@ -93,6 +106,4 @@ export interface Ledger {
|
|
|
93
106
|
/** One tenant's rows, oldest first, for a tenant's export. Deterministic order; no secrets are in this table to omit. */
|
|
94
107
|
exportRows(handle: Handle, tenantId: string): Promise<readonly AuditEventRow[]>;
|
|
95
108
|
}
|
|
96
|
-
export declare function createLedger(options:
|
|
97
|
-
vocabulary: LedgerVocabulary;
|
|
98
|
-
}): Ledger;
|
|
109
|
+
export declare function createLedger(options: LedgerOptions): Ledger;
|
package/dist/ledger.js
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { and, asc, desc, eq, lt, or, sql } from 'drizzle-orm';
|
|
2
2
|
import { rowSchema } from './schema.js';
|
|
3
|
-
import { auditEvents } from './tables.js';
|
|
3
|
+
import { auditEvents as auditEvents_ } from './tables.js';
|
|
4
|
+
const LEDGER_KEYS = new Set(Object.keys(auditEvents_));
|
|
4
5
|
export function createLedger(options) {
|
|
5
|
-
const { vocabulary } = options;
|
|
6
|
-
const schema = rowSchema(vocabulary);
|
|
6
|
+
const { vocabulary, table: auditEvents = auditEvents_, schemaVersion = 1 } = options;
|
|
7
|
+
const schema = rowSchema(vocabulary, { schemaVersion });
|
|
7
8
|
async function sign(handle, input) {
|
|
8
9
|
const meta = vocabulary.events[input.action];
|
|
9
10
|
if (!meta) {
|
|
@@ -31,11 +32,19 @@ export function createLedger(options) {
|
|
|
31
32
|
before: input.before ?? null,
|
|
32
33
|
after: input.after ?? null,
|
|
33
34
|
erased_at: null,
|
|
34
|
-
schema_version:
|
|
35
|
+
schema_version: schemaVersion,
|
|
35
36
|
subject_class: input.subject?.class ?? null,
|
|
36
37
|
subject_id: input.subject?.id ?? null,
|
|
37
38
|
});
|
|
39
|
+
const extra = input.extra ?? {};
|
|
40
|
+
for (const key of Object.keys(extra)) {
|
|
41
|
+
if (LEDGER_KEYS.has(key))
|
|
42
|
+
throw new Error(`audit: "${key}" is a ledger column, not a host column`);
|
|
43
|
+
if (!(key in auditEvents))
|
|
44
|
+
throw new Error(`audit: the ledger's table has no column "${key}"`);
|
|
45
|
+
}
|
|
38
46
|
await handle.insert(auditEvents).values({
|
|
47
|
+
...extra,
|
|
39
48
|
occurredAt,
|
|
40
49
|
tenantId: row.tenant_id,
|
|
41
50
|
actorClass: row.actor_class,
|
package/dist/schema.d.ts
CHANGED
|
@@ -20,7 +20,9 @@ export declare function isJsonValue(value: unknown, seen?: WeakSet<object>): boo
|
|
|
20
20
|
* the break-glass context must carry the session, a reason from the closed
|
|
21
21
|
* code set and a reference; every other row's reason is free text.
|
|
22
22
|
*/
|
|
23
|
-
export declare function rowSchema(vocabulary: LedgerVocabulary
|
|
23
|
+
export declare function rowSchema(vocabulary: LedgerVocabulary, options?: {
|
|
24
|
+
schemaVersion?: number;
|
|
25
|
+
}): z.ZodObject<{
|
|
24
26
|
occurred_at: z.ZodISODateTime;
|
|
25
27
|
tenant_id: z.ZodNullable<z.ZodString>;
|
|
26
28
|
actor_class: z.ZodEnum<{
|
|
@@ -49,7 +51,7 @@ export declare function rowSchema(vocabulary: LedgerVocabulary): z.ZodObject<{
|
|
|
49
51
|
before: z.ZodNullable<z.ZodUnknown>;
|
|
50
52
|
after: z.ZodNullable<z.ZodUnknown>;
|
|
51
53
|
erased_at: z.ZodNullable<z.ZodISODateTime>;
|
|
52
|
-
schema_version: z.ZodLiteral<
|
|
54
|
+
schema_version: z.ZodLiteral<number>;
|
|
53
55
|
subject_class: z.ZodNullable<z.ZodEnum<{
|
|
54
56
|
[x: string]: string;
|
|
55
57
|
}>>;
|
package/dist/schema.js
CHANGED
|
@@ -60,7 +60,8 @@ const enumOf = (values) => z.enum([...values]);
|
|
|
60
60
|
* the break-glass context must carry the session, a reason from the closed
|
|
61
61
|
* code set and a reference; every other row's reason is free text.
|
|
62
62
|
*/
|
|
63
|
-
export function rowSchema(vocabulary) {
|
|
63
|
+
export function rowSchema(vocabulary, options = {}) {
|
|
64
|
+
const schemaVersion = options.schemaVersion ?? 1;
|
|
64
65
|
const eventNames = Object.keys(vocabulary.events);
|
|
65
66
|
const reasonCodes = new Set(vocabulary.breakGlassReasonCodes ?? []);
|
|
66
67
|
return z
|
|
@@ -85,7 +86,7 @@ export function rowSchema(vocabulary) {
|
|
|
85
86
|
before: jsonColumn.nullable(),
|
|
86
87
|
after: jsonColumn.nullable(),
|
|
87
88
|
erased_at: z.iso.datetime({ offset: true }).nullable(),
|
|
88
|
-
schema_version: z.literal(
|
|
89
|
+
schema_version: z.literal(schemaVersion),
|
|
89
90
|
subject_class: enumOf(vocabulary.actorClasses).nullable(),
|
|
90
91
|
subject_id: identifier(AUDIT_LIMITS.id).nullable(),
|
|
91
92
|
})
|
package/dist/tables.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type ExtraConfigColumn } from 'drizzle-orm/pg-core';
|
|
1
2
|
/**
|
|
2
3
|
* The ledger, mirrored from `migrations/0001_audit.sql`, which is the source
|
|
3
4
|
* of truth: the SQL carries the CHECKs, the append-only triggers, the partial
|
|
@@ -5,6 +6,162 @@
|
|
|
5
6
|
* re-exports this from its own schema module so its drizzle instance and its
|
|
6
7
|
* types know it.
|
|
7
8
|
*/
|
|
9
|
+
/**
|
|
10
|
+
* The ledger's columns, as drizzle builders, so a host that needs a column
|
|
11
|
+
* of its own beside them (a team, a region) declares
|
|
12
|
+
* `pgTable('audit_events', { ...AUDIT_COLUMNS, teamId: uuid('team_id') }, …)`
|
|
13
|
+
* and hands that table to `createLedger`. The package's own `auditEvents`
|
|
14
|
+
* is the same call with nothing added.
|
|
15
|
+
*/
|
|
16
|
+
export declare const AUDIT_COLUMNS: {
|
|
17
|
+
id: import("drizzle-orm").IsPrimaryKey<import("drizzle-orm").NotNull<import("drizzle-orm").IsIdentity<import("drizzle-orm/pg-core").PgBigInt53BuilderInitial<"id">, "always">>>;
|
|
18
|
+
occurredAt: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTimestampBuilderInitial<"occurred_at">>>;
|
|
19
|
+
tenantId: import("drizzle-orm/pg-core").PgUUIDBuilderInitial<"tenant_id">;
|
|
20
|
+
actorClass: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
21
|
+
name: "actor_class";
|
|
22
|
+
dataType: "string";
|
|
23
|
+
columnType: "PgText";
|
|
24
|
+
data: string;
|
|
25
|
+
enumValues: [string, ...string[]];
|
|
26
|
+
driverParam: string;
|
|
27
|
+
}>>;
|
|
28
|
+
actorId: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
29
|
+
name: "actor_id";
|
|
30
|
+
dataType: "string";
|
|
31
|
+
columnType: "PgText";
|
|
32
|
+
data: string;
|
|
33
|
+
enumValues: [string, ...string[]];
|
|
34
|
+
driverParam: string;
|
|
35
|
+
}>>;
|
|
36
|
+
actorDisplay: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
37
|
+
name: "actor_display";
|
|
38
|
+
dataType: "string";
|
|
39
|
+
columnType: "PgText";
|
|
40
|
+
data: string;
|
|
41
|
+
enumValues: [string, ...string[]];
|
|
42
|
+
driverParam: string;
|
|
43
|
+
}>>;
|
|
44
|
+
action: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
45
|
+
name: "action";
|
|
46
|
+
dataType: "string";
|
|
47
|
+
columnType: "PgText";
|
|
48
|
+
data: string;
|
|
49
|
+
enumValues: [string, ...string[]];
|
|
50
|
+
driverParam: string;
|
|
51
|
+
}>>;
|
|
52
|
+
targetType: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
53
|
+
name: "target_type";
|
|
54
|
+
dataType: "string";
|
|
55
|
+
columnType: "PgText";
|
|
56
|
+
data: string;
|
|
57
|
+
enumValues: [string, ...string[]];
|
|
58
|
+
driverParam: string;
|
|
59
|
+
}>>;
|
|
60
|
+
targetId: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
61
|
+
name: "target_id";
|
|
62
|
+
dataType: "string";
|
|
63
|
+
columnType: "PgText";
|
|
64
|
+
data: string;
|
|
65
|
+
enumValues: [string, ...string[]];
|
|
66
|
+
driverParam: string;
|
|
67
|
+
}>>;
|
|
68
|
+
outcome: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
69
|
+
name: "outcome";
|
|
70
|
+
dataType: "string";
|
|
71
|
+
columnType: "PgText";
|
|
72
|
+
data: string;
|
|
73
|
+
enumValues: [string, ...string[]];
|
|
74
|
+
driverParam: string;
|
|
75
|
+
}>>;
|
|
76
|
+
context: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
77
|
+
name: "context";
|
|
78
|
+
dataType: "string";
|
|
79
|
+
columnType: "PgText";
|
|
80
|
+
data: string;
|
|
81
|
+
enumValues: [string, ...string[]];
|
|
82
|
+
driverParam: string;
|
|
83
|
+
}>>;
|
|
84
|
+
sessionId: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
85
|
+
name: "session_id";
|
|
86
|
+
dataType: "string";
|
|
87
|
+
columnType: "PgText";
|
|
88
|
+
data: string;
|
|
89
|
+
enumValues: [string, ...string[]];
|
|
90
|
+
driverParam: string;
|
|
91
|
+
}>;
|
|
92
|
+
reason: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
93
|
+
name: "reason";
|
|
94
|
+
dataType: "string";
|
|
95
|
+
columnType: "PgText";
|
|
96
|
+
data: string;
|
|
97
|
+
enumValues: [string, ...string[]];
|
|
98
|
+
driverParam: string;
|
|
99
|
+
}>;
|
|
100
|
+
reference: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
101
|
+
name: "reference";
|
|
102
|
+
dataType: "string";
|
|
103
|
+
columnType: "PgText";
|
|
104
|
+
data: string;
|
|
105
|
+
enumValues: [string, ...string[]];
|
|
106
|
+
driverParam: string;
|
|
107
|
+
}>;
|
|
108
|
+
requestId: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
109
|
+
name: "request_id";
|
|
110
|
+
dataType: "string";
|
|
111
|
+
columnType: "PgText";
|
|
112
|
+
data: string;
|
|
113
|
+
enumValues: [string, ...string[]];
|
|
114
|
+
driverParam: string;
|
|
115
|
+
}>;
|
|
116
|
+
ip: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
117
|
+
name: "ip";
|
|
118
|
+
dataType: "string";
|
|
119
|
+
columnType: "PgText";
|
|
120
|
+
data: string;
|
|
121
|
+
enumValues: [string, ...string[]];
|
|
122
|
+
driverParam: string;
|
|
123
|
+
}>;
|
|
124
|
+
userAgent: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
125
|
+
name: "user_agent";
|
|
126
|
+
dataType: "string";
|
|
127
|
+
columnType: "PgText";
|
|
128
|
+
data: string;
|
|
129
|
+
enumValues: [string, ...string[]];
|
|
130
|
+
driverParam: string;
|
|
131
|
+
}>;
|
|
132
|
+
tenantVisible: import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgBooleanBuilderInitial<"tenant_visible">>;
|
|
133
|
+
before: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"before">;
|
|
134
|
+
after: import("drizzle-orm/pg-core").PgJsonbBuilderInitial<"after">;
|
|
135
|
+
erasedAt: import("drizzle-orm/pg-core").PgTimestampBuilderInitial<"erased_at">;
|
|
136
|
+
schemaVersion: import("drizzle-orm").HasDefault<import("drizzle-orm").NotNull<import("drizzle-orm/pg-core").PgSmallIntBuilderInitial<"schema_version">>>;
|
|
137
|
+
subjectClass: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
138
|
+
name: "subject_class";
|
|
139
|
+
dataType: "string";
|
|
140
|
+
columnType: "PgText";
|
|
141
|
+
data: string;
|
|
142
|
+
enumValues: [string, ...string[]];
|
|
143
|
+
driverParam: string;
|
|
144
|
+
}>;
|
|
145
|
+
subjectId: import("drizzle-orm/pg-core").PgTextBuilder<{
|
|
146
|
+
name: "subject_id";
|
|
147
|
+
dataType: "string";
|
|
148
|
+
columnType: "PgText";
|
|
149
|
+
data: string;
|
|
150
|
+
enumValues: [string, ...string[]];
|
|
151
|
+
driverParam: string;
|
|
152
|
+
}>;
|
|
153
|
+
};
|
|
154
|
+
/** The indexes every ledger table carries, for a host declaring its own table over `AUDIT_COLUMNS`. */
|
|
155
|
+
export declare function auditIndexes(t: {
|
|
156
|
+
[K in keyof typeof AUDIT_COLUMNS]: ExtraConfigColumn;
|
|
157
|
+
}): import("drizzle-orm/pg-core").IndexBuilder[];
|
|
158
|
+
/**
|
|
159
|
+
* The ledger, mirrored from `migrations/0001_audit.sql`, which is the source
|
|
160
|
+
* of truth: the SQL carries the CHECKs, the append-only triggers, the partial
|
|
161
|
+
* indexes and the erasure function that drizzle-kit does not generate. A host
|
|
162
|
+
* re-exports this from its own schema module so its drizzle instance and its
|
|
163
|
+
* types know it, or declares a wider table over `AUDIT_COLUMNS`.
|
|
164
|
+
*/
|
|
8
165
|
export declare const auditEvents: import("drizzle-orm/pg-core").PgTableWithColumns<{
|
|
9
166
|
name: "audit_events";
|
|
10
167
|
schema: undefined;
|
|
@@ -420,6 +577,8 @@ export declare const auditEvents: import("drizzle-orm/pg-core").PgTableWithColum
|
|
|
420
577
|
};
|
|
421
578
|
dialect: "pg";
|
|
422
579
|
}>;
|
|
580
|
+
/** The package's table, or a host's declared over `AUDIT_COLUMNS` with more columns; structurally either. */
|
|
581
|
+
export type AuditTable = typeof auditEvents;
|
|
423
582
|
export type AuditEventRow = typeof auditEvents.$inferSelect;
|
|
424
583
|
export type AuditEventInsert = typeof auditEvents.$inferInsert;
|
|
425
584
|
export declare const tables: {
|
package/dist/tables.js
CHANGED
|
@@ -7,7 +7,14 @@ import { bigint, boolean, index, jsonb, pgTable, smallint, text, timestamp, uuid
|
|
|
7
7
|
* re-exports this from its own schema module so its drizzle instance and its
|
|
8
8
|
* types know it.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The ledger's columns, as drizzle builders, so a host that needs a column
|
|
12
|
+
* of its own beside them (a team, a region) declares
|
|
13
|
+
* `pgTable('audit_events', { ...AUDIT_COLUMNS, teamId: uuid('team_id') }, …)`
|
|
14
|
+
* and hands that table to `createLedger`. The package's own `auditEvents`
|
|
15
|
+
* is the same call with nothing added.
|
|
16
|
+
*/
|
|
17
|
+
export const AUDIT_COLUMNS = {
|
|
11
18
|
id: bigint('id', { mode: 'number' }).generatedAlwaysAsIdentity().primaryKey(),
|
|
12
19
|
occurredAt: timestamp('occurred_at', { withTimezone: true }).notNull().default(sql `now()`),
|
|
13
20
|
tenantId: uuid('tenant_id'),
|
|
@@ -32,15 +39,27 @@ export const auditEvents = pgTable('audit_events', {
|
|
|
32
39
|
schemaVersion: smallint('schema_version').notNull().default(1),
|
|
33
40
|
subjectClass: text('subject_class'),
|
|
34
41
|
subjectId: text('subject_id'),
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
.on(t.
|
|
41
|
-
.
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
42
|
+
};
|
|
43
|
+
/** The indexes every ledger table carries, for a host declaring its own table over `AUDIT_COLUMNS`. */
|
|
44
|
+
export function auditIndexes(t) {
|
|
45
|
+
return [
|
|
46
|
+
index('audit_events_tenant_time_idx').on(t.tenantId, t.occurredAt.desc(), t.id.desc()),
|
|
47
|
+
index('audit_events_actor_time_idx').on(t.actorId, t.occurredAt.desc()),
|
|
48
|
+
index('audit_events_action_time_idx').on(t.action, t.occurredAt.desc()),
|
|
49
|
+
index('audit_events_subject_time_idx')
|
|
50
|
+
.on(t.subjectClass, t.subjectId, t.occurredAt.desc(), t.id.desc())
|
|
51
|
+
.where(sql `${t.subjectId} is not null`),
|
|
52
|
+
index('audit_events_request_idx')
|
|
53
|
+
.on(t.requestId, t.occurredAt.desc())
|
|
54
|
+
.where(sql `${t.requestId} is not null`),
|
|
55
|
+
];
|
|
56
|
+
}
|
|
57
|
+
/**
|
|
58
|
+
* The ledger, mirrored from `migrations/0001_audit.sql`, which is the source
|
|
59
|
+
* of truth: the SQL carries the CHECKs, the append-only triggers, the partial
|
|
60
|
+
* indexes and the erasure function that drizzle-kit does not generate. A host
|
|
61
|
+
* re-exports this from its own schema module so its drizzle instance and its
|
|
62
|
+
* types know it, or declares a wider table over `AUDIT_COLUMNS`.
|
|
63
|
+
*/
|
|
64
|
+
export const auditEvents = pgTable('audit_events', AUDIT_COLUMNS, (t) => auditIndexes(t));
|
|
46
65
|
export const tables = { events: auditEvents };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@wtfalch/audit",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "The estate's append-only audit ledger: the table's shape and walls, erasure, export and readers, with a signer a host builds once inside its trusted base and never exports. Per-app Postgres, a host-supplied vocabulary of closed sets, no framework.",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|