@recombine-ai/platform 0.2.6-test.4 → 0.2.6-test.6
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/build/analytics.js +13 -8
- package/build/context-cache.js +6 -2
- package/build/index.d.ts +7 -7
- package/build/index.d.ts.map +1 -1
- package/build/index.js +23 -7
- package/build/interfaces/platform.d.ts +4 -4
- package/build/interfaces/platform.d.ts.map +1 -1
- package/build/interfaces/platform.js +4 -1
- package/build/interfaces/rc-voice.js +2 -1
- package/build/postgres-analytics-query.d.ts +1 -1
- package/build/postgres-analytics-query.d.ts.map +1 -1
- package/build/postgres-analytics-query.js +11 -5
- package/build/postgres-analytics.d.ts +2 -3
- package/build/postgres-analytics.d.ts.map +1 -1
- package/build/postgres-analytics.js +55 -24
- package/build/timeouts-manager.d.ts +1 -1
- package/build/timeouts-manager.d.ts.map +1 -1
- package/build/timeouts-manager.js +5 -1
- package/package.json +3 -10
- package/build/analytics.spec.d.ts +0 -2
- package/build/analytics.spec.d.ts.map +0 -1
- package/build/analytics.spec.js +0 -57
- package/build/postgres-analytics-query.spec.d.ts +0 -2
- package/build/postgres-analytics-query.spec.d.ts.map +0 -1
- package/build/postgres-analytics-query.spec.js +0 -223
- package/build/postgres-analytics.spec.d.ts +0 -2
- package/build/postgres-analytics.spec.d.ts.map +0 -1
- package/build/postgres-analytics.spec.js +0 -61
package/build/analytics.js
CHANGED
|
@@ -1,28 +1,33 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.analyticsKey = void 0;
|
|
4
|
+
exports.createInMemoryAnalytics = createInMemoryAnalytics;
|
|
5
|
+
exports.assertAnalyticsJsonValue = assertAnalyticsJsonValue;
|
|
6
|
+
const zod_1 = require("zod");
|
|
2
7
|
function defineKey(key, schema) {
|
|
3
8
|
if (!/^[A-Za-z0-9_.:-]{1,128}$/.test(key)) {
|
|
4
9
|
throw new Error('Analytics key contains unsupported characters');
|
|
5
10
|
}
|
|
6
11
|
return Object.freeze({ key, schema });
|
|
7
12
|
}
|
|
8
|
-
|
|
13
|
+
exports.analyticsKey = {
|
|
9
14
|
boolean(key) {
|
|
10
|
-
return defineKey(key, z.boolean());
|
|
15
|
+
return defineKey(key, zod_1.z.boolean());
|
|
11
16
|
},
|
|
12
17
|
number(key) {
|
|
13
|
-
return defineKey(key, z.number().finite());
|
|
18
|
+
return defineKey(key, zod_1.z.number().finite());
|
|
14
19
|
},
|
|
15
20
|
string(key) {
|
|
16
|
-
return defineKey(key, z.string());
|
|
21
|
+
return defineKey(key, zod_1.z.string());
|
|
17
22
|
},
|
|
18
23
|
enum(key, values) {
|
|
19
|
-
return defineKey(key, z.enum(values));
|
|
24
|
+
return defineKey(key, zod_1.z.enum(values));
|
|
20
25
|
},
|
|
21
26
|
json(key, schema) {
|
|
22
27
|
return defineKey(key, schema);
|
|
23
28
|
},
|
|
24
29
|
};
|
|
25
|
-
|
|
30
|
+
function createInMemoryAnalytics(summaries = []) {
|
|
26
31
|
const conversations = new Map();
|
|
27
32
|
const summariesById = new Map(summaries.map((summary) => [summary.conversationId, structuredClone(summary)]));
|
|
28
33
|
return {
|
|
@@ -58,7 +63,7 @@ export function createInMemoryAnalytics(summaries = []) {
|
|
|
58
63
|
},
|
|
59
64
|
};
|
|
60
65
|
}
|
|
61
|
-
|
|
66
|
+
function assertAnalyticsJsonValue(value) {
|
|
62
67
|
if (value === null ||
|
|
63
68
|
typeof value === 'boolean' ||
|
|
64
69
|
typeof value === 'string' ||
|
package/build/context-cache.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createInMemoryCache = createInMemoryCache;
|
|
4
|
+
exports.createRedisCache = createRedisCache;
|
|
5
|
+
function createInMemoryCache() {
|
|
2
6
|
const cache = new Map();
|
|
3
7
|
async function getContext(callId, defaultContext) {
|
|
4
8
|
if (cache.has(callId)) {
|
|
@@ -21,7 +25,7 @@ export function createInMemoryCache() {
|
|
|
21
25
|
},
|
|
22
26
|
};
|
|
23
27
|
}
|
|
24
|
-
|
|
28
|
+
function createRedisCache(client, options = DEFAULT_REDIS_CACHE_OPTIONS) {
|
|
25
29
|
function createKey(callId) {
|
|
26
30
|
return options.keyPrefix + callId;
|
|
27
31
|
}
|
package/build/index.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
export * from './interfaces/platform
|
|
2
|
-
export * from './interfaces/rc-voice
|
|
3
|
-
export * from './context-cache
|
|
4
|
-
export * from './analytics
|
|
5
|
-
export * from './postgres-analytics
|
|
6
|
-
export * from './postgres-analytics-query
|
|
7
|
-
export * from './timeouts-manager
|
|
1
|
+
export * from './interfaces/platform';
|
|
2
|
+
export * from './interfaces/rc-voice';
|
|
3
|
+
export * from './context-cache';
|
|
4
|
+
export * from './analytics';
|
|
5
|
+
export * from './postgres-analytics';
|
|
6
|
+
export * from './postgres-analytics-query';
|
|
7
|
+
export * from './timeouts-manager';
|
|
8
8
|
//# sourceMappingURL=index.d.ts.map
|
package/build/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,uBAAuB,CAAA;AACrC,cAAc,uBAAuB,CAAA;AACrC,cAAc,iBAAiB,CAAA;AAG/B,cAAc,aAAa,CAAA;AAC3B,cAAc,sBAAsB,CAAA;AACpC,cAAc,4BAA4B,CAAA;AAC1C,cAAc,oBAAoB,CAAA"}
|
package/build/index.js
CHANGED
|
@@ -1,7 +1,23 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
__exportStar(require("./interfaces/platform"), exports);
|
|
18
|
+
__exportStar(require("./interfaces/rc-voice"), exports);
|
|
19
|
+
__exportStar(require("./context-cache"), exports);
|
|
20
|
+
__exportStar(require("./analytics"), exports);
|
|
21
|
+
__exportStar(require("./postgres-analytics"), exports);
|
|
22
|
+
__exportStar(require("./postgres-analytics-query"), exports);
|
|
23
|
+
__exportStar(require("./timeouts-manager"), exports);
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { ReadableStream } from 'node:stream/web';
|
|
2
2
|
import { AIEngine, AIStreamEngine, LlmAdapter, LlmStreamAdapter, Logger, Message, PromptFS, ResponseChunk } from '@recombine-ai/engine';
|
|
3
|
-
import { Analytics } from '../analytics
|
|
4
|
-
import { ContextCache } from '../context-cache
|
|
5
|
-
import { TimeoutManager } from '../timeouts-manager
|
|
6
|
-
import { IRcVoice } from './rc-voice
|
|
3
|
+
import { Analytics } from '../analytics';
|
|
4
|
+
import { ContextCache } from '../context-cache';
|
|
5
|
+
import { TimeoutManager } from '../timeouts-manager';
|
|
6
|
+
import { IRcVoice } from './rc-voice';
|
|
7
7
|
export interface IModels {
|
|
8
8
|
models: Record<string, LlmAdapter>;
|
|
9
9
|
streamingModels: Record<string, LlmStreamAdapter>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/interfaces/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EACH,QAAQ,EACR,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,EAChB,MAAM,sBAAsB,CAAA;AAI7B,OAAO,EAAE,SAAS,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../../src/interfaces/platform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,iBAAiB,CAAA;AAEhD,OAAO,EACH,QAAQ,EACR,cAAc,EACd,UAAU,EACV,gBAAgB,EAChB,MAAM,EACN,OAAO,EACP,QAAQ,EACR,aAAa,EAChB,MAAM,sBAAsB,CAAA;AAI7B,OAAO,EAAE,SAAS,EAAE,MAAM,cAAc,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,kBAAkB,CAAA;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AACpD,OAAO,EAAE,QAAQ,EAAE,MAAM,YAAY,CAAA;AAErC,MAAM,WAAW,OAAO;IACpB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAA;IAClC,eAAe,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAA;CACpD;AAID,MAAM,WAAW,QAAQ,CACrB,QAAQ,SAAS,EAAE,EACnB,OAAO,SAAS,OAAO,EACvB,QAAQ,GAAG,QAAQ,EACnB,eAAe,GAAG,cAAc,EAChC,aAAa,GAAG,YAAY,CAAC,QAAQ,CAAC;IAGtC,QAAQ,EAAE,cAAc,CAAC,QAAQ,CAAC,CAAA;IAElC,EAAE,EAAE,QAAQ,CAAC,QAAQ,CAAC,CAAA;IAEtB,EAAE,EAAE,QAAQ,CAAA;IAEZ,OAAO,EAAE,QAAQ,CAAA;IACjB,MAAM,EAAE,MAAM,CAAA;IACd,cAAc,EAAE,eAAe,CAAA;IAE/B,MAAM,EAAE,OAAO,CAAC,QAAQ,CAAC,CAAA;IACzB,eAAe,EAAE,OAAO,CAAC,iBAAiB,CAAC,CAAA;IAE3C,YAAY,EAAE,aAAa,CAAA;IAK3B,SAAS,EAAE,SAAS,CAAA;CACvB;AAED,MAAM,WAAW,QAAQ;IACrB,MAAM,EAAE,MAAM,CAAA;IACd,WAAW,CAAC,EAAE,MAAM,CAAA;IAEpB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;CACnC;AAED,MAAM,WAAW,UAAU;IACvB,WAAW,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC;QACrC,cAAc,EAAE,MAAM,CAAA;QACtB,eAAe,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,CAAC,CAAA;KACpD,CAAC,CAAA;IACF,WAAW,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC9C,cAAc,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,cAAc,CAAC,aAAa,CAAC,CAAC,CAAA;IAC/F,SAAS,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IAC5C,iBAAiB,EAAE,CAAC,QAAQ,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,QAAQ,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;IACzE,gBAAgB,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,CAAA;IACrC,YAAY,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,GAAG,KAAK,OAAO,CAAC,IAAI,CAAC,CAAA;CAC5D;AAED,wBAAgB,WAAW,CAAC,QAAQ,SAAS,EAAE,EAAE,SAAS,EAAE,OAAO,SAAS,OAAO,EAC/E,OAAO,EAAE,CAAC,QAAQ,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,EAAE,QAAQ,EAAE,SAAS,KAAK,UAAU,cAA/D,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAC,YAAY,SAAS,KAAK,UAAU,CAGtF"}
|
|
@@ -1 +1,2 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import { AnalyticsJsonValue } from './analytics
|
|
2
|
+
import { AnalyticsJsonValue } from './analytics';
|
|
3
3
|
export type AnalyticsFilterScalar = boolean | number | string;
|
|
4
4
|
export type AnalyticsFilterValueType = 'boolean' | 'number' | 'string';
|
|
5
5
|
export type AnalyticsFilterOperator = 'eq' | 'exists' | 'gt' | 'gte' | 'in' | 'lt' | 'lte';
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"postgres-analytics-query.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,UAAU,EAAE,WAAW,EAAE,MAAM,IAAI,CAAA;AAElD,OAAO,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAIhD,MAAM,MAAM,qBAAqB,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAA;AAC7D,MAAM,MAAM,wBAAwB,GAAG,SAAS,GAAG,QAAQ,GAAG,QAAQ,CAAA;AACtE,MAAM,MAAM,uBAAuB,GAAG,IAAI,GAAG,QAAQ,GAAG,IAAI,GAAG,KAAK,GAAG,IAAI,GAAG,IAAI,GAAG,KAAK,CAAA;AAE1F,MAAM,WAAW,eAAe;IAC5B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,wBAAwB,CAAA;IAC9B,QAAQ,EAAE,uBAAuB,CAAA;IACjC,KAAK,CAAC,EAAE,qBAAqB,CAAA;IAC7B,MAAM,CAAC,EAAE,qBAAqB,EAAE,CAAA;CACnC;AAED,MAAM,MAAM,eAAe,GACrB;IAAE,SAAS,EAAE,OAAO,CAAA;CAAE,GACtB;IAAE,SAAS,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,eAAe,CAAA;CAAE,GAC9C;IAAE,SAAS,EAAE,SAAS,GAAG,KAAK,CAAC;IAAC,GAAG,EAAE,MAAM,CAAA;CAAE,CAAA;AAEnD,MAAM,MAAM,wBAAwB,GAAG,MAAM,GAAG,KAAK,GAAG,MAAM,GAAG,OAAO,CAAA;AACxE,MAAM,MAAM,wBAAwB,GAAG,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAAA;AAIxE,MAAM,WAAW,qBAAqB;IAClC,cAAc,EAAE,MAAM,CAAA;IACtB,MAAM,EAAE,MAAM,CAAA;IACd,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,WAAW,CAAC,EAAE,MAAM,CAAA;IACpB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAA;IACjB,SAAS,EAAE,MAAM,CAAA;IACjB,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAA;CAChD;AAED,MAAM,WAAW,yBAAyB;IACtC,KAAK,EAAE,MAAM,CAAA;IACb,KAAK,EAAE,qBAAqB,EAAE,CAAA;IAC9B,kBAAkB,CAAC,EAAE,MAAM,CAAA;CAC9B;AAID,MAAM,WAAW,mBAAmB;IAChC,KAAK,EAAE,qBAAqB,CAAA;IAC5B,KAAK,EAAE,MAAM,CAAA;CAChB;AAED,MAAM,WAAW,cAAc;IAC3B,GAAG,EAAE,MAAM,CAAA;IACX,IAAI,EAAE,wBAAwB,CAAA;IAC9B,YAAY,EAAE,MAAM,CAAA;IACpB,MAAM,EAAE,mBAAmB,EAAE,CAAA;CAChC;AAED,MAAM,WAAW,eAAe;IAC5B,KAAK,EAAE,cAAc,EAAE,CAAA;CAC1B;AAID,MAAM,WAAW,uBAAuB;IACpC,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,GAAG,IAAI,CAAA;IACpB,SAAS,CAAC,EAAE,MAAM,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACvB;AAED,MAAM,WAAW,mBAAmB;IAChC,MAAM,EAAE,eAAe,CAAA;IACvB,WAAW,EAAE,wBAAwB,CAAA;IACrC,IAAI,EAAE,MAAM,CAAA;IACZ,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,uBAAuB,EAAE,CAAA;CACnC;AAED,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;AA6CD,qBAAa,mBAAoB,SAAQ,KAAK;IACjC,IAAI,SAAwB;CACxC;AAQD,qBAAa,6BAA6B;IAC1B,OAAO,CAAC,QAAQ,CAAC,QAAQ;gBAAR,QAAQ,EAAE,SAAS;IAI1C,eAAe,CAAC,cAAc,EAAE,MAAM,GAAG,OAAO,CAAC,qBAAqB,GAAG,SAAS,CAAC;IAgBnF,gBAAgB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,yBAAyB,CAAC;IA2CrF,kBAAkB,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,eAAe,CAAC;IA4C7E,aAAa,CAAC,KAAK,EAAE,wBAAwB,GAAG,OAAO,CAAC,mBAAmB,CAAC;CAiErF;AAID,MAAM,WAAW,8BAA8B;IAC3C,OAAO,EAAE,6BAA6B,CAAA;IACtC,KAAK,IAAI,OAAO,CAAC,IAAI,CAAC,CAAA;CACzB;AAED,wBAAgB,oCAAoC,CAChD,MAAM,EAAE,UAAU,GACnB,8BAA8B,CAMhC"}
|
|
@@ -1,8 +1,13 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresAnalyticsQueryService = exports.AnalyticsQueryError = void 0;
|
|
4
|
+
exports.createPostgresAnalyticsQueryServices = createPostgresAnalyticsQueryServices;
|
|
5
|
+
const pg_1 = require("pg");
|
|
6
|
+
class AnalyticsQueryError extends Error {
|
|
3
7
|
name = 'AnalyticsQueryError';
|
|
4
8
|
}
|
|
5
|
-
|
|
9
|
+
exports.AnalyticsQueryError = AnalyticsQueryError;
|
|
10
|
+
class PostgresAnalyticsQueryService {
|
|
6
11
|
database;
|
|
7
12
|
constructor(database) {
|
|
8
13
|
this.database = database;
|
|
@@ -139,8 +144,9 @@ export class PostgresAnalyticsQueryService {
|
|
|
139
144
|
};
|
|
140
145
|
}
|
|
141
146
|
}
|
|
142
|
-
|
|
143
|
-
|
|
147
|
+
exports.PostgresAnalyticsQueryService = PostgresAnalyticsQueryService;
|
|
148
|
+
function createPostgresAnalyticsQueryServices(config) {
|
|
149
|
+
const pool = new pg_1.Pool(config);
|
|
144
150
|
return {
|
|
145
151
|
queries: new PostgresAnalyticsQueryService(pool),
|
|
146
152
|
close: () => pool.end(),
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { Pool, PoolConfig, QueryResult } from 'pg';
|
|
2
|
-
import { Analytics, ConversationAnalytics } from './analytics
|
|
3
|
-
import { PostgresAnalyticsQueryService } from './postgres-analytics-query
|
|
2
|
+
import { Analytics, ConversationAnalytics } from './analytics';
|
|
3
|
+
import { PostgresAnalyticsQueryService } from './postgres-analytics-query';
|
|
4
4
|
export interface ConversationRecord {
|
|
5
5
|
conversationId: string;
|
|
6
6
|
agentId?: 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;
|
|
@@ -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,
|
|
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,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;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"}
|
|
@@ -1,7 +1,12 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PostgresConversationRepository = exports.PostgresAnalytics = void 0;
|
|
4
|
+
exports.migrateAnalyticsDatabase = migrateAnalyticsDatabase;
|
|
5
|
+
exports.createPostgresAnalyticsServices = createPostgresAnalyticsServices;
|
|
6
|
+
const pg_1 = require("pg");
|
|
7
|
+
const analytics_1 = require("./analytics");
|
|
8
|
+
const postgres_analytics_query_1 = require("./postgres-analytics-query");
|
|
9
|
+
class PostgresAnalytics {
|
|
5
10
|
database;
|
|
6
11
|
constructor(database) {
|
|
7
12
|
this.database = database;
|
|
@@ -11,7 +16,7 @@ export class PostgresAnalytics {
|
|
|
11
16
|
return {
|
|
12
17
|
async set(key, value) {
|
|
13
18
|
const parsed = key.schema.parse(value);
|
|
14
|
-
assertAnalyticsJsonValue(parsed);
|
|
19
|
+
(0, analytics_1.assertAnalyticsJsonValue)(parsed);
|
|
15
20
|
const serialized = JSON.stringify(parsed);
|
|
16
21
|
if (serialized === undefined) {
|
|
17
22
|
throw new Error(`Analytics value for '${key.key}' is not valid JSON`);
|
|
@@ -66,12 +71,13 @@ export class PostgresAnalytics {
|
|
|
66
71
|
};
|
|
67
72
|
}
|
|
68
73
|
}
|
|
74
|
+
exports.PostgresAnalytics = PostgresAnalytics;
|
|
69
75
|
function assertConversationExists(rowCount, conversationId) {
|
|
70
76
|
if (rowCount !== 1) {
|
|
71
77
|
throw new Error(`Conversation '${conversationId}' does not exist`);
|
|
72
78
|
}
|
|
73
79
|
}
|
|
74
|
-
|
|
80
|
+
class PostgresConversationRepository {
|
|
75
81
|
pool;
|
|
76
82
|
constructor(pool) {
|
|
77
83
|
this.pool = pool;
|
|
@@ -82,7 +88,7 @@ export class PostgresConversationRepository {
|
|
|
82
88
|
await client.query('BEGIN');
|
|
83
89
|
await this.upsertConversation(client, conversation);
|
|
84
90
|
if (phoneCall) {
|
|
85
|
-
await this.upsertPhoneCall(client, phoneCall);
|
|
91
|
+
await this.upsertPhoneCall(client, conversation.conversationId, phoneCall);
|
|
86
92
|
}
|
|
87
93
|
await client.query('COMMIT');
|
|
88
94
|
}
|
|
@@ -105,12 +111,13 @@ export class PostgresConversationRepository {
|
|
|
105
111
|
duration_seconds
|
|
106
112
|
) VALUES ($1, $2, $3, $4, $5, $6, $7)
|
|
107
113
|
ON CONFLICT (conversation_id) DO UPDATE SET
|
|
108
|
-
agent_id = EXCLUDED.agent_id,
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
+
agent_id = COALESCE(conversations.agent_id, EXCLUDED.agent_id),
|
|
115
|
+
region = COALESCE(conversations.region, EXCLUDED.region),
|
|
116
|
+
ended_at = COALESCE(conversations.ended_at, EXCLUDED.ended_at),
|
|
117
|
+
duration_seconds = COALESCE(
|
|
118
|
+
conversations.duration_seconds,
|
|
119
|
+
EXCLUDED.duration_seconds
|
|
120
|
+
),
|
|
114
121
|
updated_at = now()`, [
|
|
115
122
|
conversation.conversationId,
|
|
116
123
|
conversation.agentId ?? null,
|
|
@@ -121,7 +128,7 @@ export class PostgresConversationRepository {
|
|
|
121
128
|
conversation.durationSeconds ?? null,
|
|
122
129
|
]);
|
|
123
130
|
}
|
|
124
|
-
async upsertPhoneCall(client, phoneCall) {
|
|
131
|
+
async upsertPhoneCall(client, conversationId, phoneCall) {
|
|
125
132
|
await client.query(`INSERT INTO phone_calls (
|
|
126
133
|
conversation_id,
|
|
127
134
|
phone_number,
|
|
@@ -129,16 +136,17 @@ export class PostgresConversationRepository {
|
|
|
129
136
|
ended_reason
|
|
130
137
|
) VALUES ($1, $2, $3, $4)
|
|
131
138
|
ON CONFLICT (conversation_id) DO UPDATE SET
|
|
132
|
-
phone_number = EXCLUDED.phone_number,
|
|
133
|
-
recording_url = EXCLUDED.recording_url,
|
|
134
|
-
ended_reason = EXCLUDED.ended_reason`, [
|
|
135
|
-
|
|
139
|
+
phone_number = COALESCE(phone_calls.phone_number, EXCLUDED.phone_number),
|
|
140
|
+
recording_url = COALESCE(phone_calls.recording_url, EXCLUDED.recording_url),
|
|
141
|
+
ended_reason = COALESCE(phone_calls.ended_reason, EXCLUDED.ended_reason)`, [
|
|
142
|
+
conversationId,
|
|
136
143
|
phoneCall.phoneNumber ?? null,
|
|
137
144
|
phoneCall.recordingUrl ?? null,
|
|
138
145
|
phoneCall.endedReason ?? null,
|
|
139
146
|
]);
|
|
140
147
|
}
|
|
141
148
|
}
|
|
149
|
+
exports.PostgresConversationRepository = PostgresConversationRepository;
|
|
142
150
|
const migrations = [
|
|
143
151
|
{
|
|
144
152
|
version: 1,
|
|
@@ -192,10 +200,13 @@ const migrations = [
|
|
|
192
200
|
`,
|
|
193
201
|
},
|
|
194
202
|
];
|
|
195
|
-
|
|
203
|
+
async function migrateAnalyticsDatabase(pool) {
|
|
196
204
|
const client = await pool.connect();
|
|
205
|
+
let lockAcquired = false;
|
|
206
|
+
let migrationFailed = false;
|
|
197
207
|
try {
|
|
198
208
|
await client.query("SELECT pg_advisory_lock(hashtext('recombine_analytics_migrations'))");
|
|
209
|
+
lockAcquired = true;
|
|
199
210
|
await client.query(`
|
|
200
211
|
CREATE TABLE IF NOT EXISTS analytics_schema_migrations (
|
|
201
212
|
version integer PRIMARY KEY,
|
|
@@ -221,13 +232,33 @@ export async function migrateAnalyticsDatabase(pool) {
|
|
|
221
232
|
}
|
|
222
233
|
}
|
|
223
234
|
}
|
|
235
|
+
catch (error) {
|
|
236
|
+
migrationFailed = true;
|
|
237
|
+
throw error;
|
|
238
|
+
}
|
|
239
|
+
finally {
|
|
240
|
+
await releaseAnalyticsMigrationClient(client, lockAcquired, migrationFailed);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
async function releaseAnalyticsMigrationClient(client, lockAcquired, migrationFailed) {
|
|
244
|
+
let discardClient = false;
|
|
245
|
+
try {
|
|
246
|
+
if (lockAcquired) {
|
|
247
|
+
await client.query("SELECT pg_advisory_unlock(hashtext('recombine_analytics_migrations'))");
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
catch (error) {
|
|
251
|
+
discardClient = true;
|
|
252
|
+
if (!migrationFailed) {
|
|
253
|
+
throw error;
|
|
254
|
+
}
|
|
255
|
+
}
|
|
224
256
|
finally {
|
|
225
|
-
|
|
226
|
-
client.release();
|
|
257
|
+
client.release(discardClient);
|
|
227
258
|
}
|
|
228
259
|
}
|
|
229
|
-
|
|
230
|
-
const pool = new Pool(config);
|
|
260
|
+
async function createPostgresAnalyticsServices(config) {
|
|
261
|
+
const pool = new pg_1.Pool(config);
|
|
231
262
|
try {
|
|
232
263
|
await migrateAnalyticsDatabase(pool);
|
|
233
264
|
}
|
|
@@ -238,7 +269,7 @@ export async function createPostgresAnalyticsServices(config) {
|
|
|
238
269
|
return {
|
|
239
270
|
analytics: new PostgresAnalytics(pool),
|
|
240
271
|
conversations: new PostgresConversationRepository(pool),
|
|
241
|
-
queries: new PostgresAnalyticsQueryService(pool),
|
|
272
|
+
queries: new postgres_analytics_query_1.PostgresAnalyticsQueryService(pool),
|
|
242
273
|
close: () => pool.end(),
|
|
243
274
|
};
|
|
244
275
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"timeouts-manager.d.ts","sourceRoot":"","sources":["../src/timeouts-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;
|
|
1
|
+
{"version":3,"file":"timeouts-manager.d.ts","sourceRoot":"","sources":["../src/timeouts-manager.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE7C,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAA;AAK9C,MAAM,MAAM,aAAa,GAAG;IACxB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CACnC,CAAA;AAKD,KAAK,kBAAkB,GAAG;IACtB,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAC,OAAO,CAAA;CACxB,CAAA;AAED,qBAAa,cAAc;IAOnB,QAAQ,CAAC,aAAa,EAAE,YAAY,CAAC,aAAa,CAAC;IACnD,QAAQ,CAAC,MAAM,EAAE,MAAM;IAEvB,OAAO,CAAC,SAAS;IANrB,QAAQ,CAAC,mBAAmB,kDAAwD;gBAGvE,aAAa,EAAE,YAAY,CAAC,aAAa,CAAC,EAC1C,MAAM,EAAE,MAAM,EAEf,SAAS,UAAQ;IAQ7B,gBAAgB,CAAC,MAAM,EAAE,MAAM;IAYzB,iBAAiB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAWlE,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,SAAS,CAAC,EAAE,MAAM;IA4B7D,UAAU,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM,EAAE,EAAE,EAAE,MAAM,OAAO,CAAC,IAAI,CAAC,GAAG,IAAI,EAAE,OAAO,EAAE,MAAM;IAmEvF,gBAAgB,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC;CAYxD"}
|
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TimeoutManager = void 0;
|
|
4
|
+
class TimeoutManager {
|
|
2
5
|
timeoutsCache;
|
|
3
6
|
logger;
|
|
4
7
|
shallSkip;
|
|
@@ -97,3 +100,4 @@ export class TimeoutManager {
|
|
|
97
100
|
this.logger.debug(`TimeoutManager: cleared all timeouts`, { callId });
|
|
98
101
|
}
|
|
99
102
|
}
|
|
103
|
+
exports.TimeoutManager = TimeoutManager;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@recombine-ai/platform",
|
|
3
|
-
"version": "0.2.6-test.
|
|
3
|
+
"version": "0.2.6-test.6",
|
|
4
4
|
"description": "Recombine AI: voice agent platform",
|
|
5
5
|
"license": "EULA",
|
|
6
6
|
"author": "recombine.ai",
|
|
@@ -12,23 +12,16 @@
|
|
|
12
12
|
"files": [
|
|
13
13
|
"build"
|
|
14
14
|
],
|
|
15
|
-
"
|
|
15
|
+
"main": "./build/index.js",
|
|
16
16
|
"types": "./build/index.d.ts",
|
|
17
17
|
"dependencies": {
|
|
18
18
|
"@recombine-ai/engine": "1.0.0",
|
|
19
|
-
"@recombine-ai/telescope": "0.
|
|
19
|
+
"@recombine-ai/telescope": "^0.3.0",
|
|
20
20
|
"@types/pg": "8.15.5",
|
|
21
21
|
"pg": "8.16.3",
|
|
22
22
|
"redis": "5.11.0",
|
|
23
23
|
"zod": "^4.4.3"
|
|
24
24
|
},
|
|
25
|
-
"exports": {
|
|
26
|
-
".": {
|
|
27
|
-
"types": "./build/index.d.ts",
|
|
28
|
-
"import": "./build/index.js",
|
|
29
|
-
"default": "./build/index.js"
|
|
30
|
-
}
|
|
31
|
-
},
|
|
32
25
|
"publishConfig": {
|
|
33
26
|
"registry": "https://registry.npmjs.org"
|
|
34
27
|
},
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"analytics.spec.d.ts","sourceRoot":"","sources":["../src/analytics.spec.ts"],"names":[],"mappings":""}
|
package/build/analytics.spec.js
DELETED
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import { describe, expect, expectTypeOf, it } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { analyticsKey, createInMemoryAnalytics } from './analytics.js';
|
|
4
|
-
describe('Analytics', () => {
|
|
5
|
-
it('rejects unsupported key characters', () => {
|
|
6
|
-
expect(() => analyticsKey.string('not allowed')).toThrow('Analytics key contains unsupported characters');
|
|
7
|
-
});
|
|
8
|
-
it('keeps key and value types paired', async () => {
|
|
9
|
-
const analytics = createInMemoryAnalytics();
|
|
10
|
-
const escalated = analyticsKey.boolean('common.escalated');
|
|
11
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
12
|
-
await conversation.set(escalated, true);
|
|
13
|
-
const value = await conversation.get(escalated);
|
|
14
|
-
expectTypeOf(value).toEqualTypeOf();
|
|
15
|
-
expect(value).toBe(true);
|
|
16
|
-
});
|
|
17
|
-
it('clears a value idempotently', async () => {
|
|
18
|
-
const analytics = createInMemoryAnalytics();
|
|
19
|
-
const score = analyticsKey.number('project.score');
|
|
20
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
21
|
-
await conversation.set(score, 3);
|
|
22
|
-
await conversation.clear(score);
|
|
23
|
-
await conversation.clear(score);
|
|
24
|
-
await expect(conversation.get(score)).resolves.toBeUndefined();
|
|
25
|
-
});
|
|
26
|
-
it('validates JSON values at runtime', async () => {
|
|
27
|
-
const analytics = createInMemoryAnalytics();
|
|
28
|
-
const result = analyticsKey.json('project.result', z.object({ reason: z.string(), accepted: z.boolean() }));
|
|
29
|
-
const conversation = analytics.getConversationAnalytics('conversation-1');
|
|
30
|
-
await expect(conversation.set(result, {
|
|
31
|
-
reason: 'done',
|
|
32
|
-
accepted: true,
|
|
33
|
-
})).resolves.toBeUndefined();
|
|
34
|
-
await expect(conversation.set(result, {
|
|
35
|
-
reason: 'done',
|
|
36
|
-
accepted: 'yes',
|
|
37
|
-
})).rejects.toThrow();
|
|
38
|
-
});
|
|
39
|
-
it('distinguishes JSON null from a missing key', async () => {
|
|
40
|
-
const analytics = createInMemoryAnalytics();
|
|
41
|
-
const result = analyticsKey.json('project.result', z.null());
|
|
42
|
-
await analytics.getConversationAnalytics('conversation-1').set(result, null);
|
|
43
|
-
await expect(analytics.getConversationAnalytics('conversation-1').get(result)).resolves.toBeNull();
|
|
44
|
-
await expect(analytics.getConversationAnalytics('conversation-2').get(result)).resolves.toBeUndefined();
|
|
45
|
-
});
|
|
46
|
-
it('returns common conversation data from getSummary', async () => {
|
|
47
|
-
const summary = {
|
|
48
|
-
conversationId: 'conversation-1',
|
|
49
|
-
agentId: 'support',
|
|
50
|
-
channel: 'phone',
|
|
51
|
-
startedAt: new Date('2026-08-22T10:00:00Z'),
|
|
52
|
-
durationSeconds: 60,
|
|
53
|
-
};
|
|
54
|
-
const analytics = createInMemoryAnalytics([summary]);
|
|
55
|
-
await expect(analytics.getConversationAnalytics('conversation-1').getSummary()).resolves.toEqual(summary);
|
|
56
|
-
});
|
|
57
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics-query.spec.d.ts","sourceRoot":"","sources":["../src/postgres-analytics-query.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,223 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { AnalyticsQueryError, PostgresAnalyticsQueryService } from './postgres-analytics-query.js';
|
|
3
|
-
function result(rows = []) {
|
|
4
|
-
return { rows, rowCount: rows.length };
|
|
5
|
-
}
|
|
6
|
-
describe('PostgresAnalyticsQueryService', () => {
|
|
7
|
-
it('binds each analytics filter once', async () => {
|
|
8
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
9
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
10
|
-
await service.getConversations({
|
|
11
|
-
agentId: 'collection-en',
|
|
12
|
-
limit: '20',
|
|
13
|
-
filters: JSON.stringify([
|
|
14
|
-
{
|
|
15
|
-
key: 'wandoo.disposition',
|
|
16
|
-
type: 'string',
|
|
17
|
-
operator: 'eq',
|
|
18
|
-
value: 'PAID',
|
|
19
|
-
},
|
|
20
|
-
]),
|
|
21
|
-
});
|
|
22
|
-
const [sql, values] = query.mock.calls[0];
|
|
23
|
-
expect(sql.match(/jsonb_build_object/g)).toHaveLength(1);
|
|
24
|
-
expect(values).toEqual(['collection-en', 'wandoo.disposition', '"PAID"', 21]);
|
|
25
|
-
});
|
|
26
|
-
it('ANDs a multiselect filter with the other analytics filters', async () => {
|
|
27
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
28
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
29
|
-
await service.getConversations({
|
|
30
|
-
filters: JSON.stringify([
|
|
31
|
-
{
|
|
32
|
-
key: 'wandoo.escalated',
|
|
33
|
-
type: 'boolean',
|
|
34
|
-
operator: 'eq',
|
|
35
|
-
value: true,
|
|
36
|
-
},
|
|
37
|
-
{
|
|
38
|
-
key: 'wandoo.disposition',
|
|
39
|
-
type: 'string',
|
|
40
|
-
operator: 'in',
|
|
41
|
-
values: ['PAID', 'DROPPED'],
|
|
42
|
-
},
|
|
43
|
-
]),
|
|
44
|
-
});
|
|
45
|
-
const [sql, values] = query.mock.calls[0];
|
|
46
|
-
expect(sql).toContain('jsonb_build_object($1::text, $2::jsonb) AND');
|
|
47
|
-
expect(sql).toContain('(c.analytics @> jsonb_build_object($3::text, $4::jsonb) OR ' +
|
|
48
|
-
'c.analytics @> jsonb_build_object($3::text, $5::jsonb))');
|
|
49
|
-
expect(values).toEqual([
|
|
50
|
-
'wandoo.escalated',
|
|
51
|
-
'true',
|
|
52
|
-
'wandoo.disposition',
|
|
53
|
-
'"PAID"',
|
|
54
|
-
'"DROPPED"',
|
|
55
|
-
11,
|
|
56
|
-
]);
|
|
57
|
-
});
|
|
58
|
-
it('uses the returned keyset cursor on the next page', async () => {
|
|
59
|
-
const firstStartedAt = new Date('2026-08-02T10:00:00Z');
|
|
60
|
-
const query = vi
|
|
61
|
-
.fn()
|
|
62
|
-
.mockResolvedValueOnce(result([
|
|
63
|
-
{
|
|
64
|
-
conversation_id: 'conversation-2',
|
|
65
|
-
channel: 'phone',
|
|
66
|
-
started_at: firstStartedAt,
|
|
67
|
-
analytics: {},
|
|
68
|
-
},
|
|
69
|
-
{
|
|
70
|
-
conversation_id: 'conversation-1',
|
|
71
|
-
channel: 'phone',
|
|
72
|
-
started_at: new Date('2026-08-01T10:00:00Z'),
|
|
73
|
-
analytics: {},
|
|
74
|
-
},
|
|
75
|
-
]))
|
|
76
|
-
.mockResolvedValueOnce(result());
|
|
77
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
78
|
-
const firstPage = await service.getConversations({ limit: '1' });
|
|
79
|
-
expect(firstPage.lastConversationId).toBeTypeOf('string');
|
|
80
|
-
await service.getConversations({
|
|
81
|
-
limit: '1',
|
|
82
|
-
lastConversationId: firstPage.lastConversationId,
|
|
83
|
-
});
|
|
84
|
-
const [sql, values] = query.mock.calls[1];
|
|
85
|
-
expect(sql).toContain('(c.started_at, c.conversation_id) < ($1, $2)');
|
|
86
|
-
expect(values).toEqual([firstStartedAt, 'conversation-2', 2]);
|
|
87
|
-
});
|
|
88
|
-
it('rejects malformed filters without querying PostgreSQL', async () => {
|
|
89
|
-
const query = vi.fn();
|
|
90
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
91
|
-
await expect(service.getConversations({
|
|
92
|
-
filters: JSON.stringify([
|
|
93
|
-
{
|
|
94
|
-
key: 'wandoo.disposition',
|
|
95
|
-
type: 'string',
|
|
96
|
-
operator: 'gt',
|
|
97
|
-
value: 'PAID',
|
|
98
|
-
},
|
|
99
|
-
]),
|
|
100
|
-
})).rejects.toBeInstanceOf(AnalyticsQueryError);
|
|
101
|
-
expect(query).not.toHaveBeenCalled();
|
|
102
|
-
});
|
|
103
|
-
it('omits value counts for fields with more than 50 variants', async () => {
|
|
104
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
105
|
-
{
|
|
106
|
-
key: 'wandoo.disposition',
|
|
107
|
-
type: 'string',
|
|
108
|
-
value: 'PAID',
|
|
109
|
-
occurrence_count: '3',
|
|
110
|
-
variant_count: 2,
|
|
111
|
-
},
|
|
112
|
-
{
|
|
113
|
-
key: 'wandoo.reference',
|
|
114
|
-
type: 'string',
|
|
115
|
-
value: 'first',
|
|
116
|
-
occurrence_count: '1',
|
|
117
|
-
variant_count: 51,
|
|
118
|
-
},
|
|
119
|
-
]));
|
|
120
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
121
|
-
await expect(service.getAnalyticsFields({ agentId: 'collection-en' })).resolves.toEqual({
|
|
122
|
-
items: [
|
|
123
|
-
{
|
|
124
|
-
key: 'wandoo.disposition',
|
|
125
|
-
type: 'string',
|
|
126
|
-
variantCount: 2,
|
|
127
|
-
values: [{ value: 'PAID', count: 3 }],
|
|
128
|
-
},
|
|
129
|
-
{
|
|
130
|
-
key: 'wandoo.reference',
|
|
131
|
-
type: 'string',
|
|
132
|
-
variantCount: 51,
|
|
133
|
-
values: [],
|
|
134
|
-
},
|
|
135
|
-
],
|
|
136
|
-
});
|
|
137
|
-
});
|
|
138
|
-
it('returns a rate time series with numerator and denominator', async () => {
|
|
139
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
140
|
-
{
|
|
141
|
-
bucket_start: new Date('2026-08-03T00:00:00Z'),
|
|
142
|
-
metric_value: '0.25',
|
|
143
|
-
numerator: '1',
|
|
144
|
-
denominator: '4',
|
|
145
|
-
},
|
|
146
|
-
]));
|
|
147
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
148
|
-
const metric = {
|
|
149
|
-
operation: 'rate',
|
|
150
|
-
filter: {
|
|
151
|
-
key: 'wandoo.escalated',
|
|
152
|
-
type: 'boolean',
|
|
153
|
-
operator: 'eq',
|
|
154
|
-
value: true,
|
|
155
|
-
},
|
|
156
|
-
};
|
|
157
|
-
await expect(service.getTimeSeries({
|
|
158
|
-
from: '2026-08-01T00:00:00Z',
|
|
159
|
-
to: '2026-09-01T00:00:00Z',
|
|
160
|
-
granularity: 'week',
|
|
161
|
-
metric: JSON.stringify(metric),
|
|
162
|
-
})).resolves.toEqual({
|
|
163
|
-
metric,
|
|
164
|
-
granularity: 'week',
|
|
165
|
-
from: '2026-08-01T00:00:00.000Z',
|
|
166
|
-
to: '2026-09-01T00:00:00.000Z',
|
|
167
|
-
items: [
|
|
168
|
-
{
|
|
169
|
-
startAt: '2026-08-03T00:00:00.000Z',
|
|
170
|
-
value: 0.25,
|
|
171
|
-
numerator: 1,
|
|
172
|
-
denominator: 4,
|
|
173
|
-
},
|
|
174
|
-
],
|
|
175
|
-
});
|
|
176
|
-
const [sql] = query.mock.calls[0];
|
|
177
|
-
expect(sql).toContain("date_trunc($3::text, c.started_at, 'UTC')");
|
|
178
|
-
});
|
|
179
|
-
it.each([
|
|
180
|
-
['average', 'AVG'],
|
|
181
|
-
['sum', 'SUM'],
|
|
182
|
-
])('builds the %s metric', async (operation, sqlFunction) => {
|
|
183
|
-
const query = vi.fn().mockResolvedValue(result());
|
|
184
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
185
|
-
await service.getTimeSeries({
|
|
186
|
-
from: '2026-08-01T00:00:00Z',
|
|
187
|
-
to: '2026-09-01T00:00:00Z',
|
|
188
|
-
metric: JSON.stringify({ operation, key: 'wandoo.score' }),
|
|
189
|
-
});
|
|
190
|
-
const [sql, values] = query.mock.calls[0];
|
|
191
|
-
expect(sql).toContain(`${sqlFunction}(CASE WHEN jsonb_typeof`);
|
|
192
|
-
expect(values).toEqual([
|
|
193
|
-
new Date('2026-08-01T00:00:00Z'),
|
|
194
|
-
new Date('2026-09-01T00:00:00Z'),
|
|
195
|
-
'day',
|
|
196
|
-
'wandoo.score',
|
|
197
|
-
]);
|
|
198
|
-
});
|
|
199
|
-
it('maps one PostgreSQL conversation to the Telescope response', async () => {
|
|
200
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
201
|
-
{
|
|
202
|
-
conversation_id: 'conversation-1',
|
|
203
|
-
agent_id: 'collection-en',
|
|
204
|
-
channel: 'phone',
|
|
205
|
-
region: 'GB',
|
|
206
|
-
started_at: new Date('2026-08-01T10:00:00Z'),
|
|
207
|
-
ended_at: new Date('2026-08-01T10:02:00Z'),
|
|
208
|
-
duration_seconds: 120,
|
|
209
|
-
analytics: { 'wandoo.escalated': true },
|
|
210
|
-
phone_number: '+440000000000',
|
|
211
|
-
recording_url: 'https://example.test/recording.wav',
|
|
212
|
-
},
|
|
213
|
-
]));
|
|
214
|
-
const service = new PostgresAnalyticsQueryService({ query });
|
|
215
|
-
await expect(service.getConversation('conversation-1')).resolves.toMatchObject({
|
|
216
|
-
conversationId: 'conversation-1',
|
|
217
|
-
medium: 'phone',
|
|
218
|
-
duration: 120,
|
|
219
|
-
analytics: { 'wandoo.escalated': true },
|
|
220
|
-
phoneNumber: '+440000000000',
|
|
221
|
-
});
|
|
222
|
-
});
|
|
223
|
-
});
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"postgres-analytics.spec.d.ts","sourceRoot":"","sources":["../src/postgres-analytics.spec.ts"],"names":[],"mappings":""}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { z } from 'zod';
|
|
3
|
-
import { analyticsKey } from './analytics.js';
|
|
4
|
-
import { PostgresAnalytics } from './postgres-analytics.js';
|
|
5
|
-
function result(rows = [], rowCount = rows.length) {
|
|
6
|
-
return { rows, rowCount };
|
|
7
|
-
}
|
|
8
|
-
describe('PostgresAnalytics', () => {
|
|
9
|
-
it('stores a typed value under one literal JSON key', async () => {
|
|
10
|
-
const query = vi.fn().mockResolvedValue(result([], 1));
|
|
11
|
-
const analytics = new PostgresAnalytics({ query });
|
|
12
|
-
await analytics
|
|
13
|
-
.getConversationAnalytics('conversation-1')
|
|
14
|
-
.set(analyticsKey.boolean('common.escalated'), true);
|
|
15
|
-
expect(query).toHaveBeenCalledWith(expect.stringContaining('jsonb_set'), [
|
|
16
|
-
'conversation-1',
|
|
17
|
-
'common.escalated',
|
|
18
|
-
'true',
|
|
19
|
-
]);
|
|
20
|
-
});
|
|
21
|
-
it('validates values before writing', async () => {
|
|
22
|
-
const query = vi.fn();
|
|
23
|
-
const analytics = new PostgresAnalytics({ query });
|
|
24
|
-
await expect(analytics
|
|
25
|
-
.getConversationAnalytics('conversation-1')
|
|
26
|
-
.set(analyticsKey.number('score'), 'wrong')).rejects.toThrow();
|
|
27
|
-
expect(query).not.toHaveBeenCalled();
|
|
28
|
-
});
|
|
29
|
-
it('reads JSON null as a present value', async () => {
|
|
30
|
-
const query = vi.fn().mockResolvedValue(result([{ present: true, value: null }]));
|
|
31
|
-
const analytics = new PostgresAnalytics({ query });
|
|
32
|
-
await expect(analytics
|
|
33
|
-
.getConversationAnalytics('conversation-1')
|
|
34
|
-
.get(analyticsKey.json('result', z.null()))).resolves.toBeNull();
|
|
35
|
-
});
|
|
36
|
-
it('returns a common conversation summary', async () => {
|
|
37
|
-
const startedAt = new Date('2026-08-01T10:00:00Z');
|
|
38
|
-
const endedAt = new Date('2026-08-01T10:02:00Z');
|
|
39
|
-
const query = vi.fn().mockResolvedValue(result([
|
|
40
|
-
{
|
|
41
|
-
conversation_id: 'conversation-1',
|
|
42
|
-
agent_id: 'support-agent',
|
|
43
|
-
channel: 'phone',
|
|
44
|
-
region: 'GB',
|
|
45
|
-
started_at: startedAt,
|
|
46
|
-
ended_at: endedAt,
|
|
47
|
-
duration_seconds: 120,
|
|
48
|
-
},
|
|
49
|
-
]));
|
|
50
|
-
const analytics = new PostgresAnalytics({ query });
|
|
51
|
-
await expect(analytics.getConversationAnalytics('conversation-1').getSummary()).resolves.toEqual({
|
|
52
|
-
conversationId: 'conversation-1',
|
|
53
|
-
agentId: 'support-agent',
|
|
54
|
-
channel: 'phone',
|
|
55
|
-
region: 'GB',
|
|
56
|
-
startedAt,
|
|
57
|
-
endedAt,
|
|
58
|
-
durationSeconds: 120,
|
|
59
|
-
});
|
|
60
|
-
});
|
|
61
|
-
});
|