@axiom-lattice/pg-stores 3.1.8 → 3.1.10
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/.turbo/turbo-build.log +10 -10
- package/CHANGELOG.md +16 -0
- package/dist/index.d.mts +26 -2
- package/dist/index.d.ts +26 -2
- package/dist/index.js +322 -47
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +321 -47
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLA2AApiKeyStore.migrations.test.ts +2 -1
- package/src/__tests__/PostgreSQLA2AApiKeyStore.test.ts +45 -0
- package/src/__tests__/PostgreSQLOpenAuditStore.test.ts +100 -0
- package/src/__tests__/PostgreSQLProjectRoomMessageStore.test.ts +14 -0
- package/src/__tests__/PostgresSharedResourceStore.openGrants.test.ts +62 -0
- package/src/__tests__/create_open_audit_table.test.ts +40 -0
- package/src/createPgStoreConfig.ts +8 -0
- package/src/index.ts +1 -0
- package/src/migrations/add_api_key_grants.ts +25 -0
- package/src/migrations/add_share_open_grants.ts +23 -0
- package/src/migrations/create_open_audit_table.ts +36 -0
- package/src/stores/PostgreSQLA2AApiKeyStore.ts +90 -14
- package/src/stores/PostgreSQLOpenAuditStore.ts +143 -0
- package/src/stores/PostgreSQLProjectRoomMessageStore.ts +20 -1
- package/src/stores/PostgresSharedResourceStore.ts +5 -2
|
@@ -0,0 +1,143 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import type { PoolConfig } from "pg";
|
|
3
|
+
import type {
|
|
4
|
+
OpenAuditStore,
|
|
5
|
+
OpenAuditAppendInput,
|
|
6
|
+
OpenAuditQuery,
|
|
7
|
+
OpenAuditRecord,
|
|
8
|
+
} from "@axiom-lattice/protocols";
|
|
9
|
+
import { MigrationManager } from "../migrations/migration";
|
|
10
|
+
import { createOpenAuditTable } from "../migrations/create_open_audit_table";
|
|
11
|
+
|
|
12
|
+
export interface PostgreSQLOpenAuditStoreOptions {
|
|
13
|
+
pool?: Pool;
|
|
14
|
+
poolConfig?: string | PoolConfig;
|
|
15
|
+
autoMigrate?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/** Append-only PostgreSQL audit store for the Open API surface. */
|
|
19
|
+
export class PostgreSQLOpenAuditStore implements OpenAuditStore {
|
|
20
|
+
private pool: Pool;
|
|
21
|
+
private migrationManager!: MigrationManager;
|
|
22
|
+
private initialized = false;
|
|
23
|
+
private ownsPool = true;
|
|
24
|
+
private initPromise: Promise<void> | null = null;
|
|
25
|
+
|
|
26
|
+
constructor(options: PostgreSQLOpenAuditStoreOptions) {
|
|
27
|
+
if (options.pool) {
|
|
28
|
+
this.pool = options.pool;
|
|
29
|
+
this.ownsPool = false;
|
|
30
|
+
this.initialized = true;
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.pool =
|
|
34
|
+
typeof options.poolConfig === "string"
|
|
35
|
+
? new Pool({ connectionString: options.poolConfig })
|
|
36
|
+
: options.poolConfig
|
|
37
|
+
? new Pool(options.poolConfig as PoolConfig)
|
|
38
|
+
: (() => {
|
|
39
|
+
throw new Error("Either pool or poolConfig must be provided");
|
|
40
|
+
})();
|
|
41
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
42
|
+
this.migrationManager.register(createOpenAuditTable);
|
|
43
|
+
if (options.autoMigrate !== false) {
|
|
44
|
+
this.initialize().catch((error) => {
|
|
45
|
+
console.error("Failed to initialize PostgreSQLOpenAuditStore:", error);
|
|
46
|
+
throw error;
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
async initialize(): Promise<void> {
|
|
52
|
+
if (this.initialized) return;
|
|
53
|
+
if (this.initPromise) return this.initPromise;
|
|
54
|
+
this.initPromise = (async () => {
|
|
55
|
+
try {
|
|
56
|
+
await this.migrationManager.migrate();
|
|
57
|
+
this.initialized = true;
|
|
58
|
+
} finally {
|
|
59
|
+
this.initPromise = null;
|
|
60
|
+
}
|
|
61
|
+
})();
|
|
62
|
+
return this.initPromise;
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
async dispose(): Promise<void> {
|
|
66
|
+
if (this.ownsPool && this.pool) await this.pool.end();
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
private async ensureInitialized(): Promise<void> {
|
|
70
|
+
if (!this.initialized) await this.initialize();
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async query(params: OpenAuditQuery): Promise<OpenAuditRecord[]> {
|
|
74
|
+
await this.ensureInitialized();
|
|
75
|
+
const clauses = ["tenant_id = $1"];
|
|
76
|
+
const values: unknown[] = [params.tenantId];
|
|
77
|
+
if (params.credentialId) {
|
|
78
|
+
values.push(params.credentialId);
|
|
79
|
+
clauses.push(`credential_id = $${values.length}`);
|
|
80
|
+
}
|
|
81
|
+
if (params.domain) {
|
|
82
|
+
values.push(params.domain);
|
|
83
|
+
clauses.push(`domain = $${values.length}`);
|
|
84
|
+
}
|
|
85
|
+
if (params.status) {
|
|
86
|
+
values.push(params.status);
|
|
87
|
+
clauses.push(`status = $${values.length}`);
|
|
88
|
+
}
|
|
89
|
+
if (params.from) {
|
|
90
|
+
values.push(params.from);
|
|
91
|
+
clauses.push(`created_at >= $${values.length}`);
|
|
92
|
+
}
|
|
93
|
+
if (params.to) {
|
|
94
|
+
values.push(params.to);
|
|
95
|
+
clauses.push(`created_at < $${values.length}`);
|
|
96
|
+
}
|
|
97
|
+
const limit = params.limit ?? 100;
|
|
98
|
+
const offset = params.offset ?? 0;
|
|
99
|
+
values.push(limit, offset);
|
|
100
|
+
const { rows } = await this.pool.query(
|
|
101
|
+
`SELECT * FROM lattice_open_audit
|
|
102
|
+
WHERE ${clauses.join(" AND ")}
|
|
103
|
+
ORDER BY created_at DESC
|
|
104
|
+
LIMIT $${values.length - 1} OFFSET $${values.length}`,
|
|
105
|
+
values,
|
|
106
|
+
);
|
|
107
|
+
return rows.map((row) => ({
|
|
108
|
+
id: row.id,
|
|
109
|
+
tenantId: row.tenant_id,
|
|
110
|
+
credentialId: row.credential_id,
|
|
111
|
+
credentialKind: row.credential_kind,
|
|
112
|
+
projectId: row.project_id,
|
|
113
|
+
domain: row.domain,
|
|
114
|
+
action: row.action,
|
|
115
|
+
args: row.args,
|
|
116
|
+
status: row.status,
|
|
117
|
+
errorCode: row.error_code ?? undefined,
|
|
118
|
+
durationMs: row.duration_ms,
|
|
119
|
+
createdAt: row.created_at,
|
|
120
|
+
}));
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async append(record: OpenAuditAppendInput): Promise<void> {
|
|
124
|
+
await this.ensureInitialized();
|
|
125
|
+
await this.pool.query(
|
|
126
|
+
`INSERT INTO lattice_open_audit
|
|
127
|
+
(tenant_id, credential_id, credential_kind, project_id, domain, action, args, status, error_code, duration_ms)
|
|
128
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10)`,
|
|
129
|
+
[
|
|
130
|
+
record.tenantId,
|
|
131
|
+
record.credentialId,
|
|
132
|
+
record.credentialKind,
|
|
133
|
+
record.projectId,
|
|
134
|
+
record.domain,
|
|
135
|
+
record.action,
|
|
136
|
+
record.args === undefined ? null : JSON.stringify(record.args),
|
|
137
|
+
record.status,
|
|
138
|
+
record.errorCode ?? null,
|
|
139
|
+
Math.max(0, Math.round(record.durationMs)),
|
|
140
|
+
],
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
}
|
|
@@ -83,6 +83,25 @@ function mapRow(row: MessageRow): ProjectRoomMessage {
|
|
|
83
83
|
...(row.idempotency_key === null ? {} : { idempotencyKey: row.idempotency_key }), createdAt: row.created_at,
|
|
84
84
|
};
|
|
85
85
|
}
|
|
86
|
+
function mapRowsTolerant(
|
|
87
|
+
rows: MessageRow[],
|
|
88
|
+
scope: { tenantId: string; roomId: string },
|
|
89
|
+
): ProjectRoomMessage[] {
|
|
90
|
+
const messages: ProjectRoomMessage[] = [];
|
|
91
|
+
for (const row of rows) {
|
|
92
|
+
try {
|
|
93
|
+
messages.push(mapRow(row));
|
|
94
|
+
} catch (error) {
|
|
95
|
+
console.warn("[ProjectRoomMessageStore] Skipping invalid message row", {
|
|
96
|
+
tenantId: scope.tenantId,
|
|
97
|
+
roomId: scope.roomId,
|
|
98
|
+
rowId: typeof row.id === "string" ? row.id : undefined,
|
|
99
|
+
reason: error instanceof Error ? error.message : String(error),
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
return messages;
|
|
104
|
+
}
|
|
86
105
|
function isUniqueViolation(error: unknown): error is PostgreSQLError {
|
|
87
106
|
return typeof error === "object" && error !== null && "code" in error
|
|
88
107
|
&& (error as PostgreSQLError).code === "23505";
|
|
@@ -159,7 +178,7 @@ export class PostgreSQLProjectRoomMessageStore implements ProjectRoomMessageStor
|
|
|
159
178
|
WHERE tenant_id = $1 AND room_id = $2 ORDER BY created_at DESC, id DESC LIMIT $3`,
|
|
160
179
|
[input.tenantId, input.roomId, limit],
|
|
161
180
|
);
|
|
162
|
-
return result.rows.
|
|
181
|
+
return mapRowsTolerant(result.rows, { tenantId: input.tenantId, roomId: input.roomId });
|
|
163
182
|
}
|
|
164
183
|
|
|
165
184
|
/** Finds a message by tenant-scoped ID. */
|
|
@@ -25,6 +25,7 @@ type ShareRow = {
|
|
|
25
25
|
password_hash: string | null;
|
|
26
26
|
expires_at: string | null;
|
|
27
27
|
max_access: number | null;
|
|
28
|
+
open_grants: unknown[] | null;
|
|
28
29
|
access_count: number;
|
|
29
30
|
revoked: boolean;
|
|
30
31
|
created_at: string;
|
|
@@ -106,8 +107,8 @@ export class PostgresSharedResourceStore implements SharedResourceStore {
|
|
|
106
107
|
`INSERT INTO lattice_shared_resources
|
|
107
108
|
(token, address, title, created_by, tenant_id, workspace_id, project_id,
|
|
108
109
|
assistant_id, vm_isolation, visibility, password_hash,
|
|
109
|
-
expires_at, max_access, revoked)
|
|
110
|
-
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14)
|
|
110
|
+
expires_at, max_access, open_grants, revoked)
|
|
111
|
+
VALUES ($1,$2,$3,$4,$5,$6,$7,$8,$9,$10,$11,$12,$13,$14,$15)
|
|
111
112
|
RETURNING *`,
|
|
112
113
|
[
|
|
113
114
|
record.token,
|
|
@@ -123,6 +124,7 @@ export class PostgresSharedResourceStore implements SharedResourceStore {
|
|
|
123
124
|
record.passwordHash,
|
|
124
125
|
record.expiresAt ? record.expiresAt.toISOString() : null,
|
|
125
126
|
record.maxAccess,
|
|
127
|
+
record.openGrants ? JSON.stringify(record.openGrants) : null,
|
|
126
128
|
record.revoked,
|
|
127
129
|
],
|
|
128
130
|
);
|
|
@@ -229,6 +231,7 @@ export class PostgresSharedResourceStore implements SharedResourceStore {
|
|
|
229
231
|
passwordHash: row.password_hash,
|
|
230
232
|
expiresAt: row.expires_at ? new Date(row.expires_at) : null,
|
|
231
233
|
maxAccess: row.max_access,
|
|
234
|
+
openGrants: (row.open_grants as ShareRecord["openGrants"]) ?? undefined,
|
|
232
235
|
accessCount: row.access_count ?? 0,
|
|
233
236
|
revoked: row.revoked ?? false,
|
|
234
237
|
createdAt: new Date(row.created_at),
|