@recombine-ai/platform 0.2.6-test.5 → 0.2.6-test.7

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.
@@ -9,21 +9,14 @@ export interface AnalyticsKey<T> {
9
9
  export type AnalyticsValue<TKey extends AnalyticsKey<unknown>> = TKey extends AnalyticsKey<infer TValue> ? TValue : never;
10
10
  export interface ConversationAnalyticsSummary {
11
11
  conversationId: string;
12
- agentId?: string;
13
12
  channel: string;
14
- region?: string;
15
13
  startedAt: Date;
16
- endedAt?: Date;
17
- durationSeconds?: number;
18
- }
19
- export interface ConversationAnalytics {
20
- set<TKey extends AnalyticsKey<unknown>>(key: TKey, value: AnalyticsValue<TKey>): Promise<void>;
21
- clear(key: AnalyticsKey<unknown>): Promise<void>;
22
- get<TKey extends AnalyticsKey<unknown>>(key: TKey): Promise<AnalyticsValue<TKey> | undefined>;
23
- getSummary(): Promise<ConversationAnalyticsSummary | undefined>;
24
14
  }
25
15
  export interface Analytics {
26
- getConversationAnalytics(conversationId: string): ConversationAnalytics;
16
+ set<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey, value: AnalyticsValue<TKey>): Promise<void>;
17
+ clear(conversationId: string, key: AnalyticsKey<unknown>): Promise<void>;
18
+ get<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey): Promise<AnalyticsValue<TKey> | undefined>;
19
+ getSummary(conversationId: string): Promise<ConversationAnalyticsSummary | undefined>;
27
20
  }
