@axiom-lattice/pg-stores 3.0.0 → 3.0.2
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 +18 -0
- package/dist/index.d.mts +30 -3
- package/dist/index.d.ts +30 -3
- package/dist/index.js +254 -24
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +252 -24
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLAgentWebAppStore.migrations.test.ts +77 -0
- package/src/__tests__/PostgreSQLAgentWebAppStore.test.ts +290 -0
- package/src/createPgStoreConfig.ts +4 -0
- package/src/index.ts +10 -0
- package/src/migrations/agent_web_apps_migration.ts +37 -0
- package/src/stores/PostgreSQLAgentWebAppStore.ts +280 -0
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
import { randomUUID } from "crypto";
|
|
2
|
+
import type {
|
|
3
|
+
AgentWebApp,
|
|
4
|
+
AgentWebAppAppearance,
|
|
5
|
+
AgentWebAppFeatures,
|
|
6
|
+
AgentWebAppScope,
|
|
7
|
+
AgentWebAppStatus,
|
|
8
|
+
AgentWebAppStore,
|
|
9
|
+
AgentWebAppStorePatch,
|
|
10
|
+
AgentWebAppUpdateOptions,
|
|
11
|
+
CreateAgentWebAppInput,
|
|
12
|
+
} from "@axiom-lattice/protocols";
|
|
13
|
+
import { Pool } from "pg";
|
|
14
|
+
import type { PoolConfig } from "pg";
|
|
15
|
+
|
|
16
|
+
import { createAgentWebAppsTable } from "../migrations/agent_web_apps_migration";
|
|
17
|
+
import { MigrationManager } from "../migrations/migration";
|
|
18
|
+
|
|
19
|
+
export interface PostgreSQLAgentWebAppStoreOptions {
|
|
20
|
+
pool?: Pool;
|
|
21
|
+
poolConfig?: string | PoolConfig;
|
|
22
|
+
autoMigrate?: boolean;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
type AgentWebAppRow = {
|
|
26
|
+
id: string;
|
|
27
|
+
tenant_id: string;
|
|
28
|
+
assistant_id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
description: string | null;
|
|
31
|
+
status: AgentWebAppStatus;
|
|
32
|
+
integration: unknown;
|
|
33
|
+
scope: unknown;
|
|
34
|
+
features: unknown;
|
|
35
|
+
appearance: unknown;
|
|
36
|
+
created_at: Date;
|
|
37
|
+
updated_at: Date;
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
function isRecord(value: unknown): value is Record<string, unknown> {
|
|
41
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function isStringArray(value: unknown): value is string[] {
|
|
45
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isIntegration(value: unknown): value is AgentWebApp["integration"] {
|
|
49
|
+
return isRecord(value) && value.type === "react_sdk";
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function isScope(value: unknown): value is AgentWebAppScope {
|
|
53
|
+
return isRecord(value)
|
|
54
|
+
&& typeof value.defaultProjectId === "string"
|
|
55
|
+
&& isStringArray(value.allowedProjectIds)
|
|
56
|
+
&& (value.defaultModelKey === undefined || typeof value.defaultModelKey === "string")
|
|
57
|
+
&& (value.allowedModelKeys === undefined || isStringArray(value.allowedModelKeys));
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function isFeatures(value: unknown): value is AgentWebAppFeatures {
|
|
61
|
+
return isRecord(value)
|
|
62
|
+
&& typeof value.projectSelector === "boolean"
|
|
63
|
+
&& typeof value.modelSelector === "boolean"
|
|
64
|
+
&& typeof value.threadManagement === "boolean"
|
|
65
|
+
&& typeof value.attachments === "boolean"
|
|
66
|
+
&& typeof value.hitl === "boolean"
|
|
67
|
+
&& typeof value.genUI === "boolean";
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function isAppearance(value: unknown): value is AgentWebAppAppearance {
|
|
71
|
+
return isRecord(value)
|
|
72
|
+
&& (value.title === undefined || typeof value.title === "string")
|
|
73
|
+
&& (value.welcomeMessage === undefined || typeof value.welcomeMessage === "string")
|
|
74
|
+
&& (value.primaryColor === undefined || typeof value.primaryColor === "string");
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function isStatus(value: unknown): value is AgentWebAppStatus {
|
|
78
|
+
return value === "draft" || value === "active" || value === "disabled";
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function mapRow(row: AgentWebAppRow): AgentWebApp {
|
|
82
|
+
if (
|
|
83
|
+
!isStatus(row.status)
|
|
84
|
+
|| !isIntegration(row.integration)
|
|
85
|
+
|| !isScope(row.scope)
|
|
86
|
+
|| !isFeatures(row.features)
|
|
87
|
+
|| !isAppearance(row.appearance)
|
|
88
|
+
) {
|
|
89
|
+
throw new Error(`Invalid agent web app row: ${row.id}`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
return {
|
|
93
|
+
id: row.id,
|
|
94
|
+
tenantId: row.tenant_id,
|
|
95
|
+
assistantId: row.assistant_id,
|
|
96
|
+
name: row.name,
|
|
97
|
+
description: row.description ?? undefined,
|
|
98
|
+
status: row.status,
|
|
99
|
+
integration: row.integration,
|
|
100
|
+
scope: row.scope,
|
|
101
|
+
features: row.features,
|
|
102
|
+
appearance: row.appearance,
|
|
103
|
+
createdAt: row.created_at,
|
|
104
|
+
updatedAt: row.updated_at,
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
export class PostgreSQLAgentWebAppStore implements AgentWebAppStore {
|
|
109
|
+
private pool: Pool;
|
|
110
|
+
private migrationManager!: MigrationManager;
|
|
111
|
+
private initialized = false;
|
|
112
|
+
private ownsPool = true;
|
|
113
|
+
private initPromise: Promise<void> | null = null;
|
|
114
|
+
|
|
115
|
+
constructor(options: PostgreSQLAgentWebAppStoreOptions) {
|
|
116
|
+
if (options.pool) {
|
|
117
|
+
this.pool = options.pool;
|
|
118
|
+
this.ownsPool = false;
|
|
119
|
+
this.initialized = true;
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
this.pool = typeof options.poolConfig === "string"
|
|
124
|
+
? new Pool({ connectionString: options.poolConfig })
|
|
125
|
+
: options.poolConfig
|
|
126
|
+
? new Pool(options.poolConfig)
|
|
127
|
+
: (() => { throw new Error("Either pool or poolConfig must be provided"); })();
|
|
128
|
+
|
|
129
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
130
|
+
this.migrationManager.register(createAgentWebAppsTable);
|
|
131
|
+
|
|
132
|
+
if (options.autoMigrate !== false) {
|
|
133
|
+
this.startInitialization();
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async initialize(): Promise<void> {
|
|
138
|
+
if (this.initialized) return;
|
|
139
|
+
if (this.initPromise) return this.initPromise;
|
|
140
|
+
return this.startInitialization();
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
private startInitialization(): Promise<void> {
|
|
144
|
+
this.initPromise = this.migrationManager.migrate().then(() => {
|
|
145
|
+
this.initialized = true;
|
|
146
|
+
});
|
|
147
|
+
// Eager migration errors remain on initPromise for initialize/operations.
|
|
148
|
+
void this.initPromise.catch(() => undefined);
|
|
149
|
+
return this.initPromise;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
async dispose(): Promise<void> {
|
|
153
|
+
if (this.ownsPool) await this.pool.end();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
private async ensureInitialized(): Promise<void> {
|
|
157
|
+
if (!this.initialized) await this.initialize();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
async list(tenantId: string, assistantId?: string): Promise<AgentWebApp[]> {
|
|
161
|
+
await this.ensureInitialized();
|
|
162
|
+
const result = assistantId === undefined
|
|
163
|
+
? await this.pool.query<AgentWebAppRow>(
|
|
164
|
+
`SELECT * FROM lattice_agent_web_apps
|
|
165
|
+
WHERE tenant_id = $1
|
|
166
|
+
ORDER BY created_at DESC, id DESC`,
|
|
167
|
+
[tenantId],
|
|
168
|
+
)
|
|
169
|
+
: await this.pool.query<AgentWebAppRow>(
|
|
170
|
+
`SELECT * FROM lattice_agent_web_apps
|
|
171
|
+
WHERE tenant_id = $1 AND assistant_id = $2
|
|
172
|
+
ORDER BY created_at DESC, id DESC`,
|
|
173
|
+
[tenantId, assistantId],
|
|
174
|
+
);
|
|
175
|
+
return result.rows.map(mapRow);
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
async getById(tenantId: string, webAppId: string): Promise<AgentWebApp | null> {
|
|
179
|
+
await this.ensureInitialized();
|
|
180
|
+
const result = await this.pool.query<AgentWebAppRow>(
|
|
181
|
+
`SELECT * FROM lattice_agent_web_apps
|
|
182
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
183
|
+
[tenantId, webAppId],
|
|
184
|
+
);
|
|
185
|
+
return result.rows[0] ? mapRow(result.rows[0]) : null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
async findById(webAppId: string): Promise<AgentWebApp | null> {
|
|
189
|
+
await this.ensureInitialized();
|
|
190
|
+
const result = await this.pool.query<AgentWebAppRow>(
|
|
191
|
+
`SELECT * FROM lattice_agent_web_apps
|
|
192
|
+
WHERE id = $1`,
|
|
193
|
+
[webAppId],
|
|
194
|
+
);
|
|
195
|
+
return result.rows[0] ? mapRow(result.rows[0]) : null;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
async create(tenantId: string, input: CreateAgentWebAppInput): Promise<AgentWebApp> {
|
|
199
|
+
await this.ensureInitialized();
|
|
200
|
+
const id = `webapp_${randomUUID().replace(/-/g, "")}`;
|
|
201
|
+
const result = await this.pool.query<AgentWebAppRow>(
|
|
202
|
+
`INSERT INTO lattice_agent_web_apps
|
|
203
|
+
(id, tenant_id, assistant_id, name, description, status, integration, scope, features, appearance)
|
|
204
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7::jsonb, $8::jsonb, $9::jsonb, $10::jsonb)
|
|
205
|
+
RETURNING *`,
|
|
206
|
+
[
|
|
207
|
+
id,
|
|
208
|
+
tenantId,
|
|
209
|
+
input.assistantId,
|
|
210
|
+
input.name,
|
|
211
|
+
input.description ?? null,
|
|
212
|
+
"draft",
|
|
213
|
+
JSON.stringify(input.integration),
|
|
214
|
+
JSON.stringify(input.scope),
|
|
215
|
+
JSON.stringify(input.features),
|
|
216
|
+
JSON.stringify(input.appearance),
|
|
217
|
+
],
|
|
218
|
+
);
|
|
219
|
+
return mapRow(result.rows[0]);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
async update(
|
|
223
|
+
tenantId: string,
|
|
224
|
+
webAppId: string,
|
|
225
|
+
patch: AgentWebAppStorePatch,
|
|
226
|
+
options?: AgentWebAppUpdateOptions,
|
|
227
|
+
): Promise<AgentWebApp | null> {
|
|
228
|
+
await this.ensureInitialized();
|
|
229
|
+
const assignments: string[] = [];
|
|
230
|
+
const values: unknown[] = [];
|
|
231
|
+
const add = (column: string, value: unknown): void => {
|
|
232
|
+
values.push(value);
|
|
233
|
+
assignments.push(`${column} = $${values.length}`);
|
|
234
|
+
};
|
|
235
|
+
|
|
236
|
+
if (patch.name !== undefined) add("name", patch.name);
|
|
237
|
+
if (Object.prototype.hasOwnProperty.call(patch, "description")) {
|
|
238
|
+
add("description", patch.description ?? null);
|
|
239
|
+
}
|
|
240
|
+
if (patch.scope !== undefined) {
|
|
241
|
+
add("scope", JSON.stringify(patch.scope));
|
|
242
|
+
}
|
|
243
|
+
if (patch.features !== undefined) {
|
|
244
|
+
add("features", JSON.stringify(patch.features));
|
|
245
|
+
}
|
|
246
|
+
if (patch.appearance !== undefined) {
|
|
247
|
+
add("appearance", JSON.stringify(patch.appearance));
|
|
248
|
+
}
|
|
249
|
+
if (patch.status !== undefined) add("status", patch.status);
|
|
250
|
+
|
|
251
|
+
if (assignments.length === 0) return this.getById(tenantId, webAppId);
|
|
252
|
+
values.push(tenantId, webAppId);
|
|
253
|
+
const tenantParam = values.length - 1;
|
|
254
|
+
const idParam = values.length;
|
|
255
|
+
const expectedUpdatedAtClause = options?.expectedUpdatedAt
|
|
256
|
+
? ` AND date_trunc('milliseconds', updated_at) = $${values.push(options.expectedUpdatedAt)}`
|
|
257
|
+
: "";
|
|
258
|
+
const result = await this.pool.query<AgentWebAppRow>(
|
|
259
|
+
`UPDATE lattice_agent_web_apps
|
|
260
|
+
SET ${assignments.join(", ")}, updated_at = GREATEST(
|
|
261
|
+
date_trunc('milliseconds', clock_timestamp()),
|
|
262
|
+
updated_at + interval '1 millisecond'
|
|
263
|
+
)
|
|
264
|
+
WHERE tenant_id = $${tenantParam} AND id = $${idParam}${expectedUpdatedAtClause}
|
|
265
|
+
RETURNING *`,
|
|
266
|
+
values,
|
|
267
|
+
);
|
|
268
|
+
return result.rows[0] ? mapRow(result.rows[0]) : null;
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async delete(tenantId: string, webAppId: string): Promise<boolean> {
|
|
272
|
+
await this.ensureInitialized();
|
|
273
|
+
const result = await this.pool.query(
|
|
274
|
+
`DELETE FROM lattice_agent_web_apps
|
|
275
|
+
WHERE tenant_id = $1 AND id = $2`,
|
|
276
|
+
[tenantId, webAppId],
|
|
277
|
+
);
|
|
278
|
+
return result.rowCount === 1;
|
|
279
|
+
}
|
|
280
|
+
}
|