@axiom-lattice/pg-stores 1.0.70 → 1.0.72
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 +67 -7
- package/dist/index.d.ts +67 -7
- package/dist/index.js +641 -32
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +638 -32
- package/dist/index.mjs.map +1 -1
- package/package.json +5 -4
- package/scripts/check-migration-versions.mjs +37 -0
- package/src/createPgStoreConfig.ts +19 -16
- package/src/index.ts +6 -0
- package/src/migrations/add_workspace_project_to_queue.ts +30 -0
- package/src/migrations/assistant_owner_user_migration.ts +26 -0
- package/src/migrations/menu_items_migration.ts +38 -0
- package/src/migrations/task_migration.ts +51 -0
- package/src/migrations/workflow_tracking_migrations.ts +19 -0
- package/src/stores/MenuStore.ts +204 -0
- package/src/stores/PostgreSQLAssistantStore.ts +47 -4
- package/src/stores/PostgreSQLChannelInstallationStore.ts +26 -0
- package/src/stores/PostgreSQLTaskStore.ts +294 -0
- package/src/stores/PostgreSQLWorkflowTrackingStore.ts +29 -3
- package/src/stores/ThreadMessageQueueStore.ts +13 -9
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.72",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -20,10 +20,11 @@
|
|
|
20
20
|
"author": "",
|
|
21
21
|
"license": "MIT",
|
|
22
22
|
"dependencies": {
|
|
23
|
+
"@langchain/langgraph-checkpoint-postgres": "1.0.0",
|
|
23
24
|
"pg": "^8.16.3",
|
|
24
25
|
"uuid": "^9.0.1",
|
|
25
|
-
"@axiom-lattice/core": "2.1.
|
|
26
|
-
"@axiom-lattice/protocols": "2.1.
|
|
26
|
+
"@axiom-lattice/core": "2.1.81",
|
|
27
|
+
"@axiom-lattice/protocols": "2.1.43"
|
|
27
28
|
},
|
|
28
29
|
"devDependencies": {
|
|
29
30
|
"@types/node": "^20.11.24",
|
|
@@ -36,7 +37,7 @@
|
|
|
36
37
|
"scripts": {
|
|
37
38
|
"build": "tsup src/index.ts --format cjs,esm --dts --sourcemap",
|
|
38
39
|
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
39
|
-
"lint": "tsc --noEmit",
|
|
40
|
+
"lint": "tsc --noEmit && node scripts/check-migration-versions.mjs",
|
|
40
41
|
"clean": "rimraf dist"
|
|
41
42
|
}
|
|
42
43
|
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check for duplicate migration version numbers.
|
|
3
|
+
* Exits with code 1 if any version collision is found.
|
|
4
|
+
* Run as part of lint pipeline.
|
|
5
|
+
*/
|
|
6
|
+
import { readFileSync, readdirSync } from "node:fs";
|
|
7
|
+
import { join, dirname } from "node:path";
|
|
8
|
+
import { fileURLToPath } from "node:url";
|
|
9
|
+
|
|
10
|
+
const MIGRATIONS_DIR = join(dirname(fileURLToPath(import.meta.url)), "../src/migrations");
|
|
11
|
+
|
|
12
|
+
const files = readdirSync(MIGRATIONS_DIR).filter(f => f.endsWith(".ts"));
|
|
13
|
+
const versionMap = new Map();
|
|
14
|
+
|
|
15
|
+
let hasError = false;
|
|
16
|
+
|
|
17
|
+
for (const file of files) {
|
|
18
|
+
const content = readFileSync(join(MIGRATIONS_DIR, file), "utf-8");
|
|
19
|
+
const matches = content.matchAll(/^\s*version:\s*(\d+)/gm);
|
|
20
|
+
for (const m of matches) {
|
|
21
|
+
const version = parseInt(m[1]);
|
|
22
|
+
if (versionMap.has(version)) {
|
|
23
|
+
console.error(
|
|
24
|
+
`\x1b[31mMIGRATION VERSION CONFLICT:\x1b[0m v${version} in both "${versionMap.get(version)}" and "${file}"`
|
|
25
|
+
);
|
|
26
|
+
hasError = true;
|
|
27
|
+
} else {
|
|
28
|
+
versionMap.set(version, file);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
if (hasError) {
|
|
34
|
+
process.exit(1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
console.log(`\x1b[32mMigration versions OK\x1b[0m (${versionMap.size} unique versions in ${files.length} files)`);
|
|
@@ -1,10 +1,3 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* createPgStoreConfig
|
|
3
|
-
*
|
|
4
|
-
* Creates a map of all PG store instances from a single connection string,
|
|
5
|
-
* ready to pass directly to configureStores().
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
1
|
import { PostgreSQLThreadStore } from "./stores/PostgreSQLThreadStore";
|
|
9
2
|
import { PostgreSQLAssistantStore } from "./stores/PostgreSQLAssistantStore";
|
|
10
3
|
import { PostgreSQLDatabaseConfigStore } from "./stores/PostgreSQLDatabaseConfigStore";
|
|
@@ -22,28 +15,38 @@ import { ChannelBindingStore } from "./stores/ChannelBindingStore";
|
|
|
22
15
|
import { PostgreSQLChannelInstallationStore } from "./stores/PostgreSQLChannelInstallationStore";
|
|
23
16
|
import { PostgreSQLA2AApiKeyStore } from "./stores/PostgreSQLA2AApiKeyStore";
|
|
24
17
|
import { PostgreSQLScheduleStorage } from "./stores/PostgreSQLScheduleStorage";
|
|
18
|
+
import { PostgreSQLTaskStore } from "./stores/PostgreSQLTaskStore";
|
|
19
|
+
import { MenuStore } from "./stores/MenuStore";
|
|
20
|
+
import { PostgresSaver } from "@langchain/langgraph-checkpoint-postgres";
|
|
25
21
|
|
|
26
22
|
export function createPgStoreConfig(connectionString: string) {
|
|
27
|
-
const opts = { poolConfig: connectionString, autoMigrate:
|
|
28
|
-
|
|
23
|
+
const opts = { poolConfig: connectionString, autoMigrate: true } as const;
|
|
24
|
+
|
|
25
|
+
const checkpoint = PostgresSaver.fromConnString(connectionString);
|
|
26
|
+
checkpoint.setup().catch((err) => {
|
|
27
|
+
console.error("[pg-stores] Failed to setup checkpoint table:", err.message || err);
|
|
28
|
+
});
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
31
|
workspace: new PostgreSQLWorkspaceStore(opts),
|
|
32
32
|
project: new PostgreSQLProjectStore(opts),
|
|
33
|
-
eval: new PostgreSQLEvalStore(
|
|
33
|
+
eval: new PostgreSQLEvalStore(opts),
|
|
34
34
|
user: new PostgreSQLUserStore(opts),
|
|
35
35
|
tenant: new PostgreSQLTenantStore(opts),
|
|
36
36
|
userTenantLink: new PostgreSQLUserTenantLinkStore(opts),
|
|
37
|
-
channelBinding: new ChannelBindingStore(
|
|
38
|
-
channelInstallation: new PostgreSQLChannelInstallationStore(
|
|
37
|
+
channelBinding: new ChannelBindingStore(opts),
|
|
38
|
+
channelInstallation: new PostgreSQLChannelInstallationStore(opts),
|
|
39
39
|
thread: new PostgreSQLThreadStore(opts),
|
|
40
40
|
database: new PostgreSQLDatabaseConfigStore(opts),
|
|
41
41
|
metrics: new PostgreSQLMetricsServerConfigStore(opts),
|
|
42
42
|
mcp: new PostgreSQLMcpServerConfigStore(opts),
|
|
43
43
|
assistant: new PostgreSQLAssistantStore(opts),
|
|
44
|
-
workflowTracking: new PostgreSQLWorkflowTrackingStore(
|
|
45
|
-
threadMessageQueue: new ThreadMessageQueueStore(
|
|
46
|
-
|
|
44
|
+
workflowTracking: new PostgreSQLWorkflowTrackingStore(opts),
|
|
45
|
+
threadMessageQueue: new ThreadMessageQueueStore(opts),
|
|
46
|
+
task: new PostgreSQLTaskStore(opts),
|
|
47
|
+
a2aApiKey: new PostgreSQLA2AApiKeyStore(opts),
|
|
47
48
|
schedule: new PostgreSQLScheduleStorage(opts),
|
|
49
|
+
menu: new MenuStore(opts),
|
|
50
|
+
checkpoint,
|
|
48
51
|
};
|
|
49
|
-
}
|
|
52
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -57,6 +57,8 @@ export * from "./stores/PostgreSQLA2AApiKeyStore";
|
|
|
57
57
|
export * from "./stores/PostgreSQLWorkflowTrackingStore";
|
|
58
58
|
export * from "./stores/PostgreSQLEvalStore";
|
|
59
59
|
export * from "./migrations/eval_migrations";
|
|
60
|
+
export * from "./migrations/menu_items_migration";
|
|
61
|
+
export * from "./stores/MenuStore";
|
|
60
62
|
|
|
61
63
|
// Re-export for convenience
|
|
62
64
|
export { PostgreSQLThreadStore } from "./stores/PostgreSQLThreadStore";
|
|
@@ -72,6 +74,7 @@ export { PostgreSQLUserStore } from "./stores/PostgreSQLUserStore";
|
|
|
72
74
|
export { PostgreSQLTenantStore } from "./stores/PostgreSQLTenantStore";
|
|
73
75
|
export { PostgreSQLUserTenantLinkStore } from "./stores/PostgreSQLUserTenantLinkStore";
|
|
74
76
|
export { ChannelBindingStore } from "./stores/ChannelBindingStore";
|
|
77
|
+
export { MenuStore } from "./stores/MenuStore";
|
|
75
78
|
export { ChannelIdentityMappingStore } from "./stores/ChannelIdentityMappingStore";
|
|
76
79
|
export { PostgreSQLChannelInstallationStore } from "./stores/PostgreSQLChannelInstallationStore";
|
|
77
80
|
export { PostgreSQLA2AApiKeyStore } from "./stores/PostgreSQLA2AApiKeyStore";
|
|
@@ -102,6 +105,9 @@ export type {
|
|
|
102
105
|
Binding,
|
|
103
106
|
BindingRegistry,
|
|
104
107
|
CreateBindingInput,
|
|
108
|
+
MenuItem,
|
|
109
|
+
MenuRegistry,
|
|
110
|
+
CreateMenuItemInput,
|
|
105
111
|
ChannelInstallationStore,
|
|
106
112
|
ChannelInstallation,
|
|
107
113
|
CreateChannelInstallationRequest,
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Add workspace_id and project_id columns to thread message queue
|
|
3
|
+
* so that thread recovery after server restart uses the correct scope.
|
|
4
|
+
*/
|
|
5
|
+
|
|
6
|
+
import { PoolClient } from "pg";
|
|
7
|
+
import { Migration } from "./migration";
|
|
8
|
+
|
|
9
|
+
export const addWorkspaceProjectToQueue: Migration = {
|
|
10
|
+
version: 131,
|
|
11
|
+
name: "add_workspace_project_to_queue",
|
|
12
|
+
up: async (client: PoolClient) => {
|
|
13
|
+
await client.query(`
|
|
14
|
+
ALTER TABLE lattice_thread_message_queue
|
|
15
|
+
ADD COLUMN IF NOT EXISTS workspace_id VARCHAR(255)
|
|
16
|
+
`);
|
|
17
|
+
await client.query(`
|
|
18
|
+
ALTER TABLE lattice_thread_message_queue
|
|
19
|
+
ADD COLUMN IF NOT EXISTS project_id VARCHAR(255)
|
|
20
|
+
`);
|
|
21
|
+
},
|
|
22
|
+
down: async (client: PoolClient) => {
|
|
23
|
+
await client.query(
|
|
24
|
+
"ALTER TABLE lattice_thread_message_queue DROP COLUMN IF EXISTS workspace_id"
|
|
25
|
+
);
|
|
26
|
+
await client.query(
|
|
27
|
+
"ALTER TABLE lattice_thread_message_queue DROP COLUMN IF EXISTS project_id"
|
|
28
|
+
);
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { Migration } from "./migration";
|
|
3
|
+
|
|
4
|
+
export const addAssistantOwnerUserId: Migration = {
|
|
5
|
+
version: 132,
|
|
6
|
+
name: "add_assistant_owner_user_id",
|
|
7
|
+
up: async (client: PoolClient) => {
|
|
8
|
+
await client.query(`
|
|
9
|
+
ALTER TABLE lattice_assistants
|
|
10
|
+
ADD COLUMN IF NOT EXISTS owner_user_id VARCHAR(255)
|
|
11
|
+
`);
|
|
12
|
+
|
|
13
|
+
await client.query(`
|
|
14
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_assistants_owner_user_id
|
|
15
|
+
ON lattice_assistants(owner_user_id)
|
|
16
|
+
`);
|
|
17
|
+
},
|
|
18
|
+
down: async (client: PoolClient) => {
|
|
19
|
+
await client.query(
|
|
20
|
+
"DROP INDEX IF EXISTS idx_lattice_assistants_owner_user_id"
|
|
21
|
+
);
|
|
22
|
+
await client.query(
|
|
23
|
+
"ALTER TABLE lattice_assistants DROP COLUMN IF EXISTS owner_user_id"
|
|
24
|
+
);
|
|
25
|
+
},
|
|
26
|
+
};
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { Migration } from "./migration";
|
|
3
|
+
|
|
4
|
+
export const createMenuItemsTable: Migration = {
|
|
5
|
+
version: 134,
|
|
6
|
+
name: "create_menu_items_table",
|
|
7
|
+
up: async (client: PoolClient) => {
|
|
8
|
+
await client.query(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS lattice_menu_items (
|
|
10
|
+
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
|
11
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
12
|
+
menu_target VARCHAR(20) NOT NULL CHECK (menu_target IN ('sidebar', 'workspace')),
|
|
13
|
+
"group" VARCHAR(100),
|
|
14
|
+
name VARCHAR(255) NOT NULL,
|
|
15
|
+
icon VARCHAR(50),
|
|
16
|
+
sort_order INT NOT NULL DEFAULT 0,
|
|
17
|
+
content_type VARCHAR(20) NOT NULL CHECK (content_type IN ('agent', 'html', 'custom')),
|
|
18
|
+
content_config JSONB NOT NULL DEFAULT '{}',
|
|
19
|
+
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
20
|
+
created_at TIMESTAMP NOT NULL DEFAULT NOW(),
|
|
21
|
+
updated_at TIMESTAMP NOT NULL DEFAULT NOW()
|
|
22
|
+
)
|
|
23
|
+
`);
|
|
24
|
+
|
|
25
|
+
await client.query(`
|
|
26
|
+
CREATE INDEX IF NOT EXISTS idx_menu_items_tenant_target
|
|
27
|
+
ON lattice_menu_items(tenant_id, menu_target)
|
|
28
|
+
`);
|
|
29
|
+
|
|
30
|
+
await client.query(`
|
|
31
|
+
CREATE INDEX IF NOT EXISTS idx_menu_items_sort
|
|
32
|
+
ON lattice_menu_items(tenant_id, menu_target, sort_order)
|
|
33
|
+
`);
|
|
34
|
+
},
|
|
35
|
+
down: async (client: PoolClient) => {
|
|
36
|
+
await client.query("DROP TABLE IF EXISTS lattice_menu_items");
|
|
37
|
+
},
|
|
38
|
+
};
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { PoolClient } from "pg";
|
|
2
|
+
import { Migration } from "./migration";
|
|
3
|
+
|
|
4
|
+
export const createTasksTable: Migration = {
|
|
5
|
+
version: 133,
|
|
6
|
+
name: "create_lattice_tasks_table",
|
|
7
|
+
up: async (client: PoolClient) => {
|
|
8
|
+
await client.query(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS lattice_tasks (
|
|
10
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
11
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
12
|
+
owner_type VARCHAR(10) NOT NULL DEFAULT 'user',
|
|
13
|
+
owner_id VARCHAR(255) NOT NULL,
|
|
14
|
+
title VARCHAR(500) NOT NULL,
|
|
15
|
+
description TEXT,
|
|
16
|
+
status VARCHAR(20) NOT NULL DEFAULT 'pending',
|
|
17
|
+
priority VARCHAR(10) NOT NULL DEFAULT 'medium',
|
|
18
|
+
due_date VARCHAR(50),
|
|
19
|
+
metadata JSONB,
|
|
20
|
+
parent_id VARCHAR(255),
|
|
21
|
+
source_id VARCHAR(255),
|
|
22
|
+
context JSONB,
|
|
23
|
+
created_at VARCHAR(50) NOT NULL,
|
|
24
|
+
updated_at VARCHAR(50) NOT NULL
|
|
25
|
+
)
|
|
26
|
+
`);
|
|
27
|
+
|
|
28
|
+
await client.query(`
|
|
29
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_tenant_id
|
|
30
|
+
ON lattice_tasks(tenant_id)
|
|
31
|
+
`);
|
|
32
|
+
|
|
33
|
+
await client.query(`
|
|
34
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_owner
|
|
35
|
+
ON lattice_tasks(tenant_id, owner_type, owner_id)
|
|
36
|
+
`);
|
|
37
|
+
|
|
38
|
+
await client.query(`
|
|
39
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_status
|
|
40
|
+
ON lattice_tasks(tenant_id, status)
|
|
41
|
+
`);
|
|
42
|
+
|
|
43
|
+
await client.query(`
|
|
44
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_tasks_parent_id
|
|
45
|
+
ON lattice_tasks(tenant_id, parent_id)
|
|
46
|
+
`);
|
|
47
|
+
},
|
|
48
|
+
down: async (client: PoolClient) => {
|
|
49
|
+
await client.query("DROP TABLE IF EXISTS lattice_tasks");
|
|
50
|
+
},
|
|
51
|
+
};
|
|
@@ -77,3 +77,22 @@ export const createWorkflowTrackingTables: Migration = {
|
|
|
77
77
|
`);
|
|
78
78
|
},
|
|
79
79
|
};
|
|
80
|
+
|
|
81
|
+
export const addStepThreadId: Migration = {
|
|
82
|
+
name: "add_step_thread_id",
|
|
83
|
+
version: 113,
|
|
84
|
+
|
|
85
|
+
up: async (client) => {
|
|
86
|
+
await client.query(`
|
|
87
|
+
ALTER TABLE lattice_workflow_steps
|
|
88
|
+
ADD COLUMN IF NOT EXISTS thread_id VARCHAR(255);
|
|
89
|
+
`);
|
|
90
|
+
},
|
|
91
|
+
|
|
92
|
+
down: async (client) => {
|
|
93
|
+
await client.query(`
|
|
94
|
+
ALTER TABLE lattice_workflow_steps
|
|
95
|
+
DROP COLUMN IF EXISTS thread_id;
|
|
96
|
+
`);
|
|
97
|
+
},
|
|
98
|
+
};
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import { Pool } from "pg";
|
|
2
|
+
import type { PoolConfig } from "pg";
|
|
3
|
+
import type {
|
|
4
|
+
MenuItem,
|
|
5
|
+
MenuRegistry,
|
|
6
|
+
CreateMenuItemInput,
|
|
7
|
+
UpdateMenuItemInput,
|
|
8
|
+
MenuContentConfig,
|
|
9
|
+
} from "@axiom-lattice/protocols";
|
|
10
|
+
import { MigrationManager } from "../migrations/migration";
|
|
11
|
+
import { createMenuItemsTable } from "../migrations/menu_items_migration";
|
|
12
|
+
|
|
13
|
+
export interface MenuStoreOptions {
|
|
14
|
+
poolConfig: string | PoolConfig;
|
|
15
|
+
autoMigrate?: boolean;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
type MenuItemRow = {
|
|
19
|
+
id: string;
|
|
20
|
+
tenant_id: string;
|
|
21
|
+
menu_target: string;
|
|
22
|
+
group: string | null;
|
|
23
|
+
name: string;
|
|
24
|
+
icon: string | null;
|
|
25
|
+
sort_order: number;
|
|
26
|
+
content_type: string;
|
|
27
|
+
content_config: Record<string, unknown>;
|
|
28
|
+
enabled: boolean;
|
|
29
|
+
created_at: Date;
|
|
30
|
+
updated_at: Date;
|
|
31
|
+
};
|
|
32
|
+
|
|
33
|
+
export class MenuStore implements MenuRegistry {
|
|
34
|
+
private pool: Pool;
|
|
35
|
+
private migrationManager: MigrationManager;
|
|
36
|
+
private initialized = false;
|
|
37
|
+
private initPromise: Promise<void> | null = null;
|
|
38
|
+
|
|
39
|
+
constructor(options: MenuStoreOptions) {
|
|
40
|
+
this.pool =
|
|
41
|
+
typeof options.poolConfig === "string"
|
|
42
|
+
? new Pool({ connectionString: options.poolConfig })
|
|
43
|
+
: new Pool(options.poolConfig);
|
|
44
|
+
|
|
45
|
+
this.migrationManager = new MigrationManager(this.pool);
|
|
46
|
+
this.migrationManager.register(createMenuItemsTable);
|
|
47
|
+
|
|
48
|
+
if (options.autoMigrate !== false) {
|
|
49
|
+
this.initialize().catch((error) => {
|
|
50
|
+
console.error("Failed to initialize MenuStore:", error);
|
|
51
|
+
throw error;
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
async initialize(): Promise<void> {
|
|
57
|
+
if (this.initialized) return;
|
|
58
|
+
if (this.initPromise) return this.initPromise;
|
|
59
|
+
this.initPromise = (async () => {
|
|
60
|
+
try {
|
|
61
|
+
await this.migrationManager.migrate();
|
|
62
|
+
this.initialized = true;
|
|
63
|
+
} finally {
|
|
64
|
+
this.initPromise = null;
|
|
65
|
+
}
|
|
66
|
+
})();
|
|
67
|
+
return this.initPromise;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async list(params: { tenantId: string; menuTarget?: string }): Promise<MenuItem[]> {
|
|
71
|
+
await this.ensureInitialized();
|
|
72
|
+
const conditions: string[] = ["tenant_id = $1"];
|
|
73
|
+
const values: unknown[] = [params.tenantId];
|
|
74
|
+
let idx = 2;
|
|
75
|
+
|
|
76
|
+
if (params.menuTarget) {
|
|
77
|
+
conditions.push(`menu_target = $${idx++}`);
|
|
78
|
+
values.push(params.menuTarget);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
const result = await this.pool.query<MenuItemRow>(
|
|
82
|
+
`SELECT * FROM lattice_menu_items
|
|
83
|
+
WHERE ${conditions.join(" AND ")}
|
|
84
|
+
AND enabled = true
|
|
85
|
+
ORDER BY sort_order ASC`,
|
|
86
|
+
values,
|
|
87
|
+
);
|
|
88
|
+
return result.rows.map((r) => this.mapRowToMenuItem(r));
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async getById(id: string): Promise<MenuItem | null> {
|
|
92
|
+
await this.ensureInitialized();
|
|
93
|
+
const result = await this.pool.query<MenuItemRow>(
|
|
94
|
+
`SELECT * FROM lattice_menu_items WHERE id = $1`,
|
|
95
|
+
[id],
|
|
96
|
+
);
|
|
97
|
+
if (result.rows.length === 0) return null;
|
|
98
|
+
return this.mapRowToMenuItem(result.rows[0]);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
async create(input: CreateMenuItemInput & { tenantId: string }): Promise<MenuItem> {
|
|
102
|
+
await this.ensureInitialized();
|
|
103
|
+
const result = await this.pool.query<MenuItemRow>(
|
|
104
|
+
`INSERT INTO lattice_menu_items
|
|
105
|
+
(tenant_id, menu_target, "group", name, icon, sort_order, content_type, content_config)
|
|
106
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
107
|
+
RETURNING *`,
|
|
108
|
+
[
|
|
109
|
+
input.tenantId,
|
|
110
|
+
input.menuTarget,
|
|
111
|
+
input.group || null,
|
|
112
|
+
input.name,
|
|
113
|
+
input.icon || null,
|
|
114
|
+
input.sortOrder ?? 0,
|
|
115
|
+
input.contentType,
|
|
116
|
+
JSON.stringify(input.contentConfig),
|
|
117
|
+
],
|
|
118
|
+
);
|
|
119
|
+
return this.mapRowToMenuItem(result.rows[0]);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async update(id: string, patch: UpdateMenuItemInput): Promise<MenuItem> {
|
|
123
|
+
await this.ensureInitialized();
|
|
124
|
+
const existing = await this.pool.query<MenuItemRow>(
|
|
125
|
+
`SELECT * FROM lattice_menu_items WHERE id = $1`,
|
|
126
|
+
[id],
|
|
127
|
+
);
|
|
128
|
+
if (existing.rows.length === 0) {
|
|
129
|
+
throw new Error(`MenuItem ${id} not found`);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const row = existing.rows[0];
|
|
133
|
+
const setClauses: string[] = [];
|
|
134
|
+
const values: unknown[] = [];
|
|
135
|
+
let idx = 1;
|
|
136
|
+
|
|
137
|
+
if (patch.group !== undefined) {
|
|
138
|
+
setClauses.push(`"group" = $${idx++}`);
|
|
139
|
+
values.push(patch.group);
|
|
140
|
+
}
|
|
141
|
+
if (patch.name !== undefined) {
|
|
142
|
+
setClauses.push(`name = $${idx++}`);
|
|
143
|
+
values.push(patch.name);
|
|
144
|
+
}
|
|
145
|
+
if (patch.icon !== undefined) {
|
|
146
|
+
setClauses.push(`icon = $${idx++}`);
|
|
147
|
+
values.push(patch.icon);
|
|
148
|
+
}
|
|
149
|
+
if (patch.sortOrder !== undefined) {
|
|
150
|
+
setClauses.push(`sort_order = $${idx++}`);
|
|
151
|
+
values.push(patch.sortOrder);
|
|
152
|
+
}
|
|
153
|
+
if (patch.contentConfig !== undefined) {
|
|
154
|
+
setClauses.push(`content_config = $${idx++}`);
|
|
155
|
+
values.push(JSON.stringify(patch.contentConfig));
|
|
156
|
+
}
|
|
157
|
+
if (patch.enabled !== undefined) {
|
|
158
|
+
setClauses.push(`enabled = $${idx++}`);
|
|
159
|
+
values.push(patch.enabled);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (setClauses.length === 0) {
|
|
163
|
+
return this.mapRowToMenuItem(row);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
setClauses.push(`updated_at = NOW()`);
|
|
167
|
+
values.push(id);
|
|
168
|
+
|
|
169
|
+
const result = await this.pool.query<MenuItemRow>(
|
|
170
|
+
`UPDATE lattice_menu_items SET ${setClauses.join(", ")} WHERE id = $${idx}
|
|
171
|
+
RETURNING *`,
|
|
172
|
+
values,
|
|
173
|
+
);
|
|
174
|
+
return this.mapRowToMenuItem(result.rows[0]);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
async delete(id: string): Promise<void> {
|
|
178
|
+
await this.ensureInitialized();
|
|
179
|
+
await this.pool.query(`DELETE FROM lattice_menu_items WHERE id = $1`, [id]);
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
private async ensureInitialized(): Promise<void> {
|
|
183
|
+
if (!this.initialized) {
|
|
184
|
+
await this.initialize();
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private mapRowToMenuItem(row: MenuItemRow): MenuItem {
|
|
189
|
+
return {
|
|
190
|
+
id: row.id,
|
|
191
|
+
tenantId: row.tenant_id,
|
|
192
|
+
menuTarget: row.menu_target as MenuItem["menuTarget"],
|
|
193
|
+
group: row.group || undefined,
|
|
194
|
+
name: row.name,
|
|
195
|
+
icon: row.icon || undefined,
|
|
196
|
+
sortOrder: row.sort_order,
|
|
197
|
+
contentType: row.content_type as MenuItem["contentType"],
|
|
198
|
+
contentConfig: row.content_config as unknown as MenuContentConfig,
|
|
199
|
+
enabled: row.enabled,
|
|
200
|
+
createdAt: row.created_at,
|
|
201
|
+
updatedAt: row.updated_at,
|
|
202
|
+
};
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -13,6 +13,7 @@ import { MigrationManager } from "../migrations/migration";
|
|
|
13
13
|
import { createAssistantsTable } from "../migrations/assistant_migrations";
|
|
14
14
|
import { addAssistantTenantId } from "../migrations/assistant_tenant_migration";
|
|
15
15
|
import { changeAssistantPrimaryKey } from "../migrations/assistant_pk_migration";
|
|
16
|
+
import { addAssistantOwnerUserId } from "../migrations/assistant_owner_user_migration";
|
|
16
17
|
|
|
17
18
|
/**
|
|
18
19
|
* PostgreSQL AssistantStore options
|
|
@@ -56,6 +57,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
56
57
|
this.migrationManager.register(createAssistantsTable);
|
|
57
58
|
this.migrationManager.register(addAssistantTenantId);
|
|
58
59
|
this.migrationManager.register(changeAssistantPrimaryKey);
|
|
60
|
+
this.migrationManager.register(addAssistantOwnerUserId);
|
|
59
61
|
|
|
60
62
|
// Auto-migrate by default
|
|
61
63
|
if (options.autoMigrate !== false) {
|
|
@@ -103,11 +105,12 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
103
105
|
name: string;
|
|
104
106
|
description: string | null;
|
|
105
107
|
graph_definition: any;
|
|
108
|
+
owner_user_id: string | null;
|
|
106
109
|
created_at: Date;
|
|
107
110
|
updated_at: Date;
|
|
108
111
|
}>(
|
|
109
112
|
`
|
|
110
|
-
SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
|
|
113
|
+
SELECT id, tenant_id, name, description, graph_definition, owner_user_id, created_at, updated_at
|
|
111
114
|
FROM lattice_assistants
|
|
112
115
|
WHERE tenant_id = $1
|
|
113
116
|
ORDER BY created_at DESC
|
|
@@ -130,11 +133,12 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
130
133
|
name: string;
|
|
131
134
|
description: string | null;
|
|
132
135
|
graph_definition: any;
|
|
136
|
+
owner_user_id: string | null;
|
|
133
137
|
created_at: Date;
|
|
134
138
|
updated_at: Date;
|
|
135
139
|
}>(
|
|
136
140
|
`
|
|
137
|
-
SELECT id, tenant_id, name, description, graph_definition, created_at, updated_at
|
|
141
|
+
SELECT id, tenant_id, name, description, graph_definition, owner_user_id, created_at, updated_at
|
|
138
142
|
FROM lattice_assistants
|
|
139
143
|
WHERE tenant_id = $1 AND id = $2
|
|
140
144
|
`,
|
|
@@ -162,12 +166,13 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
162
166
|
|
|
163
167
|
await this.pool.query(
|
|
164
168
|
`
|
|
165
|
-
INSERT INTO lattice_assistants (id, tenant_id, name, description, graph_definition, created_at, updated_at)
|
|
166
|
-
VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
169
|
+
INSERT INTO lattice_assistants (id, tenant_id, name, description, graph_definition, owner_user_id, created_at, updated_at)
|
|
170
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, $8)
|
|
167
171
|
ON CONFLICT (id, tenant_id) DO UPDATE SET
|
|
168
172
|
name = EXCLUDED.name,
|
|
169
173
|
description = EXCLUDED.description,
|
|
170
174
|
graph_definition = EXCLUDED.graph_definition,
|
|
175
|
+
owner_user_id = EXCLUDED.owner_user_id,
|
|
171
176
|
updated_at = EXCLUDED.updated_at
|
|
172
177
|
`,
|
|
173
178
|
[
|
|
@@ -176,6 +181,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
176
181
|
data.name,
|
|
177
182
|
data.description || null,
|
|
178
183
|
JSON.stringify(data.graphDefinition),
|
|
184
|
+
data.ownerUserId || null,
|
|
179
185
|
now,
|
|
180
186
|
now,
|
|
181
187
|
]
|
|
@@ -187,6 +193,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
187
193
|
name: data.name,
|
|
188
194
|
description: data.description,
|
|
189
195
|
graphDefinition: data.graphDefinition,
|
|
196
|
+
ownerUserId: data.ownerUserId,
|
|
190
197
|
createdAt: now,
|
|
191
198
|
updatedAt: now,
|
|
192
199
|
};
|
|
@@ -228,6 +235,11 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
228
235
|
updateValues.push(JSON.stringify(updates.graphDefinition));
|
|
229
236
|
}
|
|
230
237
|
|
|
238
|
+
if (updates.ownerUserId !== undefined) {
|
|
239
|
+
updateFields.push(`owner_user_id = $${paramIndex++}`);
|
|
240
|
+
updateValues.push(updates.ownerUserId || null);
|
|
241
|
+
}
|
|
242
|
+
|
|
231
243
|
if (updateFields.length === 0) {
|
|
232
244
|
// No fields to update
|
|
233
245
|
return existing;
|
|
@@ -289,6 +301,35 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
289
301
|
return result.rows.length > 0;
|
|
290
302
|
}
|
|
291
303
|
|
|
304
|
+
/**
|
|
305
|
+
* Get assistant by owner user ID
|
|
306
|
+
*/
|
|
307
|
+
async getByOwner(tenantId: string, userId: string): Promise<Assistant | null> {
|
|
308
|
+
await this.ensureInitialized();
|
|
309
|
+
|
|
310
|
+
const result = await this.pool.query<{
|
|
311
|
+
id: string;
|
|
312
|
+
tenant_id: string;
|
|
313
|
+
name: string;
|
|
314
|
+
description: string | null;
|
|
315
|
+
graph_definition: any;
|
|
316
|
+
owner_user_id: string | null;
|
|
317
|
+
created_at: Date;
|
|
318
|
+
updated_at: Date;
|
|
319
|
+
}>(
|
|
320
|
+
`
|
|
321
|
+
SELECT id, tenant_id, name, description, graph_definition, owner_user_id, created_at, updated_at
|
|
322
|
+
FROM lattice_assistants
|
|
323
|
+
WHERE tenant_id = $1 AND owner_user_id = $2
|
|
324
|
+
LIMIT 1
|
|
325
|
+
`,
|
|
326
|
+
[tenantId, userId]
|
|
327
|
+
);
|
|
328
|
+
|
|
329
|
+
if (result.rows.length === 0) return null;
|
|
330
|
+
return this.mapRowToAssistant(result.rows[0]);
|
|
331
|
+
}
|
|
332
|
+
|
|
292
333
|
/**
|
|
293
334
|
* Dispose resources and close the connection pool
|
|
294
335
|
* Should be called when the store is no longer needed
|
|
@@ -317,6 +358,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
317
358
|
name: string;
|
|
318
359
|
description: string | null;
|
|
319
360
|
graph_definition: any;
|
|
361
|
+
owner_user_id: string | null;
|
|
320
362
|
created_at: Date;
|
|
321
363
|
updated_at: Date;
|
|
322
364
|
}): Assistant {
|
|
@@ -329,6 +371,7 @@ export class PostgreSQLAssistantStore implements AssistantStore {
|
|
|
329
371
|
typeof row.graph_definition === "string"
|
|
330
372
|
? JSON.parse(row.graph_definition)
|
|
331
373
|
: row.graph_definition,
|
|
374
|
+
ownerUserId: row.owner_user_id || undefined,
|
|
332
375
|
createdAt: row.created_at,
|
|
333
376
|
updatedAt: row.updated_at,
|
|
334
377
|
};
|