@cortexmemory/cli 0.26.2 → 0.27.3
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/dist/commands/convex.js +1 -1
- package/dist/commands/convex.js.map +1 -1
- package/dist/commands/deploy.d.ts +1 -1
- package/dist/commands/deploy.d.ts.map +1 -1
- package/dist/commands/deploy.js +771 -144
- package/dist/commands/deploy.js.map +1 -1
- package/dist/commands/dev.d.ts.map +1 -1
- package/dist/commands/dev.js +210 -36
- package/dist/commands/dev.js.map +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +273 -43
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/setup.d.ts.map +1 -1
- package/dist/commands/setup.js +102 -46
- package/dist/commands/setup.js.map +1 -1
- package/dist/commands/status.d.ts.map +1 -1
- package/dist/commands/status.js +94 -7
- package/dist/commands/status.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/types.d.ts +23 -0
- package/dist/types.d.ts.map +1 -1
- package/dist/utils/app-template-sync.d.ts +95 -0
- package/dist/utils/app-template-sync.d.ts.map +1 -0
- package/dist/utils/app-template-sync.js +425 -0
- package/dist/utils/app-template-sync.js.map +1 -0
- package/dist/utils/config.d.ts +11 -0
- package/dist/utils/config.d.ts.map +1 -1
- package/dist/utils/config.js +20 -0
- package/dist/utils/config.js.map +1 -1
- package/dist/utils/deployment-selector.d.ts +21 -0
- package/dist/utils/deployment-selector.d.ts.map +1 -1
- package/dist/utils/deployment-selector.js +32 -0
- package/dist/utils/deployment-selector.js.map +1 -1
- package/dist/utils/init/graph-setup.d.ts.map +1 -1
- package/dist/utils/init/graph-setup.js +25 -2
- package/dist/utils/init/graph-setup.js.map +1 -1
- package/dist/utils/init/quickstart-setup.d.ts +87 -0
- package/dist/utils/init/quickstart-setup.d.ts.map +1 -0
- package/dist/utils/init/quickstart-setup.js +462 -0
- package/dist/utils/init/quickstart-setup.js.map +1 -0
- package/dist/utils/schema-sync.d.ts.map +1 -1
- package/dist/utils/schema-sync.js +27 -21
- package/dist/utils/schema-sync.js.map +1 -1
- package/package.json +3 -2
- package/templates/vercel-ai-quickstart/.env.local.example +45 -0
- package/templates/vercel-ai-quickstart/README.md +280 -0
- package/templates/vercel-ai-quickstart/app/api/auth/check/route.ts +30 -0
- package/templates/vercel-ai-quickstart/app/api/auth/login/route.ts +83 -0
- package/templates/vercel-ai-quickstart/app/api/auth/register/route.ts +94 -0
- package/templates/vercel-ai-quickstart/app/api/auth/setup/route.ts +59 -0
- package/templates/vercel-ai-quickstart/app/api/chat/route.ts +277 -0
- package/templates/vercel-ai-quickstart/app/api/conversations/route.ts +179 -0
- package/templates/vercel-ai-quickstart/app/api/facts/route.ts +39 -0
- package/templates/vercel-ai-quickstart/app/api/health/route.ts +99 -0
- package/templates/vercel-ai-quickstart/app/api/memories/route.ts +37 -0
- package/templates/vercel-ai-quickstart/app/globals.css +275 -0
- package/templates/vercel-ai-quickstart/app/layout.tsx +19 -0
- package/templates/vercel-ai-quickstart/app/page.tsx +216 -0
- package/templates/vercel-ai-quickstart/components/AdminSetup.tsx +139 -0
- package/templates/vercel-ai-quickstart/components/AuthProvider.tsx +283 -0
- package/templates/vercel-ai-quickstart/components/ChatHistorySidebar.tsx +323 -0
- package/templates/vercel-ai-quickstart/components/ChatInterface.tsx +334 -0
- package/templates/vercel-ai-quickstart/components/ConvexClientProvider.tsx +21 -0
- package/templates/vercel-ai-quickstart/components/DataPreview.tsx +57 -0
- package/templates/vercel-ai-quickstart/components/HealthStatus.tsx +214 -0
- package/templates/vercel-ai-quickstart/components/LayerCard.tsx +263 -0
- package/templates/vercel-ai-quickstart/components/LayerFlowDiagram.tsx +195 -0
- package/templates/vercel-ai-quickstart/components/LoginScreen.tsx +202 -0
- package/templates/vercel-ai-quickstart/components/MemorySpaceSwitcher.tsx +93 -0
- package/templates/vercel-ai-quickstart/convex/conversations.ts +67 -0
- package/templates/vercel-ai-quickstart/convex/facts.ts +131 -0
- package/templates/vercel-ai-quickstart/convex/health.ts +15 -0
- package/templates/vercel-ai-quickstart/convex/memories.ts +104 -0
- package/templates/vercel-ai-quickstart/convex/schema.ts +20 -0
- package/templates/vercel-ai-quickstart/convex/users.ts +105 -0
- package/templates/vercel-ai-quickstart/jest.config.js +45 -0
- package/templates/vercel-ai-quickstart/lib/animations.ts +146 -0
- package/templates/vercel-ai-quickstart/lib/cortex.ts +27 -0
- package/templates/vercel-ai-quickstart/lib/layer-tracking.ts +214 -0
- package/templates/vercel-ai-quickstart/lib/password.ts +120 -0
- package/templates/vercel-ai-quickstart/next.config.js +27 -0
- package/templates/vercel-ai-quickstart/package.json +46 -0
- package/templates/vercel-ai-quickstart/postcss.config.js +5 -0
- package/templates/vercel-ai-quickstart/tailwind.config.js +37 -0
- package/templates/vercel-ai-quickstart/tests/helpers/mock-cortex.ts +263 -0
- package/templates/vercel-ai-quickstart/tests/helpers/setup.ts +48 -0
- package/templates/vercel-ai-quickstart/tests/integration/auth.test.ts +455 -0
- package/templates/vercel-ai-quickstart/tests/integration/conversations.test.ts +461 -0
- package/templates/vercel-ai-quickstart/tests/unit/password.test.ts +228 -0
- package/templates/vercel-ai-quickstart/tsconfig.json +33 -0
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Password Hashing Utilities
|
|
3
|
+
*
|
|
4
|
+
* Uses Web Crypto API (PBKDF2) for password hashing.
|
|
5
|
+
* Works in Edge runtime - no external dependencies.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const ITERATIONS = 100000;
|
|
9
|
+
const KEY_LENGTH = 256;
|
|
10
|
+
const SALT_LENGTH = 16;
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Hash a password using PBKDF2
|
|
14
|
+
*
|
|
15
|
+
* @param password - Plain text password to hash
|
|
16
|
+
* @returns Hashed password as base64 string (format: salt:hash)
|
|
17
|
+
*/
|
|
18
|
+
export async function hashPassword(password: string): Promise<string> {
|
|
19
|
+
const encoder = new TextEncoder();
|
|
20
|
+
const passwordBuffer = encoder.encode(password);
|
|
21
|
+
|
|
22
|
+
// Generate random salt
|
|
23
|
+
const salt = crypto.getRandomValues(new Uint8Array(SALT_LENGTH));
|
|
24
|
+
|
|
25
|
+
// Import password as key material
|
|
26
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
27
|
+
"raw",
|
|
28
|
+
passwordBuffer,
|
|
29
|
+
"PBKDF2",
|
|
30
|
+
false,
|
|
31
|
+
["deriveBits"]
|
|
32
|
+
);
|
|
33
|
+
|
|
34
|
+
// Derive key using PBKDF2
|
|
35
|
+
const derivedBits = await crypto.subtle.deriveBits(
|
|
36
|
+
{
|
|
37
|
+
name: "PBKDF2",
|
|
38
|
+
salt,
|
|
39
|
+
iterations: ITERATIONS,
|
|
40
|
+
hash: "SHA-256",
|
|
41
|
+
},
|
|
42
|
+
keyMaterial,
|
|
43
|
+
KEY_LENGTH
|
|
44
|
+
);
|
|
45
|
+
|
|
46
|
+
// Convert to base64 strings
|
|
47
|
+
const saltB64 = btoa(String.fromCharCode(...salt));
|
|
48
|
+
const hashB64 = btoa(String.fromCharCode(...new Uint8Array(derivedBits)));
|
|
49
|
+
|
|
50
|
+
// Return combined format: salt:hash
|
|
51
|
+
return `${saltB64}:${hashB64}`;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Verify a password against a stored hash
|
|
56
|
+
*
|
|
57
|
+
* @param password - Plain text password to verify
|
|
58
|
+
* @param storedHash - Previously hashed password (format: salt:hash)
|
|
59
|
+
* @returns True if password matches
|
|
60
|
+
*/
|
|
61
|
+
export async function verifyPassword(
|
|
62
|
+
password: string,
|
|
63
|
+
storedHash: string
|
|
64
|
+
): Promise<boolean> {
|
|
65
|
+
try {
|
|
66
|
+
const [saltB64, expectedHashB64] = storedHash.split(":");
|
|
67
|
+
if (!saltB64 || !expectedHashB64) {
|
|
68
|
+
return false;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
const encoder = new TextEncoder();
|
|
72
|
+
const passwordBuffer = encoder.encode(password);
|
|
73
|
+
|
|
74
|
+
// Decode salt from base64
|
|
75
|
+
const saltStr = atob(saltB64);
|
|
76
|
+
const salt = new Uint8Array(saltStr.length);
|
|
77
|
+
for (let i = 0; i < saltStr.length; i++) {
|
|
78
|
+
salt[i] = saltStr.charCodeAt(i);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
// Import password as key material
|
|
82
|
+
const keyMaterial = await crypto.subtle.importKey(
|
|
83
|
+
"raw",
|
|
84
|
+
passwordBuffer,
|
|
85
|
+
"PBKDF2",
|
|
86
|
+
false,
|
|
87
|
+
["deriveBits"]
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
// Derive key using same parameters
|
|
91
|
+
const derivedBits = await crypto.subtle.deriveBits(
|
|
92
|
+
{
|
|
93
|
+
name: "PBKDF2",
|
|
94
|
+
salt,
|
|
95
|
+
iterations: ITERATIONS,
|
|
96
|
+
hash: "SHA-256",
|
|
97
|
+
},
|
|
98
|
+
keyMaterial,
|
|
99
|
+
KEY_LENGTH
|
|
100
|
+
);
|
|
101
|
+
|
|
102
|
+
// Compare hashes
|
|
103
|
+
const hashB64 = btoa(String.fromCharCode(...new Uint8Array(derivedBits)));
|
|
104
|
+
return hashB64 === expectedHashB64;
|
|
105
|
+
} catch {
|
|
106
|
+
return false;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
/**
|
|
111
|
+
* Generate a secure random session token
|
|
112
|
+
*
|
|
113
|
+
* @returns Random token as hex string
|
|
114
|
+
*/
|
|
115
|
+
export function generateSessionToken(): string {
|
|
116
|
+
const bytes = crypto.getRandomValues(new Uint8Array(32));
|
|
117
|
+
return Array.from(bytes)
|
|
118
|
+
.map((b) => b.toString(16).padStart(2, "0"))
|
|
119
|
+
.join("");
|
|
120
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
const path = require("path");
|
|
2
|
+
|
|
3
|
+
/** @type {import('next').NextConfig} */
|
|
4
|
+
const nextConfig = {
|
|
5
|
+
transpilePackages: ["@cortexmemory/sdk", "@cortexmemory/vercel-ai-provider"],
|
|
6
|
+
serverExternalPackages: ["convex"],
|
|
7
|
+
experimental: {
|
|
8
|
+
// Ensure linked packages resolve dependencies from this project's node_modules
|
|
9
|
+
externalDir: true,
|
|
10
|
+
},
|
|
11
|
+
// Empty turbopack config to silence the warning about missing turbopack config
|
|
12
|
+
turbopack: {},
|
|
13
|
+
// Webpack configuration for module resolution when SDK is file-linked
|
|
14
|
+
// This is needed because the SDK uses dynamic imports that don't resolve
|
|
15
|
+
// correctly from a linked package's location during local development
|
|
16
|
+
webpack: (config) => {
|
|
17
|
+
config.resolve.alias = {
|
|
18
|
+
...config.resolve.alias,
|
|
19
|
+
"@anthropic-ai/sdk": path.resolve(__dirname, "node_modules/@anthropic-ai/sdk"),
|
|
20
|
+
"openai": path.resolve(__dirname, "node_modules/openai"),
|
|
21
|
+
"neo4j-driver": path.resolve(__dirname, "node_modules/neo4j-driver"),
|
|
22
|
+
};
|
|
23
|
+
return config;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
module.exports = nextConfig;
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@cortexmemory/vercel-ai-quickstart",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"license": "UNLICENSED",
|
|
6
|
+
"description": "Cortex Memory + Vercel AI SDK Quickstart Demo",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "next dev --webpack",
|
|
9
|
+
"build": "next build --webpack",
|
|
10
|
+
"start": "next start",
|
|
11
|
+
"lint": "next lint",
|
|
12
|
+
"test": "jest",
|
|
13
|
+
"test:watch": "jest --watch",
|
|
14
|
+
"convex:dev": "convex dev",
|
|
15
|
+
"convex:deploy": "convex deploy"
|
|
16
|
+
},
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"@ai-sdk/openai": "^3.0.1",
|
|
19
|
+
"@ai-sdk/react": "^3.0.3",
|
|
20
|
+
"@anthropic-ai/sdk": "^0.71.2",
|
|
21
|
+
"@cortexmemory/sdk": "file:../../..",
|
|
22
|
+
"@cortexmemory/vercel-ai-provider": "file:..",
|
|
23
|
+
"ai": "^6.0.3",
|
|
24
|
+
"convex": "^1.31.2",
|
|
25
|
+
"framer-motion": "^12.23.26",
|
|
26
|
+
"neo4j-driver": "^6.0.1",
|
|
27
|
+
"next": "^16.1.1",
|
|
28
|
+
"openai": "^6.15.0",
|
|
29
|
+
"react": "^19.2.3",
|
|
30
|
+
"react-dom": "^19.2.3",
|
|
31
|
+
"zod": "^4.2.1"
|
|
32
|
+
},
|
|
33
|
+
"devDependencies": {
|
|
34
|
+
"@tailwindcss/postcss": "^4.1.18",
|
|
35
|
+
"@types/jest": "^29.5.14",
|
|
36
|
+
"@types/node": "^25.0.3",
|
|
37
|
+
"@types/react": "^19.2.7",
|
|
38
|
+
"@types/react-dom": "^19.2.3",
|
|
39
|
+
"autoprefixer": "^10.4.23",
|
|
40
|
+
"jest": "^29.7.0",
|
|
41
|
+
"postcss": "^8.5.6",
|
|
42
|
+
"tailwindcss": "^4.1.18",
|
|
43
|
+
"ts-jest": "^29.2.5",
|
|
44
|
+
"typescript": "^5.9.3"
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
|
2
|
+
module.exports = {
|
|
3
|
+
content: [
|
|
4
|
+
"./app/**/*.{js,ts,jsx,tsx,mdx}",
|
|
5
|
+
"./components/**/*.{js,ts,jsx,tsx,mdx}",
|
|
6
|
+
],
|
|
7
|
+
theme: {
|
|
8
|
+
extend: {
|
|
9
|
+
colors: {
|
|
10
|
+
cortex: {
|
|
11
|
+
50: "#f0f7ff",
|
|
12
|
+
100: "#e0effe",
|
|
13
|
+
200: "#b9dffc",
|
|
14
|
+
300: "#7cc5fa",
|
|
15
|
+
400: "#36a8f5",
|
|
16
|
+
500: "#0c8ce6",
|
|
17
|
+
600: "#006fc4",
|
|
18
|
+
700: "#0159a0",
|
|
19
|
+
800: "#064b84",
|
|
20
|
+
900: "#0b3f6d",
|
|
21
|
+
950: "#072848",
|
|
22
|
+
},
|
|
23
|
+
},
|
|
24
|
+
animation: {
|
|
25
|
+
"pulse-slow": "pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite",
|
|
26
|
+
flow: "flow 2s ease-in-out infinite",
|
|
27
|
+
},
|
|
28
|
+
keyframes: {
|
|
29
|
+
flow: {
|
|
30
|
+
"0%, 100%": { opacity: "0.3" },
|
|
31
|
+
"50%": { opacity: "1" },
|
|
32
|
+
},
|
|
33
|
+
},
|
|
34
|
+
},
|
|
35
|
+
},
|
|
36
|
+
plugins: [],
|
|
37
|
+
};
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mock Cortex SDK
|
|
3
|
+
*
|
|
4
|
+
* Provides a mock implementation of the Cortex SDK for testing.
|
|
5
|
+
* Simulates the SDK behavior without requiring a real Convex backend.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
// In-memory stores for test state
|
|
9
|
+
let mutableStore: Map<string, Map<string, unknown>> = new Map();
|
|
10
|
+
let usersStore: Map<string, { userId: string; data: Record<string, unknown> }> =
|
|
11
|
+
new Map();
|
|
12
|
+
interface StoredConversation {
|
|
13
|
+
conversationId: string;
|
|
14
|
+
memorySpaceId: string;
|
|
15
|
+
type: string;
|
|
16
|
+
participants: { userId?: string; agentId?: string };
|
|
17
|
+
metadata: Record<string, unknown>;
|
|
18
|
+
createdAt: number;
|
|
19
|
+
updatedAt: number;
|
|
20
|
+
messageCount: number;
|
|
21
|
+
messages?: Array<{
|
|
22
|
+
id: string;
|
|
23
|
+
role: string;
|
|
24
|
+
content: string;
|
|
25
|
+
timestamp: number;
|
|
26
|
+
}>;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
let conversationsStore: Map<string, StoredConversation> = new Map();
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Reset all mock stores (call in beforeEach)
|
|
33
|
+
*/
|
|
34
|
+
export function resetMockStores(): void {
|
|
35
|
+
mutableStore = new Map();
|
|
36
|
+
usersStore = new Map();
|
|
37
|
+
conversationsStore = new Map();
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Create a mock Cortex SDK instance
|
|
42
|
+
*/
|
|
43
|
+
export function createMockCortex() {
|
|
44
|
+
return {
|
|
45
|
+
mutable: {
|
|
46
|
+
get: jest.fn(
|
|
47
|
+
async (namespace: string, key: string): Promise<unknown | null> => {
|
|
48
|
+
const ns = mutableStore.get(namespace);
|
|
49
|
+
if (!ns) return null;
|
|
50
|
+
return ns.get(key) ?? null;
|
|
51
|
+
}
|
|
52
|
+
),
|
|
53
|
+
set: jest.fn(
|
|
54
|
+
async (
|
|
55
|
+
namespace: string,
|
|
56
|
+
key: string,
|
|
57
|
+
value: unknown
|
|
58
|
+
): Promise<void> => {
|
|
59
|
+
if (!mutableStore.has(namespace)) {
|
|
60
|
+
mutableStore.set(namespace, new Map());
|
|
61
|
+
}
|
|
62
|
+
mutableStore.get(namespace)!.set(key, value);
|
|
63
|
+
}
|
|
64
|
+
),
|
|
65
|
+
delete: jest.fn(
|
|
66
|
+
async (namespace: string, key: string): Promise<void> => {
|
|
67
|
+
const ns = mutableStore.get(namespace);
|
|
68
|
+
if (ns) ns.delete(key);
|
|
69
|
+
}
|
|
70
|
+
),
|
|
71
|
+
},
|
|
72
|
+
users: {
|
|
73
|
+
get: jest.fn(
|
|
74
|
+
async (
|
|
75
|
+
userId: string
|
|
76
|
+
): Promise<{
|
|
77
|
+
userId: string;
|
|
78
|
+
data: Record<string, unknown>;
|
|
79
|
+
} | null> => {
|
|
80
|
+
return usersStore.get(userId) ?? null;
|
|
81
|
+
}
|
|
82
|
+
),
|
|
83
|
+
update: jest.fn(
|
|
84
|
+
async (
|
|
85
|
+
userId: string,
|
|
86
|
+
data: Record<string, unknown>
|
|
87
|
+
): Promise<{ userId: string; data: Record<string, unknown> }> => {
|
|
88
|
+
const existing = usersStore.get(userId);
|
|
89
|
+
const user = {
|
|
90
|
+
userId,
|
|
91
|
+
data: { ...(existing?.data || {}), ...data },
|
|
92
|
+
};
|
|
93
|
+
usersStore.set(userId, user);
|
|
94
|
+
return user;
|
|
95
|
+
}
|
|
96
|
+
),
|
|
97
|
+
delete: jest.fn(async (userId: string): Promise<void> => {
|
|
98
|
+
usersStore.delete(userId);
|
|
99
|
+
}),
|
|
100
|
+
list: jest.fn(async () => {
|
|
101
|
+
return {
|
|
102
|
+
users: Array.from(usersStore.values()),
|
|
103
|
+
hasMore: false,
|
|
104
|
+
};
|
|
105
|
+
}),
|
|
106
|
+
},
|
|
107
|
+
conversations: {
|
|
108
|
+
list: jest.fn(
|
|
109
|
+
async (params: { memorySpaceId?: string; userId?: string }) => {
|
|
110
|
+
const conversations = Array.from(conversationsStore.values()).filter(
|
|
111
|
+
(conv) => {
|
|
112
|
+
if (
|
|
113
|
+
params.memorySpaceId &&
|
|
114
|
+
conv.memorySpaceId !== params.memorySpaceId
|
|
115
|
+
) {
|
|
116
|
+
return false;
|
|
117
|
+
}
|
|
118
|
+
if (
|
|
119
|
+
params.userId &&
|
|
120
|
+
conv.participants.userId !== params.userId
|
|
121
|
+
) {
|
|
122
|
+
return false;
|
|
123
|
+
}
|
|
124
|
+
return true;
|
|
125
|
+
}
|
|
126
|
+
);
|
|
127
|
+
return { conversations, hasMore: false };
|
|
128
|
+
}
|
|
129
|
+
),
|
|
130
|
+
create: jest.fn(
|
|
131
|
+
async (params: {
|
|
132
|
+
memorySpaceId: string;
|
|
133
|
+
conversationId: string;
|
|
134
|
+
type: string;
|
|
135
|
+
participants: { userId?: string; agentId?: string };
|
|
136
|
+
metadata?: Record<string, unknown>;
|
|
137
|
+
}) => {
|
|
138
|
+
const now = Date.now();
|
|
139
|
+
const conversation = {
|
|
140
|
+
conversationId: params.conversationId,
|
|
141
|
+
memorySpaceId: params.memorySpaceId,
|
|
142
|
+
type: params.type,
|
|
143
|
+
participants: params.participants,
|
|
144
|
+
metadata: params.metadata || {},
|
|
145
|
+
createdAt: now,
|
|
146
|
+
updatedAt: now,
|
|
147
|
+
messageCount: 0,
|
|
148
|
+
};
|
|
149
|
+
conversationsStore.set(params.conversationId, conversation);
|
|
150
|
+
return conversation;
|
|
151
|
+
}
|
|
152
|
+
),
|
|
153
|
+
get: jest.fn(async (conversationId: string, options?: { includeMessages?: boolean; messageLimit?: number }) => {
|
|
154
|
+
const conv = conversationsStore.get(conversationId);
|
|
155
|
+
if (!conv) return null;
|
|
156
|
+
|
|
157
|
+
// If includeMessages is requested, return with messages
|
|
158
|
+
if (options?.includeMessages) {
|
|
159
|
+
return {
|
|
160
|
+
...conv,
|
|
161
|
+
messages: conv.messages || [],
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
// Otherwise return without messages
|
|
166
|
+
const { messages: _messages, ...convWithoutMessages } = conv;
|
|
167
|
+
return convWithoutMessages;
|
|
168
|
+
}),
|
|
169
|
+
delete: jest.fn(async (conversationId: string): Promise<void> => {
|
|
170
|
+
conversationsStore.delete(conversationId);
|
|
171
|
+
}),
|
|
172
|
+
update: jest.fn(
|
|
173
|
+
async (
|
|
174
|
+
conversationId: string,
|
|
175
|
+
updates: { metadata?: Record<string, unknown> }
|
|
176
|
+
) => {
|
|
177
|
+
const conv = conversationsStore.get(conversationId);
|
|
178
|
+
if (!conv) throw new Error("Conversation not found");
|
|
179
|
+
const updated = {
|
|
180
|
+
...conv,
|
|
181
|
+
metadata: { ...conv.metadata, ...updates.metadata },
|
|
182
|
+
updatedAt: Date.now(),
|
|
183
|
+
};
|
|
184
|
+
conversationsStore.set(conversationId, updated);
|
|
185
|
+
return updated;
|
|
186
|
+
}
|
|
187
|
+
),
|
|
188
|
+
},
|
|
189
|
+
memory: {
|
|
190
|
+
search: jest.fn().mockResolvedValue([]),
|
|
191
|
+
remember: jest.fn().mockResolvedValue({
|
|
192
|
+
conversation: { conversationId: "conv-1", messageIds: [] },
|
|
193
|
+
memories: [],
|
|
194
|
+
facts: [],
|
|
195
|
+
}),
|
|
196
|
+
recall: jest.fn().mockResolvedValue({
|
|
197
|
+
context: "",
|
|
198
|
+
totalResults: 0,
|
|
199
|
+
queryTimeMs: 10,
|
|
200
|
+
sources: { vector: { count: 0, items: [] } },
|
|
201
|
+
}),
|
|
202
|
+
},
|
|
203
|
+
close: jest.fn(),
|
|
204
|
+
};
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
/**
|
|
208
|
+
* Type for the mock Cortex instance
|
|
209
|
+
*/
|
|
210
|
+
export type MockCortex = ReturnType<typeof createMockCortex>;
|
|
211
|
+
|
|
212
|
+
/**
|
|
213
|
+
* Helper to seed test data into stores
|
|
214
|
+
*/
|
|
215
|
+
export const seedTestData = {
|
|
216
|
+
user: (
|
|
217
|
+
userId: string,
|
|
218
|
+
data: Record<string, unknown> = {}
|
|
219
|
+
): { userId: string; data: Record<string, unknown> } => {
|
|
220
|
+
const user = { userId, data };
|
|
221
|
+
usersStore.set(userId, user);
|
|
222
|
+
return user;
|
|
223
|
+
},
|
|
224
|
+
mutable: (namespace: string, key: string, value: unknown): void => {
|
|
225
|
+
if (!mutableStore.has(namespace)) {
|
|
226
|
+
mutableStore.set(namespace, new Map());
|
|
227
|
+
}
|
|
228
|
+
mutableStore.get(namespace)!.set(key, value);
|
|
229
|
+
},
|
|
230
|
+
conversation: (
|
|
231
|
+
conversationId: string,
|
|
232
|
+
params: {
|
|
233
|
+
memorySpaceId?: string;
|
|
234
|
+
userId?: string;
|
|
235
|
+
title?: string;
|
|
236
|
+
messages?: Array<{ role: string; content: string }>;
|
|
237
|
+
} = {}
|
|
238
|
+
) => {
|
|
239
|
+
const now = Date.now();
|
|
240
|
+
const messages = params.messages?.map((msg, i) => ({
|
|
241
|
+
id: `msg-${i}`,
|
|
242
|
+
role: msg.role,
|
|
243
|
+
content: msg.content,
|
|
244
|
+
timestamp: now + i,
|
|
245
|
+
}));
|
|
246
|
+
const conversation: StoredConversation = {
|
|
247
|
+
conversationId,
|
|
248
|
+
memorySpaceId: params.memorySpaceId || "quickstart-demo",
|
|
249
|
+
type: "user-agent",
|
|
250
|
+
participants: {
|
|
251
|
+
userId: params.userId || "test-user",
|
|
252
|
+
agentId: "quickstart-assistant",
|
|
253
|
+
},
|
|
254
|
+
metadata: { title: params.title || "Test Chat" },
|
|
255
|
+
createdAt: now,
|
|
256
|
+
updatedAt: now,
|
|
257
|
+
messageCount: messages?.length || 0,
|
|
258
|
+
messages,
|
|
259
|
+
};
|
|
260
|
+
conversationsStore.set(conversationId, conversation);
|
|
261
|
+
return conversation;
|
|
262
|
+
},
|
|
263
|
+
};
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Jest Test Setup
|
|
3
|
+
*
|
|
4
|
+
* Configures global test environment for quickstart tests.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { TextEncoder, TextDecoder } from "util";
|
|
8
|
+
|
|
9
|
+
// Polyfill TextEncoder/TextDecoder for Node.js environment
|
|
10
|
+
global.TextEncoder = TextEncoder;
|
|
11
|
+
global.TextDecoder = TextDecoder as typeof global.TextDecoder;
|
|
12
|
+
|
|
13
|
+
// Polyfill crypto for Node.js environment (required for password utilities)
|
|
14
|
+
if (typeof global.crypto === "undefined") {
|
|
15
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
16
|
+
const { webcrypto } = require("crypto");
|
|
17
|
+
global.crypto = webcrypto;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
// Polyfill btoa/atob for Node.js environment
|
|
21
|
+
if (typeof global.btoa === "undefined") {
|
|
22
|
+
global.btoa = (str: string) => Buffer.from(str, "binary").toString("base64");
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (typeof global.atob === "undefined") {
|
|
26
|
+
global.atob = (str: string) => Buffer.from(str, "base64").toString("binary");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
// Set test environment variables
|
|
30
|
+
process.env.NODE_ENV = "test";
|
|
31
|
+
process.env.CONVEX_URL = "https://test.convex.cloud";
|
|
32
|
+
|
|
33
|
+
// Suppress console output during tests (optional - comment out for debugging)
|
|
34
|
+
// global.console = {
|
|
35
|
+
// ...console,
|
|
36
|
+
// log: jest.fn(),
|
|
37
|
+
// debug: jest.fn(),
|
|
38
|
+
// info: jest.fn(),
|
|
39
|
+
// warn: jest.fn(),
|
|
40
|
+
// };
|
|
41
|
+
|
|
42
|
+
// Increase test timeout for async operations
|
|
43
|
+
jest.setTimeout(10000);
|
|
44
|
+
|
|
45
|
+
// Reset mocks between tests
|
|
46
|
+
beforeEach(() => {
|
|
47
|
+
jest.clearAllMocks();
|
|
48
|
+
});
|