28
21
  export declare const analyticsKey: {
29
22
  boolean(key: string): AnalyticsKey<boolean>;
@@ -1 +1 @@
1
- {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,MAAM,kBAAkB,GACxB,OAAO,GACP,MAAM,GACN,MAAM,GACN,IAAI,GACJ,kBAAkB,EAAE,GACpB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;CAAE,CAAA;AAI3C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;CAChC;AAID,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,IACzD,IAAI,SAAS,YAAY,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAA;AAI5D,MAAM,WAAW,4BAA4B;IACzC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID,MAAM,WAAW,qBAAqB;IAClC,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9F,KAAK,CAAC,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IAChD,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAAE,GAAG,EAAE,IAAI,GAAG,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IAC7F,UAAU,IAAI,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAAA;CAClE;AAID,MAAM,WAAW,SAAS;IACtB,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB,CAAA;CAC1E;AAgBD,eAAO,MAAM,YAAY;iBACR,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC;gBAG/B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;gBAG7B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;eAG9B,OAAO,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,OAChD,MAAM,UACH,OAAO,GAChB,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SAK3B,CAAC,SAAS,kBAAkB,OAAO,MAAM,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;CAKzF,CAAA;AAID,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,4BAA4B,EAAO,GAAG,SAAS,CAkDjG;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CA0B5F"}
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../src/analytics.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAA;AAIvB,MAAM,MAAM,kBAAkB,GACxB,OAAO,GACP,MAAM,GACN,MAAM,GACN,IAAI,GACJ,kBAAkB,EAAE,GACpB;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,kBAAkB,CAAA;CAAE,CAAA;AAI3C,MAAM,WAAW,YAAY,CAAC,CAAC;IAC3B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,MAAM,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAA;CAChC;AAID,MAAM,MAAM,cAAc,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,IACzD,IAAI,SAAS,YAAY,CAAC,MAAM,MAAM,CAAC,GAAG,MAAM,GAAG,KAAK,CAAA;AAI5D,MAAM,WAAW,4BAA4B;IACzC,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;CAClB;AAID,MAAM,WAAW,SAAS;IAGtB,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAClC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC,CAAA;IAChB,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC,CAAA;IACxE,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EAClC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,GACV,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC,CAAA;IAC5C,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC,CAAA;CACxF;AAgBD,eAAO,MAAM,YAAY;iBACR,MAAM,GAAG,YAAY,CAAC,OAAO,CAAC;gBAG/B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;gBAG7B,MAAM,GAAG,YAAY,CAAC,MAAM,CAAC;eAG9B,OAAO,SAAS,SAAS,CAAC,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC,OAChD,MAAM,UACH,OAAO,GAChB,YAAY,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;SAK3B,CAAC,SAAS,kBAAkB,OAAO,MAAM,UAAU,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,GAAG,YAAY,CAAC,CAAC,CAAC;CAKzF,CAAA;AAID,wBAAgB,uBAAuB,CAAC,SAAS,GAAE,4BAA4B,EAAO,GAAG,SAAS,CA8CjG;AAID,wBAAgB,wBAAwB,CAAC,KAAK,EAAE,OAAO,GAAG,OAAO,CAAC,KAAK,IAAI,kBAAkB,CA0B5F"}
@@ -31,35 +31,31 @@ function createInMemoryAnalytics(summaries = []) {
31
31
  const conversations = new Map();
32
32
  const summariesById = new Map(summaries.map((summary) => [summary.conversationId, structuredClone(summary)]));
33
33
  return {
34
- getConversationAnalytics(conversationId) {
35
- return {
36
- async set(key, value) {
37
- const parsed = key.schema.parse(value);
38
- assertAnalyticsJsonValue(parsed);
39
- const values = conversations.get(conversationId) ?? new Map();
40
- values.set(key.key, structuredClone(parsed));
41
- conversations.set(conversationId, values);
42
- },
43
- async clear(key) {
44
- const values = conversations.get(conversationId);
45
- values?.delete(key.key);
46
- if (values?.size === 0) {
47
- conversations.delete(conversationId);
48
- }
49
- },
50
- async get(key) {
51
- const values = conversations.get(conversationId);
52
- if (!values?.has(key.key)) {
53
- return undefined;
54
- }
55
- const value = values.get(key.key);
56
- return key.schema.parse(structuredClone(value));
57
- },
58
- async getSummary() {
59
- const summary = summariesById.get(conversationId);
60
- return summary ? structuredClone(summary) : undefined;
61
- },
62
- };
34
+ async set(conversationId, key, value) {
35
+ const parsed = key.schema.parse(value);
36
+ assertAnalyticsJsonValue(parsed);
37
+ const values = conversations.get(conversationId) ?? new Map();
38
+ values.set(key.key, structuredClone(parsed));
39
+ conversations.set(conversationId, values);
40
+ },
41
+ async clear(conversationId, key) {
42
+ const values = conversations.get(conversationId);
43
+ values?.delete(key.key);
44
+ if (values?.size === 0) {
45
+ conversations.delete(conversationId);
46
+ }
47
+ },
48
+ async get(conversationId, key) {
49
+ const values = conversations.get(conversationId);
50
+ if (!values?.has(key.key)) {
51
+ return undefined;
52
+ }
53
+ const value = values.get(key.key);
54
+ return key.schema.parse(structuredClone(value));
55
+ },
56
+ async getSummary(conversationId) {
57
+ const summary = summariesById.get(conversationId);
58
+ return summary ? structuredClone(summary) : undefined;
63
59
  },
64
60
  };
65
61
  }
@@ -1,5 +1,5 @@
1
1
  import { Pool, PoolConfig, QueryResult } from 'pg';
2
- import { Analytics, ConversationAnalytics } from './analytics';
2
+ import { Analytics, AnalyticsKey, AnalyticsValue, ConversationAnalyticsSummary } from './analytics';
3
3
  import { PostgresAnalyticsQueryService } from './postgres-analytics-query';
4
4
  export interface ConversationRecord {
5
5
  conversationId: string;
@@ -11,7 +11,6 @@ export interface ConversationRecord {
11
11
  durationSeconds?: number;
12
12
  }
13
13
  export interface PhoneCallRecord {
14
- conversationId: string;
15
14
  phoneNumber?: string;
16
15
  recordingUrl?: string;
17
16
  endedReason?: string;
@@ -22,7 +21,10 @@ interface Queryable {
22
21
  export declare class PostgresAnalytics implements Analytics {
23
22
  private readonly database;
24
23
  constructor(database: Queryable);
25
- getConversationAnalytics(conversationId: string): ConversationAnalytics;
24
+ set<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey, value: AnalyticsValue<TKey>): Promise<void>;
25
+ clear(conversationId: string, key: AnalyticsKey<unknown>): Promise<void>;
26
+ get<TKey extends AnalyticsKey<unknown>>(conversationId: string, key: TKey): Promise<AnalyticsValue<TKey> | undefined>;
27
+ getSummary(conversationId: string): Promise<ConversationAnalyticsSummary | undefined>;
26
28
  }
27
29
  export declare class PostgresConversationRepository {
28
30
  private readonly pool;
@@ -1 +1 @@
1
- {"version":3,"file":"postgres-analytics.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAE9D,OAAO,EACH,SAAS,EAGT,qBAAqB,EAGxB,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAI1E,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID,MAAM,WAAW,eAAe;IAC5B,cAAc,EAAE,MAAM,CAAA;IACtB,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAID,UAAU,SAAS;IACf,KAAK,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CACnC;AAgBD,qBAAa,iBAAkB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAIhD,wBAAwB,CAAC,cAAc,EAAE,MAAM,GAAG,qBAAqB;CAqF1E;AAYD,qBAAa,8BAA8B;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,gBAAgB,CAClB,YAAY,EAAE,kBAAkB,EAChC,SAAS,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,IAAI,CAAC;YAmBF,kBAAkB;YAoClB,eAAe;CAsBhC;AA4DD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CA2CxE;AAID,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,8BAA8B,CAAA;IAC7C,OAAO,EAAE,6BAA6B,CAAA;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAID,wBAAsB,+BAA+B,CACjD,MAAM,EAAE,UAAU,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAgBpC"}
1
+ {"version":3,"file":"postgres-analytics.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAc,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAE9D,OAAO,EACH,SAAS,EACT,YAAY,EACZ,cAAc,EACd,4BAA4B,EAE/B,MAAM,aAAa,CAAA;AACpB,OAAO,EAAE,6BAA6B,EAAE,MAAM,4BAA4B,CAAA;AAI1E,MAAM,WAAW,kBAAkB;IAC/B,cAAc,EAAE,MAAM,CAAA;IACtB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,OAAO,EAAE,MAAM,CAAA;IACf,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,IAAI,CAAA;IACf,OAAO,CAAC,EAAE,IAAI,CAAA;IACd,eAAe,CAAC,EAAE,MAAM,CAAA;CAC3B;AAID,MAAM,WAAW,eAAe;IAC5B,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,YAAY,CAAC,EAAE,MAAM,CAAA;IACrB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAID,UAAU,SAAS;IACf,KAAK,CAAC,OAAO,SAAS,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EACnE,IAAI,EAAE,MAAM,EACZ,MAAM,CAAC,EAAE,OAAO,EAAE,GACnB,OAAO,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC,CAAA;CACnC;AAYD,qBAAa,iBAAkB,YAAW,SAAS;IACnC,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAE1C,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EACxC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,EACT,KAAK,EAAE,cAAc,CAAC,IAAI,CAAC,GAC5B,OAAO,CAAC,IAAI,CAAC;IAoBV,KAAK,CAAC,cAAc,EAAE,MAAM,EAAE,GAAG,EAAE,YAAY,CAAC,OAAO,CAAC,GAAG,OAAO,CAAC,IAAI,CAAC;IAaxE,GAAG,CAAC,IAAI,SAAS,YAAY,CAAC,OAAO,CAAC,EACxC,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,IAAI,GACV,OAAO,CAAC,cAAc,CAAC,IAAI,CAAC,GAAG,SAAS,CAAC;IAgBtC,UAAU,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,4BAA4B,GAAG,SAAS,CAAC;CAmB9F;AAYD,qBAAa,8BAA8B;IAC3B,OAAO,CAAC,QAAQ,CAAC,IAAI;gBAAJ,IAAI,EAAE,IAAI;IAEjC,gBAAgB,CAClB,YAAY,EAAE,kBAAkB,EAChC,SAAS,CAAC,EAAE,eAAe,GAC5B,OAAO,CAAC,IAAI,CAAC;YAmBF,kBAAkB;YAqClB,eAAe;CA0BhC;AA4DD,wBAAsB,wBAAwB,CAAC,IAAI,EAAE,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC,CAgDxE;AA6BD,MAAM,WAAW,yBAAyB;IACtC,SAAS,EAAE,iBAAiB,CAAA;IAC5B,aAAa,EAAE,8BAA8B,CAAA;IAC7C,OAAO,EAAE,6BAA6B,CAAA;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAID,wBAAsB,+BAA+B,CACjD,MAAM,EAAE,UAAU,GACnB,OAAO,CAAC,yBAAyB,CAAC,CAgBpC"}
@@ -11,63 +11,48 @@ class PostgresAnalytics {
11
11
  constructor(database) {
12
12
  this.database = database;
13
13
  }
14
- getConversationAnalytics(conversationId) {
15
- const database = this.database;
14
+ async set(conversationId, key, value) {
15
+ const parsed = key.schema.parse(value);
16
+ (0, analytics_1.assertAnalyticsJsonValue)(parsed);
17
+ const serialized = JSON.stringify(parsed);
18
+ if (serialized === undefined) {
19
+ throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
20
+ }
21
+ const result = await this.database.query(`UPDATE conversations
22
+ SET analytics = jsonb_set(analytics, ARRAY[$2::text], $3::jsonb, true),
23
+ updated_at = now()
24
+ WHERE conversation_id = $1`, [conversationId, key.key, serialized]);
25
+ assertConversationExists(result.rowCount, conversationId);
26
+ }
27
+ async clear(conversationId, key) {
28
+ const result = await this.database.query(`UPDATE conversations
29
+ SET analytics = analytics - $2::text,
30
+ updated_at = now()
31
+ WHERE conversation_id = $1`, [conversationId, key.key]);
32
+ assertConversationExists(result.rowCount, conversationId);
33
+ }
34
+ async get(conversationId, key) {
35
+ const result = await this.database.query(`SELECT analytics ? $2::text AS present,
36
+ analytics -> $2::text AS value
37
+ FROM conversations
38
+ WHERE conversation_id = $1`, [conversationId, key.key]);
39
+ if (result.rows.length === 0 || !result.rows[0].present) {
40
+ return undefined;
41
+ }
42
+ return key.schema.parse(result.rows[0].value);
43
+ }
44
+ async getSummary(conversationId) {
45
+ const result = await this.database.query(`SELECT conversation_id, channel, started_at
46
+ FROM conversations
47
+ WHERE conversation_id = $1`, [conversationId]);
48
+ const row = result.rows[0];
49
+ if (!row) {
50
+ return undefined;
51
+ }
16
52
  return {
17
- async set(key, value) {
18
- const parsed = key.schema.parse(value);
19
- (0, analytics_1.assertAnalyticsJsonValue)(parsed);
20
- const serialized = JSON.stringify(parsed);
21
- if (serialized === undefined) {
22
- throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
23
- }
24
- const result = await database.query(`UPDATE conversations
25
- SET analytics = jsonb_set(analytics, ARRAY[$2::text], $3::jsonb, true),
26
- updated_at = now()
27
- WHERE conversation_id = $1`, [conversationId, key.key, serialized]);
28
- assertConversationExists(result.rowCount, conversationId);
29
- },
30
- async clear(key) {
31
- const result = await database.query(`UPDATE conversations
32
- SET analytics = analytics - $2::text,
33
- updated_at = now()
34
- WHERE conversation_id = $1`, [conversationId, key.key]);
35
- assertConversationExists(result.rowCount, conversationId);
36
- },
37
- async get(key) {
38
- const result = await database.query(`SELECT analytics ? $2::text AS present,
39
- analytics -> $2::text AS value
40
- FROM conversations
41
- WHERE conversation_id = $1`, [conversationId, key.key]);
42
- if (result.rows.length === 0 || !result.rows[0].present) {
43
- return undefined;
44
- }
45
- return key.schema.parse(result.rows[0].value);
46
- },
47
- async getSummary() {
48
- const result = await database.query(`SELECT conversation_id,
49
- agent_id,
50
- channel,
51
- region,
52
- started_at,
53
- ended_at,
54
- duration_seconds
55
- FROM conversations
56
- WHERE conversation_id = $1`, [conversationId]);
57
- const row = result.rows[0];
58
- if (!row) {
59
- return undefined;
60
- }
61
- return {
62
- conversationId: row.conversation_id,
63
- agentId: row.agent_id ?? undefined,
64
- channel: row.channel,
65
- region: row.region ?? undefined,
66
- startedAt: row.started_at,
67
- endedAt: row.ended_at ?? undefined,
68
- durationSeconds: row.duration_seconds ?? undefined,
69
- };
70
- },
53
+ conversationId: row.conversation_id,
54
+ channel: row.channel,
55
+ startedAt: row.started_at,
71
56
  };
72
57
  }
73
58
  }
@@ -88,7 +73,7 @@ class PostgresConversationRepository {
88
73
  await client.query('BEGIN');
89
74
  await this.upsertConversation(client, conversation);
90
75
  if (phoneCall) {
91
- await this.upsertPhoneCall(client, phoneCall);
76
+ await this.upsertPhoneCall(client, conversation.conversationId, phoneCall);
92
77
  }
93
78
  await client.query('COMMIT');
94
79
  }
@@ -111,12 +96,13 @@ class PostgresConversationRepository {
111
96
  duration_seconds
112
97
  ) VALUES ($1, $2, $3, $4, $5, $6, $7)
113
98
  ON CONFLICT (conversation_id) DO UPDATE SET
114
- agent_id = EXCLUDED.agent_id,
115
- channel = EXCLUDED.channel,
116
- region = EXCLUDED.region,
117
- started_at = EXCLUDED.started_at,
118
- ended_at = EXCLUDED.ended_at,
119
- duration_seconds = EXCLUDED.duration_seconds,
99
+ agent_id = COALESCE(conversations.agent_id, EXCLUDED.agent_id),
100
+ region = COALESCE(conversations.region, EXCLUDED.region),
101
+ ended_at = COALESCE(conversations.ended_at, EXCLUDED.ended_at),
102
+ duration_seconds = COALESCE(
103
+ conversations.duration_seconds,
104
+ EXCLUDED.duration_seconds
105
+ ),
120
106
  updated_at = now()`, [
121
107
  conversation.conversationId,
122
108
  conversation.agentId ?? null,
@@ -127,7 +113,7 @@ class PostgresConversationRepository {
127
113
  conversation.durationSeconds ?? null,
128
114
  ]);
129
115
  }
130
- async upsertPhoneCall(client, phoneCall) {
116
+ async upsertPhoneCall(client, conversationId, phoneCall) {
131
117
  await client.query(`INSERT INTO phone_calls (
132
118
  conversation_id,
133
119
  phone_number,
@@ -135,10 +121,10 @@ class PostgresConversationRepository {
135
121
  ended_reason
136
122
  ) VALUES ($1, $2, $3, $4)
137
123
  ON CONFLICT (conversation_id) DO UPDATE SET
138
- phone_number = EXCLUDED.phone_number,
139
- recording_url = EXCLUDED.recording_url,
140
- ended_reason = EXCLUDED.ended_reason`, [
141
- phoneCall.conversationId,
124
+ phone_number = COALESCE(phone_calls.phone_number, EXCLUDED.phone_number),
125
+ recording_url = COALESCE(phone_calls.recording_url, EXCLUDED.recording_url),
126
+ ended_reason = COALESCE(phone_calls.ended_reason, EXCLUDED.ended_reason)`, [
127
+ conversationId,
142
128
  phoneCall.phoneNumber ?? null,
143
129
  phoneCall.recordingUrl ?? null,
144
130
  phoneCall.endedReason ?? null,
@@ -201,8 +187,11 @@ const migrations = [
201
187
  ];
202
188
  async function migrateAnalyticsDatabase(pool) {
203
189
  const client = await pool.connect();
190
+ let lockAcquired = false;
191
+ let migrationFailed = false;
204
192
  try {
205
193
  await client.query("SELECT pg_advisory_lock(hashtext('recombine_analytics_migrations'))");
194
+ lockAcquired = true;
206
195
  await client.query(`
207
196
  CREATE TABLE IF NOT EXISTS analytics_schema_migrations (
208
197
  version integer PRIMARY KEY,
@@ -228,9 +217,29 @@ async function migrateAnalyticsDatabase(pool) {
228
217
  }
229
218
  }
230
219
  }
220
+ catch (error) {
221
+ migrationFailed = true;
222
+ throw error;
223
+ }
224
+ finally {
225
+ await releaseAnalyticsMigrationClient(client, lockAcquired, migrationFailed);
226
+ }
227
+ }
228
+ async function releaseAnalyticsMigrationClient(client, lockAcquired, migrationFailed) {
229
+ let discardClient = false;
230
+ try {
231
+ if (lockAcquired) {
232
+ await client.query("SELECT pg_advisory_unlock(hashtext('recombine_analytics_migrations'))");
233
+ }
234
+ }
235
+ catch (error) {
236
+ discardClient = true;
237
+ if (!migrationFailed) {
238
+ throw error;
239
+ }
240
+ }
231
241
  finally {
232
- await client.query("SELECT pg_advisory_unlock(hashtext('recombine_analytics_migrations'))");
233
- client.release();
242
+ client.release(discardClient);
234
243
  }
235
244
  }
236
245
  async function createPostgresAnalyticsServices(config) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@recombine-ai/platform",
3
- "version": "0.2.6-test.5",
3
+ "version": "0.2.6-test.7",
4
4
  "description": "Recombine AI: voice agent platform",
5
5
  "license": "EULA",
6
6
  "author": "recombine.ai",