@axiom-lattice/pg-stores 3.1.10 → 3.1.11
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 +10 -0
- package/dist/index.d.mts +17 -1
- package/dist/index.d.ts +17 -1
- package/dist/index.js +88 -5
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +88 -5
- package/dist/index.mjs.map +1 -1
- package/package.json +3 -3
- package/src/__tests__/PostgreSQLTaskWorkItemStore.test.ts +13 -0
- package/src/createPgStoreConfig.ts +4 -0
- package/src/migrations/user_push_subscription_migration.ts +30 -0
- package/src/stores/PostgreSQLTaskWorkItemStore.ts +5 -5
- package/src/stores/PostgreSQLUserPushSubscriptionStore.ts +61 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@axiom-lattice/pg-stores",
|
|
3
|
-
"version": "3.1.
|
|
3
|
+
"version": "3.1.11",
|
|
4
4
|
"description": "PG stores implementation for Axiom Lattice framework",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"module": "dist/index.mjs",
|
|
@@ -25,8 +25,8 @@
|
|
|
25
25
|
"@langchain/core": "1.1.30",
|
|
26
26
|
"pg": "^8.16.3",
|
|
27
27
|
"uuid": "^9.0.1",
|
|
28
|
-
"@axiom-lattice/core": "4.2.
|
|
29
|
-
"@axiom-lattice/protocols": "4.2.
|
|
28
|
+
"@axiom-lattice/core": "4.2.11",
|
|
29
|
+
"@axiom-lattice/protocols": "4.2.2"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
32
|
"@types/jest": "^29.5.14",
|
|
@@ -82,6 +82,19 @@ describe("PostgreSQLTaskWorkItemStore", () => {
|
|
|
82
82
|
expect(result[0].eventKey).toBeUndefined();
|
|
83
83
|
});
|
|
84
84
|
|
|
85
|
+
it("maps null optional columns to undefined for strict projection snapshots", async () => {
|
|
86
|
+
const query = jest.fn().mockResolvedValue({ rows: [row] });
|
|
87
|
+
const store = new PostgreSQLTaskWorkItemStore({ query } as unknown as Pool);
|
|
88
|
+
|
|
89
|
+
const result = await store.list({ tenantId: "tenant-1", taskId: "task-1" });
|
|
90
|
+
|
|
91
|
+
expect(result[0].threadId).toBeUndefined();
|
|
92
|
+
expect(result[0].attempt).toBeUndefined();
|
|
93
|
+
expect(result[0].workspaceId).toBeUndefined();
|
|
94
|
+
expect(result[0].projectId).toBeUndefined();
|
|
95
|
+
expect(result[0].detail).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
85
98
|
it.each([null, [], "legacy", 1] as const)("maps non-object detail %p to undefined", async (detail) => {
|
|
86
99
|
const query = jest.fn().mockResolvedValue({ rows: [{ ...row, detail }] });
|
|
87
100
|
const store = new PostgreSQLTaskWorkItemStore({ query } as unknown as Pool);
|
|
@@ -87,12 +87,14 @@ import { createAgentWebAppsTable } from "./migrations/agent_web_apps_migration";
|
|
|
87
87
|
import { addAgentWebAppsIdentity } from "./migrations/add_agent_web_apps_identity_migration";
|
|
88
88
|
import { createCapabilityBundlesTable } from "./migrations/capability_bundle_migration";
|
|
89
89
|
import { createProjectRoomReadStates, createProjectRoomTables } from "./migrations/project_room_migration";
|
|
90
|
+
import { createUserPushSubscriptions } from "./migrations/user_push_subscription_migration";
|
|
90
91
|
import { PostgreSQLCapabilityBundleStore } from "./stores/PostgreSQLCapabilityBundleStore";
|
|
91
92
|
import { PostgreSQLProjectRoomStore } from "./stores/PostgreSQLProjectRoomStore";
|
|
92
93
|
import { PostgreSQLProjectMembershipStore } from "./stores/PostgreSQLProjectMembershipStore";
|
|
93
94
|
import { PostgreSQLProjectBotMembershipStore } from "./stores/PostgreSQLProjectBotMembershipStore";
|
|
94
95
|
import { PostgreSQLProjectRoomMessageStore } from "./stores/PostgreSQLProjectRoomMessageStore";
|
|
95
96
|
import { PostgreSQLProjectRoomReadStateStore } from "./stores/PostgreSQLProjectRoomReadStateStore";
|
|
97
|
+
import { PostgreSQLUserPushSubscriptionStore } from "./stores/PostgreSQLUserPushSubscriptionStore";
|
|
96
98
|
|
|
97
99
|
export async function createPgStoreConfig(connectionString: string) {
|
|
98
100
|
const pool = new Pool({ connectionString });
|
|
@@ -164,6 +166,7 @@ export async function createPgStoreConfig(connectionString: string) {
|
|
|
164
166
|
mm.register(addProjectLifecycleEventIndex); // v175
|
|
165
167
|
mm.register(addAgentWebAppsIdentity); // v176
|
|
166
168
|
mm.register(createProjectRoomReadStates); // v177
|
|
169
|
+
mm.register(createUserPushSubscriptions); // v178
|
|
167
170
|
|
|
168
171
|
await mm.migrate();
|
|
169
172
|
|
|
@@ -208,6 +211,7 @@ export async function createPgStoreConfig(connectionString: string) {
|
|
|
208
211
|
projectBotMembership: new PostgreSQLProjectBotMembershipStore(opts),
|
|
209
212
|
projectRoomMessage: new PostgreSQLProjectRoomMessageStore(opts),
|
|
210
213
|
projectRoomReadState: new PostgreSQLProjectRoomReadStateStore(opts),
|
|
214
|
+
userPushSubscription: new PostgreSQLUserPushSubscriptionStore(opts),
|
|
211
215
|
vectorStoreProvider: new PGVectorStoreProvider(pool, connectionString),
|
|
212
216
|
checkpoint,
|
|
213
217
|
};
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Migration } from "./migration";
|
|
2
|
+
|
|
3
|
+
/** Adds per-user Web Push subscriptions for PWA notifications. */
|
|
4
|
+
export const createUserPushSubscriptions: Migration = {
|
|
5
|
+
version: 178,
|
|
6
|
+
name: "create_user_push_subscriptions",
|
|
7
|
+
up: async (client) => {
|
|
8
|
+
await client.query(`
|
|
9
|
+
CREATE TABLE IF NOT EXISTS lattice_user_push_subscriptions (
|
|
10
|
+
id VARCHAR(255) PRIMARY KEY,
|
|
11
|
+
tenant_id VARCHAR(255) NOT NULL,
|
|
12
|
+
user_id VARCHAR(255) NOT NULL,
|
|
13
|
+
endpoint TEXT NOT NULL,
|
|
14
|
+
p256dh TEXT NOT NULL,
|
|
15
|
+
auth TEXT NOT NULL,
|
|
16
|
+
user_agent TEXT,
|
|
17
|
+
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
|
18
|
+
UNIQUE (tenant_id, user_id, endpoint)
|
|
19
|
+
)
|
|
20
|
+
`);
|
|
21
|
+
await client.query(`
|
|
22
|
+
CREATE INDEX IF NOT EXISTS idx_lattice_user_push_subscriptions_user
|
|
23
|
+
ON lattice_user_push_subscriptions (tenant_id, user_id)
|
|
24
|
+
`);
|
|
25
|
+
},
|
|
26
|
+
down: async (client) => {
|
|
27
|
+
await client.query("DROP INDEX IF EXISTS idx_lattice_user_push_subscriptions_user");
|
|
28
|
+
await client.query("DROP TABLE IF EXISTS lattice_user_push_subscriptions");
|
|
29
|
+
},
|
|
30
|
+
};
|
|
@@ -284,12 +284,12 @@ export class PostgreSQLTaskWorkItemStore implements TaskWorkItemStore {
|
|
|
284
284
|
tenantId: row.tenant_id as string,
|
|
285
285
|
action: row.action as string,
|
|
286
286
|
actor: row.actor as string,
|
|
287
|
-
threadId: row.thread_id
|
|
288
|
-
summary: row.summary
|
|
287
|
+
threadId: row.thread_id == null ? undefined : row.thread_id as string,
|
|
288
|
+
summary: row.summary == null ? undefined : row.summary as string,
|
|
289
289
|
detail: isRecord(row.detail) ? row.detail : undefined,
|
|
290
|
-
attempt: row.attempt
|
|
291
|
-
workspaceId: row.workspace_id
|
|
292
|
-
projectId: row.project_id
|
|
290
|
+
attempt: row.attempt == null ? undefined : row.attempt as number,
|
|
291
|
+
workspaceId: row.workspace_id == null ? undefined : row.workspace_id as string,
|
|
292
|
+
projectId: row.project_id == null ? undefined : row.project_id as string,
|
|
293
293
|
eventKey: row.event_key == null ? undefined : row.event_key as string,
|
|
294
294
|
createdAt: new Date(row.created_at as string),
|
|
295
295
|
};
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import type { UserPushSubscription, UserPushSubscriptionStore } from "@axiom-lattice/protocols";
|
|
3
|
+
import type { Pool } from "pg";
|
|
4
|
+
|
|
5
|
+
type Row = {
|
|
6
|
+
id: unknown; tenant_id: unknown; user_id: unknown; endpoint: unknown;
|
|
7
|
+
p256dh: unknown; auth: unknown; user_agent: unknown; created_at: unknown;
|
|
8
|
+
};
|
|
9
|
+
const columns = "id, tenant_id, user_id, endpoint, p256dh, auth, user_agent, created_at";
|
|
10
|
+
|
|
11
|
+
function mapRow(row: Row): UserPushSubscription {
|
|
12
|
+
if (typeof row.id !== "string" || typeof row.tenant_id !== "string" || typeof row.user_id !== "string"
|
|
13
|
+
|| typeof row.endpoint !== "string" || typeof row.p256dh !== "string" || typeof row.auth !== "string"
|
|
14
|
+
|| !(row.created_at instanceof Date) || Number.isNaN(row.created_at.getTime())) {
|
|
15
|
+
throw new Error("Invalid user push subscription row");
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
id: row.id, tenantId: row.tenant_id, userId: row.user_id, endpoint: row.endpoint,
|
|
19
|
+
keys: { p256dh: row.p256dh, auth: row.auth },
|
|
20
|
+
...(typeof row.user_agent === "string" ? { userAgent: row.user_agent } : {}),
|
|
21
|
+
createdAt: row.created_at.toISOString(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/** Persists per-user Web Push subscriptions in PostgreSQL. */
|
|
26
|
+
export class PostgreSQLUserPushSubscriptionStore implements UserPushSubscriptionStore {
|
|
27
|
+
/** Creates a store using an externally managed shared pool. */
|
|
28
|
+
constructor(options: { pool: Pool }) { this.pool = options.pool; }
|
|
29
|
+
private readonly pool: Pool;
|
|
30
|
+
|
|
31
|
+
/** Lists every subscription for a user. */
|
|
32
|
+
async list(tenantId: string, userId: string): Promise<UserPushSubscription[]> {
|
|
33
|
+
const result = await this.pool.query<Row>(
|
|
34
|
+
`SELECT ${columns} FROM lattice_user_push_subscriptions WHERE tenant_id = $1 AND user_id = $2`,
|
|
35
|
+
[tenantId, userId],
|
|
36
|
+
);
|
|
37
|
+
return result.rows.map(mapRow);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** Upserts a subscription keyed by tenant + user + endpoint. */
|
|
41
|
+
async save(input: Omit<UserPushSubscription, "id" | "createdAt">): Promise<UserPushSubscription> {
|
|
42
|
+
const result = await this.pool.query<Row>(
|
|
43
|
+
`INSERT INTO lattice_user_push_subscriptions (${columns})
|
|
44
|
+
VALUES ($1, $2, $3, $4, $5, $6, $7, NOW())
|
|
45
|
+
ON CONFLICT (tenant_id, user_id, endpoint) DO UPDATE
|
|
46
|
+
SET p256dh = EXCLUDED.p256dh, auth = EXCLUDED.auth, user_agent = EXCLUDED.user_agent
|
|
47
|
+
RETURNING ${columns}`,
|
|
48
|
+
[randomUUID(), input.tenantId, input.userId, input.endpoint, input.keys.p256dh, input.keys.auth, input.userAgent ?? null],
|
|
49
|
+
);
|
|
50
|
+
return mapRow(result.rows[0]);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/** Deletes one subscription by endpoint. */
|
|
54
|
+
async delete(tenantId: string, userId: string, endpoint: string): Promise<boolean> {
|
|
55
|
+
const result = await this.pool.query(
|
|
56
|
+
`DELETE FROM lattice_user_push_subscriptions WHERE tenant_id = $1 AND user_id = $2 AND endpoint = $3`,
|
|
57
|
+
[tenantId, userId, endpoint],
|
|
58
|
+
);
|
|
59
|
+
return (result.rowCount ?? 0) > 0;
|
|
60
|
+
}
|
|
61
|
+
}
|