family-ai-agent 1.0.5 → 1.0.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.
- package/.letta/settings.local.json +3 -0
- package/dist/cli/index.js +6 -4
- package/dist/cli/index.js.map +1 -1
- package/dist/database/adapters/base-adapter.d.ts +81 -0
- package/dist/database/adapters/base-adapter.d.ts.map +1 -0
- package/dist/database/adapters/base-adapter.js +105 -0
- package/dist/database/adapters/base-adapter.js.map +1 -0
- package/dist/database/adapters/index.d.ts +49 -0
- package/dist/database/adapters/index.d.ts.map +1 -0
- package/dist/database/adapters/index.js +200 -0
- package/dist/database/adapters/index.js.map +1 -0
- package/dist/database/adapters/postgres-adapter.d.ts +75 -0
- package/dist/database/adapters/postgres-adapter.d.ts.map +1 -0
- package/dist/database/adapters/postgres-adapter.js +225 -0
- package/dist/database/adapters/postgres-adapter.js.map +1 -0
- package/dist/database/adapters/sqlite-adapter.d.ts +72 -0
- package/dist/database/adapters/sqlite-adapter.d.ts.map +1 -0
- package/dist/database/adapters/sqlite-adapter.js +368 -0
- package/dist/database/adapters/sqlite-adapter.js.map +1 -0
- package/dist/database/cache/cache-keys.d.ts +180 -0
- package/dist/database/cache/cache-keys.d.ts.map +1 -0
- package/dist/database/cache/cache-keys.js +107 -0
- package/dist/database/cache/cache-keys.js.map +1 -0
- package/dist/database/cache/index.d.ts +24 -0
- package/dist/database/cache/index.d.ts.map +1 -0
- package/dist/database/cache/index.js +34 -0
- package/dist/database/cache/index.js.map +1 -0
- package/dist/database/cache/query-cache.d.ts +67 -0
- package/dist/database/cache/query-cache.d.ts.map +1 -0
- package/dist/database/cache/query-cache.js +177 -0
- package/dist/database/cache/query-cache.js.map +1 -0
- package/dist/database/client.d.ts +63 -4
- package/dist/database/client.d.ts.map +1 -1
- package/dist/database/client.js +147 -59
- package/dist/database/client.js.map +1 -1
- package/dist/database/db-config.d.ts +104 -0
- package/dist/database/db-config.d.ts.map +1 -0
- package/dist/database/db-config.js +167 -0
- package/dist/database/db-config.js.map +1 -0
- package/dist/database/drizzle/index.d.ts +42 -0
- package/dist/database/drizzle/index.d.ts.map +1 -0
- package/dist/database/drizzle/index.js +48 -0
- package/dist/database/drizzle/index.js.map +1 -0
- package/dist/database/drizzle/schema/audit.d.ts +533 -0
- package/dist/database/drizzle/schema/audit.d.ts.map +1 -0
- package/dist/database/drizzle/schema/audit.js +71 -0
- package/dist/database/drizzle/schema/audit.js.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts +665 -0
- package/dist/database/drizzle/schema/checkpoints.d.ts.map +1 -0
- package/dist/database/drizzle/schema/checkpoints.js +110 -0
- package/dist/database/drizzle/schema/checkpoints.js.map +1 -0
- package/dist/database/drizzle/schema/conversations.d.ts +449 -0
- package/dist/database/drizzle/schema/conversations.d.ts.map +1 -0
- package/dist/database/drizzle/schema/conversations.js +91 -0
- package/dist/database/drizzle/schema/conversations.js.map +1 -0
- package/dist/database/drizzle/schema/documents.d.ts +600 -0
- package/dist/database/drizzle/schema/documents.d.ts.map +1 -0
- package/dist/database/drizzle/schema/documents.js +100 -0
- package/dist/database/drizzle/schema/documents.js.map +1 -0
- package/dist/database/drizzle/schema/index.d.ts +3084 -0
- package/dist/database/drizzle/schema/index.d.ts.map +1 -0
- package/dist/database/drizzle/schema/index.js +46 -0
- package/dist/database/drizzle/schema/index.js.map +1 -0
- package/dist/database/drizzle/schema/memories.d.ts +435 -0
- package/dist/database/drizzle/schema/memories.d.ts.map +1 -0
- package/dist/database/drizzle/schema/memories.js +73 -0
- package/dist/database/drizzle/schema/memories.js.map +1 -0
- package/dist/database/drizzle/schema/tasks.d.ts +565 -0
- package/dist/database/drizzle/schema/tasks.d.ts.map +1 -0
- package/dist/database/drizzle/schema/tasks.js +84 -0
- package/dist/database/drizzle/schema/tasks.js.map +1 -0
- package/dist/database/health/circuit-breaker.d.ts +81 -0
- package/dist/database/health/circuit-breaker.d.ts.map +1 -0
- package/dist/database/health/circuit-breaker.js +184 -0
- package/dist/database/health/circuit-breaker.js.map +1 -0
- package/dist/database/health/health-monitor.d.ts +69 -0
- package/dist/database/health/health-monitor.d.ts.map +1 -0
- package/dist/database/health/health-monitor.js +174 -0
- package/dist/database/health/health-monitor.js.map +1 -0
- package/dist/database/health/index.d.ts +27 -0
- package/dist/database/health/index.d.ts.map +1 -0
- package/dist/database/health/index.js +23 -0
- package/dist/database/health/index.js.map +1 -0
- package/dist/database/index.d.ts +16 -0
- package/dist/database/index.d.ts.map +1 -0
- package/dist/database/index.js +41 -0
- package/dist/database/index.js.map +1 -0
- package/dist/database/migrations/index.d.ts +34 -0
- package/dist/database/migrations/index.d.ts.map +1 -0
- package/dist/database/migrations/index.js +45 -0
- package/dist/database/migrations/index.js.map +1 -0
- package/dist/database/migrations/migrator.d.ts +77 -0
- package/dist/database/migrations/migrator.d.ts.map +1 -0
- package/dist/database/migrations/migrator.js +258 -0
- package/dist/database/migrations/migrator.js.map +1 -0
- package/dist/database/migrations/versions/001_initial.d.ts +9 -0
- package/dist/database/migrations/versions/001_initial.d.ts.map +1 -0
- package/dist/database/migrations/versions/001_initial.js +183 -0
- package/dist/database/migrations/versions/001_initial.js.map +1 -0
- package/dist/database/types.d.ts +255 -0
- package/dist/database/types.d.ts.map +1 -0
- package/dist/database/types.js +8 -0
- package/dist/database/types.js.map +1 -0
- package/dist/database/vector/embedding-cache.d.ts +92 -0
- package/dist/database/vector/embedding-cache.d.ts.map +1 -0
- package/dist/database/vector/embedding-cache.js +185 -0
- package/dist/database/vector/embedding-cache.js.map +1 -0
- package/dist/database/vector/hnsw-index.d.ts +111 -0
- package/dist/database/vector/hnsw-index.d.ts.map +1 -0
- package/dist/database/vector/hnsw-index.js +337 -0
- package/dist/database/vector/hnsw-index.js.map +1 -0
- package/dist/database/vector/index.d.ts +75 -0
- package/dist/database/vector/index.d.ts.map +1 -0
- package/dist/database/vector/index.js +213 -0
- package/dist/database/vector/index.js.map +1 -0
- package/dist/database/vector/similarity.d.ts +67 -0
- package/dist/database/vector/similarity.d.ts.map +1 -0
- package/dist/database/vector/similarity.js +176 -0
- package/dist/database/vector/similarity.js.map +1 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/package.json +6 -3
- package/src/cli/index.ts +5 -5
- package/src/database/adapters/base-adapter.ts +171 -0
- package/src/database/adapters/index.ts +224 -0
- package/src/database/adapters/postgres-adapter.ts +285 -0
- package/src/database/adapters/sqlite-adapter.ts +420 -0
- package/src/database/cache/cache-keys.ts +150 -0
- package/src/database/cache/index.ts +44 -0
- package/src/database/cache/query-cache.ts +213 -0
- package/src/database/client.ts +166 -64
- package/src/database/db-config.ts +194 -0
- package/src/database/drizzle/index.ts +66 -0
- package/src/database/drizzle/schema/audit.ts +127 -0
- package/src/database/drizzle/schema/checkpoints.ts +164 -0
- package/src/database/drizzle/schema/conversations.ts +138 -0
- package/src/database/drizzle/schema/documents.ts +157 -0
- package/src/database/drizzle/schema/index.ts +139 -0
- package/src/database/drizzle/schema/memories.ts +127 -0
- package/src/database/drizzle/schema/tasks.ts +129 -0
- package/src/database/health/circuit-breaker.ts +214 -0
- package/src/database/health/health-monitor.ts +224 -0
- package/src/database/health/index.ts +41 -0
- package/src/database/index.ts +157 -0
- package/src/database/migrations/index.ts +52 -0
- package/src/database/migrations/migrator.ts +325 -0
- package/src/database/migrations/versions/001_initial.ts +198 -0
- package/src/database/types.ts +324 -0
- package/src/database/vector/embedding-cache.ts +234 -0
- package/src/database/vector/hnsw-index.ts +452 -0
- package/src/database/vector/index.ts +292 -0
- package/src/database/vector/similarity.ts +198 -0
- package/src/index.ts +1 -1
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Schema Exports
|
|
3
|
+
*
|
|
4
|
+
* Central export for all Drizzle ORM schemas.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Conversations
|
|
8
|
+
export {
|
|
9
|
+
conversationsPg,
|
|
10
|
+
messagesPg,
|
|
11
|
+
conversationsSqlite,
|
|
12
|
+
messagesSqlite,
|
|
13
|
+
type ConversationPg,
|
|
14
|
+
type NewConversationPg,
|
|
15
|
+
type MessagePg,
|
|
16
|
+
type NewMessagePg,
|
|
17
|
+
type ConversationSqlite,
|
|
18
|
+
type NewConversationSqlite,
|
|
19
|
+
type MessageSqlite,
|
|
20
|
+
type NewMessageSqlite,
|
|
21
|
+
type Conversation,
|
|
22
|
+
type NewConversation,
|
|
23
|
+
type Message,
|
|
24
|
+
type NewMessage,
|
|
25
|
+
} from './conversations.js';
|
|
26
|
+
|
|
27
|
+
// Long-term memories
|
|
28
|
+
export {
|
|
29
|
+
longTermMemoriesPg,
|
|
30
|
+
longTermMemoriesSqlite,
|
|
31
|
+
type LongTermMemoryPg,
|
|
32
|
+
type NewLongTermMemoryPg,
|
|
33
|
+
type LongTermMemorySqlite,
|
|
34
|
+
type NewLongTermMemorySqlite,
|
|
35
|
+
type LongTermMemory,
|
|
36
|
+
type NewLongTermMemory,
|
|
37
|
+
type MemoryType,
|
|
38
|
+
type ParsedMemory,
|
|
39
|
+
} from './memories.js';
|
|
40
|
+
|
|
41
|
+
// Documents
|
|
42
|
+
export {
|
|
43
|
+
documentsPg,
|
|
44
|
+
documentChunksPg,
|
|
45
|
+
documentsSqlite,
|
|
46
|
+
documentChunksSqlite,
|
|
47
|
+
type DocumentPg,
|
|
48
|
+
type NewDocumentPg,
|
|
49
|
+
type DocumentChunkPg,
|
|
50
|
+
type NewDocumentChunkPg,
|
|
51
|
+
type DocumentSqlite,
|
|
52
|
+
type NewDocumentSqlite,
|
|
53
|
+
type DocumentChunkSqlite,
|
|
54
|
+
type NewDocumentChunkSqlite,
|
|
55
|
+
type Document,
|
|
56
|
+
type NewDocument,
|
|
57
|
+
type DocumentChunk,
|
|
58
|
+
type NewDocumentChunk,
|
|
59
|
+
type ParsedDocumentChunk,
|
|
60
|
+
} from './documents.js';
|
|
61
|
+
|
|
62
|
+
// Checkpoints
|
|
63
|
+
export {
|
|
64
|
+
checkpointsPg,
|
|
65
|
+
checkpointWritesPg,
|
|
66
|
+
checkpointsSqlite,
|
|
67
|
+
checkpointWritesSqlite,
|
|
68
|
+
type CheckpointPg,
|
|
69
|
+
type NewCheckpointPg,
|
|
70
|
+
type CheckpointWritePg,
|
|
71
|
+
type NewCheckpointWritePg,
|
|
72
|
+
type CheckpointSqlite,
|
|
73
|
+
type NewCheckpointSqlite,
|
|
74
|
+
type CheckpointWriteSqlite,
|
|
75
|
+
type NewCheckpointWriteSqlite,
|
|
76
|
+
type Checkpoint,
|
|
77
|
+
type NewCheckpoint,
|
|
78
|
+
type CheckpointWrite,
|
|
79
|
+
type NewCheckpointWrite,
|
|
80
|
+
} from './checkpoints.js';
|
|
81
|
+
|
|
82
|
+
// Audit logs
|
|
83
|
+
export {
|
|
84
|
+
auditLogsPg,
|
|
85
|
+
auditLogsSqlite,
|
|
86
|
+
type AuditLogPg,
|
|
87
|
+
type NewAuditLogPg,
|
|
88
|
+
type AuditLogSqlite,
|
|
89
|
+
type NewAuditLogSqlite,
|
|
90
|
+
type AuditLog,
|
|
91
|
+
type NewAuditLog,
|
|
92
|
+
type AuditActionType,
|
|
93
|
+
type AuditStatus,
|
|
94
|
+
} from './audit.js';
|
|
95
|
+
|
|
96
|
+
// Tasks
|
|
97
|
+
export {
|
|
98
|
+
tasksPg,
|
|
99
|
+
tasksSqlite,
|
|
100
|
+
type TaskPg,
|
|
101
|
+
type NewTaskPg,
|
|
102
|
+
type TaskSqlite,
|
|
103
|
+
type NewTaskSqlite,
|
|
104
|
+
type Task,
|
|
105
|
+
type NewTask,
|
|
106
|
+
type TaskStatus,
|
|
107
|
+
TaskPriority,
|
|
108
|
+
type TaskPriorityLevel,
|
|
109
|
+
} from './tasks.js';
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* All PostgreSQL tables
|
|
113
|
+
*/
|
|
114
|
+
export const pgSchema = {
|
|
115
|
+
conversations: () => import('./conversations.js').then((m) => m.conversationsPg),
|
|
116
|
+
messages: () => import('./conversations.js').then((m) => m.messagesPg),
|
|
117
|
+
longTermMemories: () => import('./memories.js').then((m) => m.longTermMemoriesPg),
|
|
118
|
+
documents: () => import('./documents.js').then((m) => m.documentsPg),
|
|
119
|
+
documentChunks: () => import('./documents.js').then((m) => m.documentChunksPg),
|
|
120
|
+
checkpoints: () => import('./checkpoints.js').then((m) => m.checkpointsPg),
|
|
121
|
+
checkpointWrites: () => import('./checkpoints.js').then((m) => m.checkpointWritesPg),
|
|
122
|
+
auditLogs: () => import('./audit.js').then((m) => m.auditLogsPg),
|
|
123
|
+
tasks: () => import('./tasks.js').then((m) => m.tasksPg),
|
|
124
|
+
};
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* All SQLite tables
|
|
128
|
+
*/
|
|
129
|
+
export const sqliteSchema = {
|
|
130
|
+
conversations: () => import('./conversations.js').then((m) => m.conversationsSqlite),
|
|
131
|
+
messages: () => import('./conversations.js').then((m) => m.messagesSqlite),
|
|
132
|
+
longTermMemories: () => import('./memories.js').then((m) => m.longTermMemoriesSqlite),
|
|
133
|
+
documents: () => import('./documents.js').then((m) => m.documentsSqlite),
|
|
134
|
+
documentChunks: () => import('./documents.js').then((m) => m.documentChunksSqlite),
|
|
135
|
+
checkpoints: () => import('./checkpoints.js').then((m) => m.checkpointsSqlite),
|
|
136
|
+
checkpointWrites: () => import('./checkpoints.js').then((m) => m.checkpointWritesSqlite),
|
|
137
|
+
auditLogs: () => import('./audit.js').then((m) => m.auditLogsSqlite),
|
|
138
|
+
tasks: () => import('./tasks.js').then((m) => m.tasksSqlite),
|
|
139
|
+
};
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Long-Term Memories Schema
|
|
3
|
+
*
|
|
4
|
+
* Drizzle ORM schema for vector-embedded memories.
|
|
5
|
+
* PostgreSQL uses pgvector, SQLite stores embeddings as JSON.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
pgTable,
|
|
10
|
+
uuid,
|
|
11
|
+
text,
|
|
12
|
+
real,
|
|
13
|
+
integer,
|
|
14
|
+
timestamp,
|
|
15
|
+
jsonb,
|
|
16
|
+
index,
|
|
17
|
+
} from 'drizzle-orm/pg-core';
|
|
18
|
+
import {
|
|
19
|
+
sqliteTable,
|
|
20
|
+
text as sqliteText,
|
|
21
|
+
real as sqliteReal,
|
|
22
|
+
integer as sqliteInteger,
|
|
23
|
+
} from 'drizzle-orm/sqlite-core';
|
|
24
|
+
import { sql } from 'drizzle-orm';
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// PostgreSQL Schema (with pgvector)
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Long-term memories table (PostgreSQL)
|
|
32
|
+
* Uses pgvector extension for embedding storage and similarity search
|
|
33
|
+
*/
|
|
34
|
+
export const longTermMemoriesPg = pgTable(
|
|
35
|
+
'long_term_memories',
|
|
36
|
+
{
|
|
37
|
+
id: uuid('id')
|
|
38
|
+
.primaryKey()
|
|
39
|
+
.default(sql`gen_random_uuid()`),
|
|
40
|
+
userId: text('user_id'),
|
|
41
|
+
memoryType: text('memory_type').notNull(), // 'semantic' | 'episodic' | 'procedural'
|
|
42
|
+
content: text('content').notNull(),
|
|
43
|
+
// pgvector type - stored as vector(1536)
|
|
44
|
+
// Note: Drizzle doesn't have native pgvector support, so we use customType
|
|
45
|
+
embedding: text('embedding').notNull(), // Will store as '[0.1, 0.2, ...]' format
|
|
46
|
+
importance: real('importance').default(0.5).notNull(),
|
|
47
|
+
accessCount: integer('access_count').default(0).notNull(),
|
|
48
|
+
lastAccessed: timestamp('last_accessed', { withTimezone: true }),
|
|
49
|
+
metadata: jsonb('metadata').default({}).notNull(),
|
|
50
|
+
createdAt: timestamp('created_at', { withTimezone: true })
|
|
51
|
+
.defaultNow()
|
|
52
|
+
.notNull(),
|
|
53
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
54
|
+
.defaultNow()
|
|
55
|
+
.notNull(),
|
|
56
|
+
},
|
|
57
|
+
(table) => ({
|
|
58
|
+
userIdIdx: index('memories_user_id_idx').on(table.userId),
|
|
59
|
+
memoryTypeIdx: index('memories_memory_type_idx').on(table.memoryType),
|
|
60
|
+
importanceIdx: index('memories_importance_idx').on(table.importance),
|
|
61
|
+
createdAtIdx: index('memories_created_at_idx').on(table.createdAt),
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// SQLite Schema
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Long-term memories table (SQLite)
|
|
71
|
+
* Embeddings stored as JSON string, similarity search done in-memory
|
|
72
|
+
*/
|
|
73
|
+
export const longTermMemoriesSqlite = sqliteTable('long_term_memories', {
|
|
74
|
+
id: sqliteText('id')
|
|
75
|
+
.primaryKey()
|
|
76
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
77
|
+
userId: sqliteText('user_id'),
|
|
78
|
+
memoryType: sqliteText('memory_type').notNull(),
|
|
79
|
+
content: sqliteText('content').notNull(),
|
|
80
|
+
// Embedding stored as JSON array string: "[0.1, 0.2, ...]"
|
|
81
|
+
embedding: sqliteText('embedding').notNull(),
|
|
82
|
+
importance: sqliteReal('importance').default(0.5).notNull(),
|
|
83
|
+
accessCount: sqliteInteger('access_count').default(0).notNull(),
|
|
84
|
+
lastAccessed: sqliteInteger('last_accessed', { mode: 'timestamp' }),
|
|
85
|
+
metadata: sqliteText('metadata', { mode: 'json' })
|
|
86
|
+
.notNull()
|
|
87
|
+
.$type<Record<string, unknown>>()
|
|
88
|
+
.default({}),
|
|
89
|
+
createdAt: sqliteInteger('created_at', { mode: 'timestamp' })
|
|
90
|
+
.notNull()
|
|
91
|
+
.$defaultFn(() => new Date()),
|
|
92
|
+
updatedAt: sqliteInteger('updated_at', { mode: 'timestamp' })
|
|
93
|
+
.notNull()
|
|
94
|
+
.$defaultFn(() => new Date()),
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
// =============================================================================
|
|
98
|
+
// TypeScript Types
|
|
99
|
+
// =============================================================================
|
|
100
|
+
|
|
101
|
+
export type LongTermMemoryPg = typeof longTermMemoriesPg.$inferSelect;
|
|
102
|
+
export type NewLongTermMemoryPg = typeof longTermMemoriesPg.$inferInsert;
|
|
103
|
+
|
|
104
|
+
export type LongTermMemorySqlite = typeof longTermMemoriesSqlite.$inferSelect;
|
|
105
|
+
export type NewLongTermMemorySqlite = typeof longTermMemoriesSqlite.$inferInsert;
|
|
106
|
+
|
|
107
|
+
// Unified types
|
|
108
|
+
export type LongTermMemory = LongTermMemoryPg | LongTermMemorySqlite;
|
|
109
|
+
export type NewLongTermMemory = NewLongTermMemoryPg | NewLongTermMemorySqlite;
|
|
110
|
+
|
|
111
|
+
// Memory type enum
|
|
112
|
+
export type MemoryType = 'semantic' | 'episodic' | 'procedural';
|
|
113
|
+
|
|
114
|
+
// Memory with parsed embedding
|
|
115
|
+
export interface ParsedMemory {
|
|
116
|
+
id: string;
|
|
117
|
+
userId: string | null;
|
|
118
|
+
memoryType: MemoryType;
|
|
119
|
+
content: string;
|
|
120
|
+
embedding: number[];
|
|
121
|
+
importance: number;
|
|
122
|
+
accessCount: number;
|
|
123
|
+
lastAccessed: Date | null;
|
|
124
|
+
metadata: Record<string, unknown>;
|
|
125
|
+
createdAt: Date;
|
|
126
|
+
updatedAt: Date;
|
|
127
|
+
}
|
|
@@ -0,0 +1,129 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tasks Schema
|
|
3
|
+
*
|
|
4
|
+
* Drizzle ORM schema for automation task queue.
|
|
5
|
+
* Supports scheduled and recurring tasks.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import {
|
|
9
|
+
pgTable,
|
|
10
|
+
uuid,
|
|
11
|
+
text,
|
|
12
|
+
integer,
|
|
13
|
+
timestamp,
|
|
14
|
+
jsonb,
|
|
15
|
+
index,
|
|
16
|
+
} from 'drizzle-orm/pg-core';
|
|
17
|
+
import {
|
|
18
|
+
sqliteTable,
|
|
19
|
+
text as sqliteText,
|
|
20
|
+
integer as sqliteInteger,
|
|
21
|
+
} from 'drizzle-orm/sqlite-core';
|
|
22
|
+
import { sql } from 'drizzle-orm';
|
|
23
|
+
|
|
24
|
+
// =============================================================================
|
|
25
|
+
// PostgreSQL Schema
|
|
26
|
+
// =============================================================================
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Tasks table (PostgreSQL)
|
|
30
|
+
*/
|
|
31
|
+
export const tasksPg = pgTable(
|
|
32
|
+
'tasks',
|
|
33
|
+
{
|
|
34
|
+
id: uuid('id')
|
|
35
|
+
.primaryKey()
|
|
36
|
+
.default(sql`gen_random_uuid()`),
|
|
37
|
+
userId: text('user_id'),
|
|
38
|
+
taskType: text('task_type').notNull(),
|
|
39
|
+
priority: integer('priority').default(0).notNull(),
|
|
40
|
+
status: text('status').default('pending').notNull(), // 'pending' | 'running' | 'completed' | 'failed' | 'cancelled'
|
|
41
|
+
payload: jsonb('payload').default({}).notNull(),
|
|
42
|
+
result: jsonb('result'),
|
|
43
|
+
errorMessage: text('error_message'),
|
|
44
|
+
scheduledAt: timestamp('scheduled_at', { withTimezone: true }),
|
|
45
|
+
startedAt: timestamp('started_at', { withTimezone: true }),
|
|
46
|
+
completedAt: timestamp('completed_at', { withTimezone: true }),
|
|
47
|
+
retryCount: integer('retry_count').default(0).notNull(),
|
|
48
|
+
maxRetries: integer('max_retries').default(3).notNull(),
|
|
49
|
+
createdAt: timestamp('created_at', { withTimezone: true })
|
|
50
|
+
.defaultNow()
|
|
51
|
+
.notNull(),
|
|
52
|
+
updatedAt: timestamp('updated_at', { withTimezone: true })
|
|
53
|
+
.defaultNow()
|
|
54
|
+
.notNull(),
|
|
55
|
+
},
|
|
56
|
+
(table) => ({
|
|
57
|
+
userIdIdx: index('tasks_user_id_idx').on(table.userId),
|
|
58
|
+
statusIdx: index('tasks_status_idx').on(table.status),
|
|
59
|
+
priorityIdx: index('tasks_priority_idx').on(table.priority),
|
|
60
|
+
scheduledAtIdx: index('tasks_scheduled_at_idx').on(table.scheduledAt),
|
|
61
|
+
taskTypeIdx: index('tasks_task_type_idx').on(table.taskType),
|
|
62
|
+
})
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// =============================================================================
|
|
66
|
+
// SQLite Schema
|
|
67
|
+
// =============================================================================
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Tasks table (SQLite)
|
|
71
|
+
*/
|
|
72
|
+
export const tasksSqlite = sqliteTable('tasks', {
|
|
73
|
+
id: sqliteText('id')
|
|
74
|
+
.primaryKey()
|
|
75
|
+
.$defaultFn(() => crypto.randomUUID()),
|
|
76
|
+
userId: sqliteText('user_id'),
|
|
77
|
+
taskType: sqliteText('task_type').notNull(),
|
|
78
|
+
priority: sqliteInteger('priority').default(0).notNull(),
|
|
79
|
+
status: sqliteText('status').default('pending').notNull(),
|
|
80
|
+
payload: sqliteText('payload', { mode: 'json' })
|
|
81
|
+
.notNull()
|
|
82
|
+
.$type<Record<string, unknown>>()
|
|
83
|
+
.default({}),
|
|
84
|
+
result: sqliteText('result', { mode: 'json' }).$type<Record<string, unknown>>(),
|
|
85
|
+
errorMessage: sqliteText('error_message'),
|
|
86
|
+
scheduledAt: sqliteInteger('scheduled_at', { mode: 'timestamp' }),
|
|
87
|
+
startedAt: sqliteInteger('started_at', { mode: 'timestamp' }),
|
|
88
|
+
completedAt: sqliteInteger('completed_at', { mode: 'timestamp' }),
|
|
89
|
+
retryCount: sqliteInteger('retry_count').default(0).notNull(),
|
|
90
|
+
maxRetries: sqliteInteger('max_retries').default(3).notNull(),
|
|
91
|
+
createdAt: sqliteInteger('created_at', { mode: 'timestamp' })
|
|
92
|
+
.notNull()
|
|
93
|
+
.$defaultFn(() => new Date()),
|
|
94
|
+
updatedAt: sqliteInteger('updated_at', { mode: 'timestamp' })
|
|
95
|
+
.notNull()
|
|
96
|
+
.$defaultFn(() => new Date()),
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// =============================================================================
|
|
100
|
+
// TypeScript Types
|
|
101
|
+
// =============================================================================
|
|
102
|
+
|
|
103
|
+
export type TaskPg = typeof tasksPg.$inferSelect;
|
|
104
|
+
export type NewTaskPg = typeof tasksPg.$inferInsert;
|
|
105
|
+
|
|
106
|
+
export type TaskSqlite = typeof tasksSqlite.$inferSelect;
|
|
107
|
+
export type NewTaskSqlite = typeof tasksSqlite.$inferInsert;
|
|
108
|
+
|
|
109
|
+
// Unified types
|
|
110
|
+
export type Task = TaskPg | TaskSqlite;
|
|
111
|
+
export type NewTask = NewTaskPg | NewTaskSqlite;
|
|
112
|
+
|
|
113
|
+
// Task status enum
|
|
114
|
+
export type TaskStatus =
|
|
115
|
+
| 'pending'
|
|
116
|
+
| 'running'
|
|
117
|
+
| 'completed'
|
|
118
|
+
| 'failed'
|
|
119
|
+
| 'cancelled';
|
|
120
|
+
|
|
121
|
+
// Task priority levels
|
|
122
|
+
export const TaskPriority = {
|
|
123
|
+
LOW: 0,
|
|
124
|
+
NORMAL: 5,
|
|
125
|
+
HIGH: 10,
|
|
126
|
+
URGENT: 20,
|
|
127
|
+
} as const;
|
|
128
|
+
|
|
129
|
+
export type TaskPriorityLevel = (typeof TaskPriority)[keyof typeof TaskPriority];
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Circuit Breaker
|
|
3
|
+
*
|
|
4
|
+
* Prevents cascade failures by temporarily blocking calls to failing services.
|
|
5
|
+
* Implements the circuit breaker pattern for database resilience.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import type { CircuitBreakerState, CircuitBreakerOptions } from '../types.js';
|
|
9
|
+
import { createLogger } from '../../utils/logger.js';
|
|
10
|
+
|
|
11
|
+
const logger = createLogger('CircuitBreaker');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Error thrown when circuit is open
|
|
15
|
+
*/
|
|
16
|
+
export class CircuitOpenError extends Error {
|
|
17
|
+
constructor(message: string = 'Circuit breaker is open') {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = 'CircuitOpenError';
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Default circuit breaker options
|
|
25
|
+
*/
|
|
26
|
+
const DEFAULT_OPTIONS: CircuitBreakerOptions = {
|
|
27
|
+
failureThreshold: 5,
|
|
28
|
+
resetTimeoutMs: 60000,
|
|
29
|
+
halfOpenMaxAttempts: 3,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Circuit breaker for database operations
|
|
34
|
+
*/
|
|
35
|
+
export class CircuitBreaker {
|
|
36
|
+
private state: CircuitBreakerState = 'closed';
|
|
37
|
+
private failureCount: number = 0;
|
|
38
|
+
private successCount: number = 0;
|
|
39
|
+
private lastFailureTime: number = 0;
|
|
40
|
+
private options: CircuitBreakerOptions;
|
|
41
|
+
private stateChangeListeners: Array<(state: CircuitBreakerState) => void> = [];
|
|
42
|
+
|
|
43
|
+
constructor(options: Partial<CircuitBreakerOptions> = {}) {
|
|
44
|
+
this.options = { ...DEFAULT_OPTIONS, ...options };
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Execute a function with circuit breaker protection
|
|
49
|
+
*/
|
|
50
|
+
async execute<T>(fn: () => Promise<T>): Promise<T> {
|
|
51
|
+
// Check if circuit should transition from open to half-open
|
|
52
|
+
if (this.state === 'open') {
|
|
53
|
+
const timeSinceLastFailure = Date.now() - this.lastFailureTime;
|
|
54
|
+
if (timeSinceLastFailure >= this.options.resetTimeoutMs) {
|
|
55
|
+
this.transitionTo('half-open');
|
|
56
|
+
} else {
|
|
57
|
+
throw new CircuitOpenError(
|
|
58
|
+
`Circuit breaker is open. Retry in ${Math.ceil(
|
|
59
|
+
(this.options.resetTimeoutMs - timeSinceLastFailure) / 1000
|
|
60
|
+
)} seconds.`
|
|
61
|
+
);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
try {
|
|
66
|
+
const result = await fn();
|
|
67
|
+
this.onSuccess();
|
|
68
|
+
return result;
|
|
69
|
+
} catch (error) {
|
|
70
|
+
this.onFailure();
|
|
71
|
+
throw error;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
/**
|
|
76
|
+
* Handle successful execution
|
|
77
|
+
*/
|
|
78
|
+
private onSuccess(): void {
|
|
79
|
+
this.failureCount = 0;
|
|
80
|
+
|
|
81
|
+
if (this.state === 'half-open') {
|
|
82
|
+
this.successCount++;
|
|
83
|
+
if (this.successCount >= this.options.halfOpenMaxAttempts) {
|
|
84
|
+
this.transitionTo('closed');
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Handle failed execution
|
|
91
|
+
*/
|
|
92
|
+
private onFailure(): void {
|
|
93
|
+
this.failureCount++;
|
|
94
|
+
this.lastFailureTime = Date.now();
|
|
95
|
+
this.successCount = 0;
|
|
96
|
+
|
|
97
|
+
if (this.state === 'half-open') {
|
|
98
|
+
// Immediately open on failure in half-open state
|
|
99
|
+
this.transitionTo('open');
|
|
100
|
+
} else if (this.failureCount >= this.options.failureThreshold) {
|
|
101
|
+
this.transitionTo('open');
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Transition to a new state
|
|
107
|
+
*/
|
|
108
|
+
private transitionTo(newState: CircuitBreakerState): void {
|
|
109
|
+
if (this.state === newState) return;
|
|
110
|
+
|
|
111
|
+
const oldState = this.state;
|
|
112
|
+
this.state = newState;
|
|
113
|
+
|
|
114
|
+
if (newState === 'closed') {
|
|
115
|
+
this.failureCount = 0;
|
|
116
|
+
this.successCount = 0;
|
|
117
|
+
} else if (newState === 'half-open') {
|
|
118
|
+
this.successCount = 0;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
logger.info('Circuit breaker state changed', {
|
|
122
|
+
from: oldState,
|
|
123
|
+
to: newState,
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
// Notify listeners
|
|
127
|
+
for (const listener of this.stateChangeListeners) {
|
|
128
|
+
try {
|
|
129
|
+
listener(newState);
|
|
130
|
+
} catch (error) {
|
|
131
|
+
logger.error('State change listener error', { error });
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Register a state change listener
|
|
138
|
+
*/
|
|
139
|
+
onStateChange(listener: (state: CircuitBreakerState) => void): () => void {
|
|
140
|
+
this.stateChangeListeners.push(listener);
|
|
141
|
+
return () => {
|
|
142
|
+
const index = this.stateChangeListeners.indexOf(listener);
|
|
143
|
+
if (index >= 0) {
|
|
144
|
+
this.stateChangeListeners.splice(index, 1);
|
|
145
|
+
}
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Get current state
|
|
151
|
+
*/
|
|
152
|
+
getState(): CircuitBreakerState {
|
|
153
|
+
return this.state;
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Check if circuit is open
|
|
158
|
+
*/
|
|
159
|
+
isOpen(): boolean {
|
|
160
|
+
return this.state === 'open';
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* Check if circuit is half-open
|
|
165
|
+
*/
|
|
166
|
+
isHalfOpen(): boolean {
|
|
167
|
+
return this.state === 'half-open';
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
/**
|
|
171
|
+
* Check if circuit is closed
|
|
172
|
+
*/
|
|
173
|
+
isClosed(): boolean {
|
|
174
|
+
return this.state === 'closed';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Force circuit to open state
|
|
179
|
+
*/
|
|
180
|
+
trip(): void {
|
|
181
|
+
this.lastFailureTime = Date.now();
|
|
182
|
+
this.transitionTo('open');
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Force circuit to closed state
|
|
187
|
+
*/
|
|
188
|
+
reset(): void {
|
|
189
|
+
this.transitionTo('closed');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Get circuit breaker statistics
|
|
194
|
+
*/
|
|
195
|
+
getStats(): {
|
|
196
|
+
state: CircuitBreakerState;
|
|
197
|
+
failureCount: number;
|
|
198
|
+
successCount: number;
|
|
199
|
+
lastFailureTime: number | null;
|
|
200
|
+
timeSinceLastFailure: number | null;
|
|
201
|
+
} {
|
|
202
|
+
return {
|
|
203
|
+
state: this.state,
|
|
204
|
+
failureCount: this.failureCount,
|
|
205
|
+
successCount: this.successCount,
|
|
206
|
+
lastFailureTime: this.lastFailureTime || null,
|
|
207
|
+
timeSinceLastFailure: this.lastFailureTime
|
|
208
|
+
? Date.now() - this.lastFailureTime
|
|
209
|
+
: null,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export default CircuitBreaker;
|