@slorenzot/memento-core 0.1.0
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/drizzle.config.ts +10 -0
- package/package.json +20 -0
- package/src/MemoryEngine.test.ts +394 -0
- package/src/MemoryEngine.ts +442 -0
- package/src/db/schema.js +39 -0
- package/src/db/schema.ts +40 -0
- package/src/index.ts +2 -0
- package/src/types.ts +52 -0
- package/tsconfig.json +37 -0
- package/tsconfig.json.bak +30 -0
package/package.json
ADDED
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@slorenzot/memento-core",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Core memory engine for Memento",
|
|
5
|
+
"main": "./dist/index.js",
|
|
6
|
+
"types": "./dist/index.d.ts",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"dev": "bun tsc --watch",
|
|
9
|
+
"build": "bun tsc --declaration --declarationMap --emitDeclarationOnly --outDir ./dist",
|
|
10
|
+
"test": "bun test",
|
|
11
|
+
"typecheck": "bun tsc --noEmit"
|
|
12
|
+
},
|
|
13
|
+
"dependencies": {
|
|
14
|
+
"zod": "^3.22.4",
|
|
15
|
+
"nanoid": "^5.0.4"
|
|
16
|
+
},
|
|
17
|
+
"devDependencies": {
|
|
18
|
+
"bun-types": "^1.3.11"
|
|
19
|
+
}
|
|
20
|
+
}
|
|
@@ -0,0 +1,394 @@
|
|
|
1
|
+
import { describe, it, expect, beforeEach, afterEach } from 'bun:test';
|
|
2
|
+
import { MemoryEngine } from './MemoryEngine';
|
|
3
|
+
import type { Observation, Session, Prompt } from './types';
|
|
4
|
+
|
|
5
|
+
describe('MemoryEngine', () => {
|
|
6
|
+
let engine: MemoryEngine;
|
|
7
|
+
let testDbPath: string;
|
|
8
|
+
|
|
9
|
+
beforeEach(() => {
|
|
10
|
+
testDbPath = `/tmp/test-memento-${Date.now()}.db`;
|
|
11
|
+
engine = new MemoryEngine(testDbPath);
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
engine.close();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
describe('Session Management', () => {
|
|
19
|
+
it('should create a session successfully', async () => {
|
|
20
|
+
const session = await engine.createSession({
|
|
21
|
+
projectId: 'test-project',
|
|
22
|
+
endedAt: null,
|
|
23
|
+
metadata: { agent: 'test-agent' },
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
expect(session).toBeDefined();
|
|
27
|
+
expect(session.id).toBeDefined();
|
|
28
|
+
expect(session.uuid).toBeDefined();
|
|
29
|
+
expect(session.projectId).toBe('test-project');
|
|
30
|
+
expect(session.metadata).toEqual({ agent: 'test-agent' });
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it('should get a session by id', async () => {
|
|
34
|
+
const created = await engine.createSession({
|
|
35
|
+
projectId: 'test-project',
|
|
36
|
+
endedAt: null,
|
|
37
|
+
metadata: {},
|
|
38
|
+
});
|
|
39
|
+
|
|
40
|
+
const retrieved = await engine.getSession(created.id);
|
|
41
|
+
|
|
42
|
+
expect(retrieved).toBeDefined();
|
|
43
|
+
expect(retrieved?.id).toBe(created.id);
|
|
44
|
+
expect(retrieved?.uuid).toBe(created.uuid);
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it('should end a session', async () => {
|
|
48
|
+
const session = await engine.createSession({
|
|
49
|
+
projectId: 'test-project',
|
|
50
|
+
endedAt: null,
|
|
51
|
+
metadata: {},
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
expect(session.endedAt).toBeNull();
|
|
55
|
+
|
|
56
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
57
|
+
|
|
58
|
+
const ended = await engine.endSession(session.id);
|
|
59
|
+
|
|
60
|
+
expect(ended.endedAt).not.toBeNull();
|
|
61
|
+
expect(ended.endedAt?.getTime()).toBeGreaterThan(session.startedAt.getTime());
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it('should return null for non-existent session', async () => {
|
|
65
|
+
const session = await engine.getSession(99999);
|
|
66
|
+
expect(session).toBeNull();
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
describe('Observation Management', () => {
|
|
71
|
+
let session: Session;
|
|
72
|
+
|
|
73
|
+
beforeEach(async () => {
|
|
74
|
+
session = await engine.createSession({
|
|
75
|
+
projectId: 'test-project',
|
|
76
|
+
endedAt: null,
|
|
77
|
+
metadata: {},
|
|
78
|
+
});
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it('should create an observation successfully', async () => {
|
|
82
|
+
const observation = await engine.createObservation({
|
|
83
|
+
sessionId: session.id,
|
|
84
|
+
title: 'Test Decision',
|
|
85
|
+
content: 'This is a test decision',
|
|
86
|
+
type: 'decision',
|
|
87
|
+
topicKey: 'test-topic',
|
|
88
|
+
projectId: 'test-project',
|
|
89
|
+
metadata: { source: 'test' },
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
expect(observation).toBeDefined();
|
|
93
|
+
expect(observation.id).toBeDefined();
|
|
94
|
+
expect(observation.title).toBe('Test Decision');
|
|
95
|
+
expect(observation.type).toBe('decision');
|
|
96
|
+
expect(observation.sessionId).toBe(session.id);
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
it('should get an observation by id', async () => {
|
|
100
|
+
const created = await engine.createObservation({
|
|
101
|
+
sessionId: session.id,
|
|
102
|
+
title: 'Get Test',
|
|
103
|
+
content: 'Get this observation',
|
|
104
|
+
type: 'note',
|
|
105
|
+
topicKey: 'get-topic',
|
|
106
|
+
projectId: 'test-project',
|
|
107
|
+
metadata: {},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const retrieved = await engine.getObservation(created.id);
|
|
111
|
+
|
|
112
|
+
expect(retrieved).toBeDefined();
|
|
113
|
+
expect(retrieved?.id).toBe(created.id);
|
|
114
|
+
expect(retrieved?.title).toBe('Get Test');
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it('should update an observation', async () => {
|
|
118
|
+
const created = await engine.createObservation({
|
|
119
|
+
sessionId: session.id,
|
|
120
|
+
title: 'Original Title',
|
|
121
|
+
content: 'Original content',
|
|
122
|
+
type: 'decision',
|
|
123
|
+
topicKey: 'update-topic',
|
|
124
|
+
projectId: 'test-project',
|
|
125
|
+
metadata: {},
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
const updated = await engine.updateObservation(created.id, {
|
|
129
|
+
title: 'Updated Title',
|
|
130
|
+
content: 'Updated content',
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
expect(updated.title).toBe('Updated Title');
|
|
134
|
+
expect(updated.content).toBe('Updated content');
|
|
135
|
+
expect(updated.type).toBe('decision');
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
it('should delete an observation', async () => {
|
|
139
|
+
const created = await engine.createObservation({
|
|
140
|
+
sessionId: session.id,
|
|
141
|
+
title: 'To Delete',
|
|
142
|
+
content: 'This will be deleted',
|
|
143
|
+
type: 'note',
|
|
144
|
+
topicKey: 'delete-topic',
|
|
145
|
+
projectId: 'test-project',
|
|
146
|
+
metadata: {},
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
await engine.deleteObservation(created.id);
|
|
150
|
+
|
|
151
|
+
const retrieved = await engine.getObservation(created.id);
|
|
152
|
+
expect(retrieved).toBeNull();
|
|
153
|
+
});
|
|
154
|
+
|
|
155
|
+
it('should return null for non-existent observation', async () => {
|
|
156
|
+
const observation = await engine.getObservation(99999);
|
|
157
|
+
expect(observation).toBeNull();
|
|
158
|
+
});
|
|
159
|
+
|
|
160
|
+
it('should handle all observation types', async () => {
|
|
161
|
+
const types: Array<Observation['type']> = ['decision', 'bug', 'discovery', 'note'];
|
|
162
|
+
|
|
163
|
+
for (const type of types) {
|
|
164
|
+
const observation = await engine.createObservation({
|
|
165
|
+
sessionId: session.id,
|
|
166
|
+
title: `${type} Observation`,
|
|
167
|
+
content: `Content for ${type}`,
|
|
168
|
+
type,
|
|
169
|
+
topicKey: `${type}-topic`,
|
|
170
|
+
projectId: 'test-project',
|
|
171
|
+
metadata: {},
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
expect(observation.type).toBe(type);
|
|
175
|
+
}
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it('should handle null topic key', async () => {
|
|
179
|
+
const observation = await engine.createObservation({
|
|
180
|
+
sessionId: session.id,
|
|
181
|
+
title: 'No Topic',
|
|
182
|
+
content: 'This has no topic',
|
|
183
|
+
type: 'note',
|
|
184
|
+
topicKey: null,
|
|
185
|
+
projectId: 'test-project',
|
|
186
|
+
metadata: {},
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
expect(observation.topicKey).toBeNull();
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
describe('Prompt Management', () => {
|
|
194
|
+
let session: Session;
|
|
195
|
+
|
|
196
|
+
beforeEach(async () => {
|
|
197
|
+
session = await engine.createSession({
|
|
198
|
+
projectId: 'test-project',
|
|
199
|
+
endedAt: null,
|
|
200
|
+
metadata: {},
|
|
201
|
+
});
|
|
202
|
+
});
|
|
203
|
+
|
|
204
|
+
it('should save a prompt successfully', async () => {
|
|
205
|
+
const prompt = await engine.savePrompt({
|
|
206
|
+
sessionId: session.id,
|
|
207
|
+
content: 'This is a test prompt',
|
|
208
|
+
projectId: 'test-project',
|
|
209
|
+
metadata: { length: 21 },
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
expect(prompt).toBeDefined();
|
|
213
|
+
expect(prompt.id).toBeDefined();
|
|
214
|
+
expect(prompt.content).toBe('This is a test prompt');
|
|
215
|
+
expect(prompt.sessionId).toBe(session.id);
|
|
216
|
+
expect(prompt.metadata).toEqual({ length: 21 });
|
|
217
|
+
});
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
describe('Search Functionality', () => {
|
|
221
|
+
let session: Session;
|
|
222
|
+
|
|
223
|
+
beforeEach(async () => {
|
|
224
|
+
session = await engine.createSession({
|
|
225
|
+
projectId: 'search-project',
|
|
226
|
+
endedAt: null,
|
|
227
|
+
metadata: {},
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
await engine.createObservation({
|
|
231
|
+
sessionId: session.id,
|
|
232
|
+
title: 'Authentication Fix',
|
|
233
|
+
content: 'Fixed the JWT authentication bug',
|
|
234
|
+
type: 'bug',
|
|
235
|
+
topicKey: 'auth',
|
|
236
|
+
projectId: 'search-project',
|
|
237
|
+
metadata: {},
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
await engine.createObservation({
|
|
241
|
+
sessionId: session.id,
|
|
242
|
+
title: 'Performance Decision',
|
|
243
|
+
content: 'Decided to use Redis for caching',
|
|
244
|
+
type: 'decision',
|
|
245
|
+
topicKey: 'performance',
|
|
246
|
+
projectId: 'search-project',
|
|
247
|
+
metadata: {},
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
await engine.createObservation({
|
|
251
|
+
sessionId: session.id,
|
|
252
|
+
title: 'Database Discovery',
|
|
253
|
+
content: 'Found a better way to query the database',
|
|
254
|
+
type: 'discovery',
|
|
255
|
+
topicKey: 'database',
|
|
256
|
+
projectId: 'search-project',
|
|
257
|
+
metadata: {},
|
|
258
|
+
});
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it('should search all observations', async () => {
|
|
262
|
+
const result = await engine.search({});
|
|
263
|
+
|
|
264
|
+
expect(result.observations.length).toBeGreaterThanOrEqual(3);
|
|
265
|
+
expect(result.total).toBeGreaterThanOrEqual(3);
|
|
266
|
+
});
|
|
267
|
+
|
|
268
|
+
it('should search by type', async () => {
|
|
269
|
+
const result = await engine.search({ type: 'bug' });
|
|
270
|
+
|
|
271
|
+
expect(result.observations).toHaveLength(1);
|
|
272
|
+
expect(result.observations[0].type).toBe('bug');
|
|
273
|
+
expect(result.observations[0].title).toContain('Authentication');
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it('should search by project id', async () => {
|
|
277
|
+
const result = await engine.search({ projectId: 'search-project' });
|
|
278
|
+
|
|
279
|
+
expect(result.observations.length).toBeGreaterThanOrEqual(3);
|
|
280
|
+
result.observations.forEach((obs) => {
|
|
281
|
+
expect(obs.projectId).toBe('search-project');
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
it('should search by topic key', async () => {
|
|
286
|
+
const result = await engine.search({ topicKey: 'auth' });
|
|
287
|
+
|
|
288
|
+
expect(result.observations).toHaveLength(1);
|
|
289
|
+
expect(result.observations[0].topicKey).toBe('auth');
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it('should limit results', async () => {
|
|
293
|
+
const result = await engine.search({ limit: 2 });
|
|
294
|
+
|
|
295
|
+
expect(result.observations.length).toBeLessThanOrEqual(2);
|
|
296
|
+
});
|
|
297
|
+
|
|
298
|
+
it('should offset results', async () => {
|
|
299
|
+
const firstPage = await engine.search({ limit: 2, offset: 0 });
|
|
300
|
+
const secondPage = await engine.search({ limit: 2, offset: 2 });
|
|
301
|
+
|
|
302
|
+
expect(firstPage.observations.length).toBe(2);
|
|
303
|
+
if (secondPage.observations.length > 0) {
|
|
304
|
+
const firstIds = firstPage.observations.map((o) => o.id);
|
|
305
|
+
const secondIds = secondPage.observations.map((o) => o.id);
|
|
306
|
+
expect(firstIds).not.toEqual(secondIds);
|
|
307
|
+
}
|
|
308
|
+
});
|
|
309
|
+
|
|
310
|
+
it('should return correct total count', async () => {
|
|
311
|
+
const result = await engine.search({ projectId: 'search-project' });
|
|
312
|
+
|
|
313
|
+
expect(result.total).toBe(result.observations.length);
|
|
314
|
+
});
|
|
315
|
+
|
|
316
|
+
it('should handle empty search results', async () => {
|
|
317
|
+
const result = await engine.search({
|
|
318
|
+
projectId: 'non-existent-project',
|
|
319
|
+
});
|
|
320
|
+
|
|
321
|
+
expect(result.observations).toHaveLength(0);
|
|
322
|
+
expect(result.total).toBe(0);
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
|
|
326
|
+
describe('Metadata Handling', () => {
|
|
327
|
+
let session: Session;
|
|
328
|
+
|
|
329
|
+
beforeEach(async () => {
|
|
330
|
+
session = await engine.createSession({
|
|
331
|
+
projectId: 'metadata-project',
|
|
332
|
+
endedAt: null,
|
|
333
|
+
metadata: {},
|
|
334
|
+
});
|
|
335
|
+
});
|
|
336
|
+
|
|
337
|
+
it('should serialize and deserialize complex metadata', async () => {
|
|
338
|
+
const complexMetadata = {
|
|
339
|
+
nested: {
|
|
340
|
+
array: [1, 2, 3],
|
|
341
|
+
object: { key: 'value' },
|
|
342
|
+
},
|
|
343
|
+
string: 'test',
|
|
344
|
+
number: 42,
|
|
345
|
+
boolean: true,
|
|
346
|
+
nullValue: null,
|
|
347
|
+
};
|
|
348
|
+
|
|
349
|
+
const observation = await engine.createObservation({
|
|
350
|
+
sessionId: session.id,
|
|
351
|
+
title: 'Complex Metadata',
|
|
352
|
+
content: 'Testing metadata serialization',
|
|
353
|
+
type: 'note',
|
|
354
|
+
topicKey: 'metadata',
|
|
355
|
+
projectId: 'metadata-project',
|
|
356
|
+
metadata: complexMetadata,
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
expect(observation.metadata).toEqual(complexMetadata);
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
it('should handle empty metadata', async () => {
|
|
363
|
+
const observation = await engine.createObservation({
|
|
364
|
+
sessionId: session.id,
|
|
365
|
+
title: 'Empty Metadata',
|
|
366
|
+
content: 'No metadata here',
|
|
367
|
+
type: 'note',
|
|
368
|
+
topicKey: 'empty',
|
|
369
|
+
projectId: 'metadata-project',
|
|
370
|
+
metadata: {},
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
expect(observation.metadata).toEqual({});
|
|
374
|
+
});
|
|
375
|
+
|
|
376
|
+
it('should update metadata', async () => {
|
|
377
|
+
const created = await engine.createObservation({
|
|
378
|
+
sessionId: session.id,
|
|
379
|
+
title: 'Update Metadata Test',
|
|
380
|
+
content: 'Will update metadata',
|
|
381
|
+
type: 'note',
|
|
382
|
+
topicKey: 'update',
|
|
383
|
+
projectId: 'metadata-project',
|
|
384
|
+
metadata: { version: 1 },
|
|
385
|
+
});
|
|
386
|
+
|
|
387
|
+
const updated = await engine.updateObservation(created.id, {
|
|
388
|
+
metadata: { version: 2, changed: true },
|
|
389
|
+
});
|
|
390
|
+
|
|
391
|
+
expect(updated.metadata).toEqual({ version: 2, changed: true });
|
|
392
|
+
});
|
|
393
|
+
});
|
|
394
|
+
});
|
|
@@ -0,0 +1,442 @@
|
|
|
1
|
+
import Database from 'bun:sqlite';
|
|
2
|
+
import { nanoid } from 'nanoid';
|
|
3
|
+
import type { Observation, Session, Prompt } from './types';
|
|
4
|
+
|
|
5
|
+
export class MemoryEngine {
|
|
6
|
+
private db: Database;
|
|
7
|
+
|
|
8
|
+
constructor(dbPath: string = './data/memento.db') {
|
|
9
|
+
this.db = new Database(dbPath, { create: true });
|
|
10
|
+
this.initializeDatabase();
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
private initializeDatabase(): void {
|
|
14
|
+
this.db.exec(`
|
|
15
|
+
PRAGMA journal_mode = WAL;
|
|
16
|
+
PRAGMA foreign_keys = ON;
|
|
17
|
+
|
|
18
|
+
CREATE TABLE IF NOT EXISTS sessions (
|
|
19
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
20
|
+
uuid TEXT UNIQUE NOT NULL,
|
|
21
|
+
project_id TEXT NOT NULL,
|
|
22
|
+
started_at INTEGER NOT NULL,
|
|
23
|
+
ended_at INTEGER,
|
|
24
|
+
metadata TEXT
|
|
25
|
+
);
|
|
26
|
+
|
|
27
|
+
CREATE TABLE IF NOT EXISTS observations (
|
|
28
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
29
|
+
uuid TEXT UNIQUE NOT NULL,
|
|
30
|
+
session_id INTEGER NOT NULL,
|
|
31
|
+
title TEXT NOT NULL,
|
|
32
|
+
content TEXT NOT NULL,
|
|
33
|
+
type TEXT NOT NULL,
|
|
34
|
+
topic_key TEXT,
|
|
35
|
+
project_id TEXT NOT NULL,
|
|
36
|
+
created_at INTEGER NOT NULL,
|
|
37
|
+
metadata TEXT,
|
|
38
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
39
|
+
);
|
|
40
|
+
|
|
41
|
+
CREATE TABLE IF NOT EXISTS prompts (
|
|
42
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
43
|
+
uuid TEXT UNIQUE NOT NULL,
|
|
44
|
+
session_id INTEGER NOT NULL,
|
|
45
|
+
content TEXT NOT NULL,
|
|
46
|
+
project_id TEXT NOT NULL,
|
|
47
|
+
created_at INTEGER NOT NULL,
|
|
48
|
+
metadata TEXT,
|
|
49
|
+
FOREIGN KEY (session_id) REFERENCES sessions(id)
|
|
50
|
+
);
|
|
51
|
+
|
|
52
|
+
CREATE TABLE IF NOT EXISTS projects (
|
|
53
|
+
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
54
|
+
name TEXT UNIQUE NOT NULL,
|
|
55
|
+
created_at INTEGER NOT NULL,
|
|
56
|
+
metadata TEXT
|
|
57
|
+
);
|
|
58
|
+
|
|
59
|
+
CREATE VIRTUAL TABLE IF NOT EXISTS observations_fts USING fts5(
|
|
60
|
+
title, content, topic_key, project_id,
|
|
61
|
+
content='observations'
|
|
62
|
+
);
|
|
63
|
+
|
|
64
|
+
CREATE TRIGGER IF NOT EXISTS observations_ai AFTER INSERT ON observations BEGIN
|
|
65
|
+
INSERT INTO observations_fts(rowid, title, content, topic_key, project_id)
|
|
66
|
+
VALUES (new.id, new.title, new.content, new.topic_key, new.project_id);
|
|
67
|
+
END;
|
|
68
|
+
|
|
69
|
+
CREATE TRIGGER IF NOT EXISTS observations_ad AFTER DELETE ON observations BEGIN
|
|
70
|
+
DELETE FROM observations_fts WHERE rowid = old.id;
|
|
71
|
+
END;
|
|
72
|
+
|
|
73
|
+
CREATE TRIGGER IF NOT EXISTS observations_au AFTER UPDATE ON observations BEGIN
|
|
74
|
+
DELETE FROM observations_fts WHERE rowid = old.id;
|
|
75
|
+
INSERT INTO observations_fts(rowid, title, content, topic_key, project_id)
|
|
76
|
+
VALUES (new.id, new.title, new.content, new.topic_key, new.project_id);
|
|
77
|
+
END;
|
|
78
|
+
`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
private serialize(value: unknown): string {
|
|
82
|
+
return JSON.stringify(value);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
private deserialize(value: string | null): Record<string, unknown> {
|
|
86
|
+
if (!value) {
|
|
87
|
+
return {};
|
|
88
|
+
}
|
|
89
|
+
try {
|
|
90
|
+
return JSON.parse(value);
|
|
91
|
+
} catch {
|
|
92
|
+
return {};
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
async createObservation(data: {
|
|
97
|
+
sessionId: number;
|
|
98
|
+
title: string;
|
|
99
|
+
content: string;
|
|
100
|
+
type: Observation['type'];
|
|
101
|
+
topicKey: string | null;
|
|
102
|
+
projectId: string;
|
|
103
|
+
metadata: Record<string, unknown>;
|
|
104
|
+
}): Promise<Observation> {
|
|
105
|
+
const uuid = nanoid();
|
|
106
|
+
const createdAt = new Date();
|
|
107
|
+
const metadata = this.serialize(data.metadata);
|
|
108
|
+
|
|
109
|
+
const stmt = this.db.prepare(
|
|
110
|
+
`INSERT INTO observations (uuid, session_id, title, content, type, topic_key, project_id, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?) RETURNING id`
|
|
111
|
+
);
|
|
112
|
+
|
|
113
|
+
const result = stmt.get(
|
|
114
|
+
uuid,
|
|
115
|
+
data.sessionId,
|
|
116
|
+
data.title,
|
|
117
|
+
data.content,
|
|
118
|
+
data.type,
|
|
119
|
+
data.topicKey ?? null,
|
|
120
|
+
data.projectId,
|
|
121
|
+
createdAt.getTime(),
|
|
122
|
+
metadata
|
|
123
|
+
) as { id: number };
|
|
124
|
+
|
|
125
|
+
if (!result) {
|
|
126
|
+
throw new Error('Failed to create observation');
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const observation = await this.getObservationById(result.id);
|
|
130
|
+
if (!observation) {
|
|
131
|
+
throw new Error('Failed to retrieve created observation');
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
return observation;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async updateObservation(
|
|
138
|
+
id: number,
|
|
139
|
+
updates: {
|
|
140
|
+
title?: string;
|
|
141
|
+
content?: string;
|
|
142
|
+
type?: Observation['type'];
|
|
143
|
+
topicKey?: string | null;
|
|
144
|
+
metadata?: Record<string, unknown>;
|
|
145
|
+
}
|
|
146
|
+
): Promise<Observation> {
|
|
147
|
+
const current = await this.getObservationById(id);
|
|
148
|
+
if (!current) {
|
|
149
|
+
throw new Error('Observation not found');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const fields: string[] = [];
|
|
153
|
+
const values: (string | number | null)[] = [];
|
|
154
|
+
|
|
155
|
+
if (updates.title !== undefined) {
|
|
156
|
+
fields.push('title = ?');
|
|
157
|
+
values.push(updates.title);
|
|
158
|
+
}
|
|
159
|
+
if (updates.content !== undefined) {
|
|
160
|
+
fields.push('content = ?');
|
|
161
|
+
values.push(updates.content);
|
|
162
|
+
}
|
|
163
|
+
if (updates.type !== undefined) {
|
|
164
|
+
fields.push('type = ?');
|
|
165
|
+
values.push(updates.type);
|
|
166
|
+
}
|
|
167
|
+
if (updates.topicKey !== undefined) {
|
|
168
|
+
fields.push('topic_key = ?');
|
|
169
|
+
values.push(updates.topicKey || '');
|
|
170
|
+
}
|
|
171
|
+
if (updates.metadata !== undefined) {
|
|
172
|
+
fields.push('metadata = ?');
|
|
173
|
+
values.push(this.serialize(updates.metadata));
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
if (fields.length === 0) {
|
|
177
|
+
return current;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
values.push(id);
|
|
181
|
+
this.db.prepare(`UPDATE observations SET ${fields.join(', ')} WHERE id = ?`).run(...values);
|
|
182
|
+
|
|
183
|
+
const updated = await this.getObservationById(id);
|
|
184
|
+
if (!updated) {
|
|
185
|
+
throw new Error('Failed to update observation');
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
return updated;
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
async deleteObservation(id: number): Promise<void> {
|
|
192
|
+
this.db.prepare('DELETE FROM observations WHERE id = ?').run(id);
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
async getObservation(id: number): Promise<Observation | null> {
|
|
196
|
+
return await this.getObservationById(id);
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
async search(params: {
|
|
200
|
+
query?: string;
|
|
201
|
+
type?: Observation['type'];
|
|
202
|
+
projectId?: string;
|
|
203
|
+
topicKey?: string;
|
|
204
|
+
limit?: number;
|
|
205
|
+
offset?: number;
|
|
206
|
+
}): Promise<{ observations: Observation[]; total: number }> {
|
|
207
|
+
const { query, type, projectId, topicKey, limit = 100, offset = 0 } = params;
|
|
208
|
+
|
|
209
|
+
let sql = 'SELECT * FROM observations WHERE 1=1';
|
|
210
|
+
const values: (string | number | null)[] = [];
|
|
211
|
+
|
|
212
|
+
if (query) {
|
|
213
|
+
sql += ' AND observations_fts MATCH ?';
|
|
214
|
+
values.push(query);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (type) {
|
|
218
|
+
sql += ' AND type = ?';
|
|
219
|
+
values.push(type);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (projectId) {
|
|
223
|
+
sql += ' AND project_id = ?';
|
|
224
|
+
values.push(projectId);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
if (topicKey) {
|
|
228
|
+
sql += ' AND topic_key = ?';
|
|
229
|
+
values.push(topicKey);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const countSql = sql.replace('SELECT *', 'SELECT COUNT(*) as count');
|
|
233
|
+
const countResult = this.db.query(countSql).get(...values);
|
|
234
|
+
const total = countResult ? (countResult as { count: number }).count : 0;
|
|
235
|
+
|
|
236
|
+
sql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';
|
|
237
|
+
values.push(limit, offset);
|
|
238
|
+
|
|
239
|
+
const stmt = this.db.query(sql);
|
|
240
|
+
const rows = stmt.all(...values);
|
|
241
|
+
const observations = rows.map((row) => this.mapObservation(row as Record<string, unknown>));
|
|
242
|
+
|
|
243
|
+
return { observations, total };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async createSession(data: {
|
|
247
|
+
projectId: string;
|
|
248
|
+
endedAt: Date | null;
|
|
249
|
+
metadata: Record<string, unknown>;
|
|
250
|
+
}): Promise<Session> {
|
|
251
|
+
const uuid = nanoid();
|
|
252
|
+
const startedAt = new Date();
|
|
253
|
+
const metadata = this.serialize(data.metadata);
|
|
254
|
+
|
|
255
|
+
const stmt = this.db.prepare(
|
|
256
|
+
`INSERT INTO sessions (uuid, project_id, started_at, ended_at, metadata) VALUES (?, ?, ?, ?, ?) RETURNING id`
|
|
257
|
+
);
|
|
258
|
+
|
|
259
|
+
const result = stmt.get(
|
|
260
|
+
uuid,
|
|
261
|
+
data.projectId,
|
|
262
|
+
startedAt.getTime(),
|
|
263
|
+
data.endedAt?.getTime() ?? null,
|
|
264
|
+
metadata
|
|
265
|
+
) as { id: number };
|
|
266
|
+
|
|
267
|
+
if (!result) {
|
|
268
|
+
throw new Error('Failed to create session');
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
const session = await this.getSessionById(result.id);
|
|
272
|
+
if (!session) {
|
|
273
|
+
throw new Error('Failed to retrieve created session');
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return session;
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async endSession(id: number): Promise<Session> {
|
|
280
|
+
const session = await this.getSessionById(id);
|
|
281
|
+
if (!session) {
|
|
282
|
+
throw new Error('Session not found');
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
const endedAt = new Date();
|
|
286
|
+
this.db.prepare('UPDATE sessions SET ended_at = ? WHERE id = ?').run(endedAt.getTime(), id);
|
|
287
|
+
|
|
288
|
+
const updated = await this.getSessionById(id);
|
|
289
|
+
if (!updated) {
|
|
290
|
+
throw new Error('Failed to update session');
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return updated;
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
async getSession(id: number): Promise<Session | null> {
|
|
297
|
+
return await this.getSessionById(id);
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
async savePrompt(data: {
|
|
301
|
+
sessionId: number;
|
|
302
|
+
content: string;
|
|
303
|
+
projectId: string;
|
|
304
|
+
metadata: Record<string, unknown>;
|
|
305
|
+
}): Promise<Prompt> {
|
|
306
|
+
const uuid = nanoid();
|
|
307
|
+
const createdAt = new Date();
|
|
308
|
+
const metadata = this.serialize(data.metadata);
|
|
309
|
+
|
|
310
|
+
const stmt = this.db.prepare(
|
|
311
|
+
`INSERT INTO prompts (uuid, session_id, content, project_id, created_at, metadata) VALUES (?, ?, ?, ?, ?, ?) RETURNING id`
|
|
312
|
+
);
|
|
313
|
+
|
|
314
|
+
const result = stmt.get(
|
|
315
|
+
uuid,
|
|
316
|
+
data.sessionId,
|
|
317
|
+
data.content,
|
|
318
|
+
data.projectId,
|
|
319
|
+
createdAt.getTime(),
|
|
320
|
+
metadata
|
|
321
|
+
) as { id: number };
|
|
322
|
+
|
|
323
|
+
if (!result) {
|
|
324
|
+
throw new Error('Failed to save prompt');
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const prompt = await this.getPromptById(result.id);
|
|
328
|
+
|
|
329
|
+
if (!prompt) {
|
|
330
|
+
throw new Error('Failed to retrieve saved prompt');
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
return prompt;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
private async getObservationById(id: number): Promise<Observation | null> {
|
|
337
|
+
const stmt = this.db.prepare('SELECT * FROM observations WHERE id = ?');
|
|
338
|
+
const row = stmt.get(id);
|
|
339
|
+
|
|
340
|
+
if (!row) {
|
|
341
|
+
return null;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
return this.mapObservation(row as Record<string, unknown>);
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
private async getSessionById(id: number): Promise<Session | null> {
|
|
348
|
+
const stmt = this.db.prepare('SELECT * FROM sessions WHERE id = ?');
|
|
349
|
+
const row = stmt.get(id);
|
|
350
|
+
|
|
351
|
+
if (!row) {
|
|
352
|
+
return null;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return this.mapSession(row as Record<string, unknown>);
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
private async getPromptById(id: number): Promise<Prompt | null> {
|
|
359
|
+
const stmt = this.db.prepare('SELECT * FROM prompts WHERE id = ?');
|
|
360
|
+
const row = stmt.get(id);
|
|
361
|
+
|
|
362
|
+
if (!row) {
|
|
363
|
+
return null;
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
return this.mapPrompt(row as Record<string, unknown>);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
private mapObservation(row: Record<string, unknown>): Observation {
|
|
370
|
+
const r = row as {
|
|
371
|
+
id: number;
|
|
372
|
+
uuid: string;
|
|
373
|
+
session_id: number;
|
|
374
|
+
title: string;
|
|
375
|
+
content: string;
|
|
376
|
+
type: Observation['type'];
|
|
377
|
+
topic_key: string | null;
|
|
378
|
+
project_id: string;
|
|
379
|
+
created_at: number;
|
|
380
|
+
metadata: string | null;
|
|
381
|
+
};
|
|
382
|
+
|
|
383
|
+
return {
|
|
384
|
+
id: r.id,
|
|
385
|
+
uuid: r.uuid,
|
|
386
|
+
sessionId: r.session_id,
|
|
387
|
+
title: r.title,
|
|
388
|
+
content: r.content,
|
|
389
|
+
type: r.type,
|
|
390
|
+
topicKey: r.topic_key,
|
|
391
|
+
projectId: r.project_id,
|
|
392
|
+
createdAt: new Date(r.created_at),
|
|
393
|
+
metadata: this.deserialize(r.metadata),
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
private mapSession(row: Record<string, unknown>): Session {
|
|
398
|
+
const r = row as {
|
|
399
|
+
id: number;
|
|
400
|
+
uuid: string;
|
|
401
|
+
project_id: string;
|
|
402
|
+
started_at: number;
|
|
403
|
+
ended_at: number | null;
|
|
404
|
+
metadata: string | null;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
return {
|
|
408
|
+
id: r.id,
|
|
409
|
+
uuid: r.uuid,
|
|
410
|
+
projectId: r.project_id,
|
|
411
|
+
startedAt: new Date(r.started_at),
|
|
412
|
+
endedAt: r.ended_at ? new Date(r.ended_at) : null,
|
|
413
|
+
metadata: this.deserialize(r.metadata),
|
|
414
|
+
};
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
private mapPrompt(row: Record<string, unknown>): Prompt {
|
|
418
|
+
const r = row as {
|
|
419
|
+
id: number;
|
|
420
|
+
uuid: string;
|
|
421
|
+
session_id: number;
|
|
422
|
+
content: string;
|
|
423
|
+
project_id: string;
|
|
424
|
+
created_at: number;
|
|
425
|
+
metadata: string | null;
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
return {
|
|
429
|
+
id: r.id,
|
|
430
|
+
uuid: r.uuid,
|
|
431
|
+
sessionId: r.session_id,
|
|
432
|
+
content: r.content,
|
|
433
|
+
projectId: r.project_id,
|
|
434
|
+
createdAt: new Date(r.created_at),
|
|
435
|
+
metadata: this.deserialize(r.metadata),
|
|
436
|
+
};
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
close(): void {
|
|
440
|
+
this.db.close();
|
|
441
|
+
}
|
|
442
|
+
}
|
package/src/db/schema.js
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.projects = exports.prompts = exports.observations = exports.sessions = void 0;
|
|
4
|
+
const sqlite_core_1 = require("drizzle-orm/sqlite-core");
|
|
5
|
+
exports.sessions = (0, sqlite_core_1.sqliteTable)('sessions', {
|
|
6
|
+
id: (0, sqlite_core_1.integer)('id').primaryKey({ autoIncrement: true }),
|
|
7
|
+
uuid: (0, sqlite_core_1.text)('uuid').unique().notNull(),
|
|
8
|
+
projectId: (0, sqlite_core_1.text)('project_id').notNull(),
|
|
9
|
+
startedAt: (0, sqlite_core_1.integer)('started_at').notNull(),
|
|
10
|
+
endedAt: (0, sqlite_core_1.integer)('ended_at'),
|
|
11
|
+
metadata: (0, sqlite_core_1.text)('metadata'),
|
|
12
|
+
});
|
|
13
|
+
exports.observations = (0, sqlite_core_1.sqliteTable)('observations', {
|
|
14
|
+
id: (0, sqlite_core_1.integer)('id').primaryKey({ autoIncrement: true }),
|
|
15
|
+
uuid: (0, sqlite_core_1.text)('uuid').unique().notNull(),
|
|
16
|
+
sessionId: (0, sqlite_core_1.integer)('session_id').notNull(),
|
|
17
|
+
title: (0, sqlite_core_1.text)('title').notNull(),
|
|
18
|
+
content: (0, sqlite_core_1.text)('content').notNull(),
|
|
19
|
+
type: (0, sqlite_core_1.text)('type').notNull(),
|
|
20
|
+
topicKey: (0, sqlite_core_1.text)('topic_key'),
|
|
21
|
+
projectId: (0, sqlite_core_1.text)('project_id').notNull(),
|
|
22
|
+
createdAt: (0, sqlite_core_1.integer)('created_at').notNull(),
|
|
23
|
+
metadata: (0, sqlite_core_1.text)('metadata'),
|
|
24
|
+
});
|
|
25
|
+
exports.prompts = (0, sqlite_core_1.sqliteTable)('prompts', {
|
|
26
|
+
id: (0, sqlite_core_1.integer)('id').primaryKey({ autoIncrement: true }),
|
|
27
|
+
uuid: (0, sqlite_core_1.text)('uuid').unique().notNull(),
|
|
28
|
+
sessionId: (0, sqlite_core_1.integer)('session_id').notNull(),
|
|
29
|
+
content: (0, sqlite_core_1.text)('content').notNull(),
|
|
30
|
+
projectId: (0, sqlite_core_1.text)('project_id').notNull(),
|
|
31
|
+
createdAt: (0, sqlite_core_1.integer)('created_at').notNull(),
|
|
32
|
+
metadata: (0, sqlite_core_1.text)('metadata'),
|
|
33
|
+
});
|
|
34
|
+
exports.projects = (0, sqlite_core_1.sqliteTable)('projects', {
|
|
35
|
+
id: (0, sqlite_core_1.integer)('id').primaryKey({ autoIncrement: true }),
|
|
36
|
+
name: (0, sqlite_core_1.text)('name').unique().notNull(),
|
|
37
|
+
createdAt: (0, sqlite_core_1.integer)('created_at').notNull(),
|
|
38
|
+
metadata: (0, sqlite_core_1.text)('metadata'),
|
|
39
|
+
});
|
package/src/db/schema.ts
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { sqliteTable, text, integer } from 'drizzle-orm/sqlite-core';
|
|
2
|
+
|
|
3
|
+
export const sessions = sqliteTable('sessions', {
|
|
4
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
5
|
+
uuid: text('uuid').unique().notNull(),
|
|
6
|
+
projectId: text('project_id').notNull(),
|
|
7
|
+
startedAt: integer('started_at').notNull(),
|
|
8
|
+
endedAt: integer('ended_at'),
|
|
9
|
+
metadata: text('metadata'),
|
|
10
|
+
});
|
|
11
|
+
|
|
12
|
+
export const observations = sqliteTable('observations', {
|
|
13
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
14
|
+
uuid: text('uuid').unique().notNull(),
|
|
15
|
+
sessionId: integer('session_id').notNull(),
|
|
16
|
+
title: text('title').notNull(),
|
|
17
|
+
content: text('content').notNull(),
|
|
18
|
+
type: text('type').notNull(),
|
|
19
|
+
topicKey: text('topic_key'),
|
|
20
|
+
projectId: text('project_id').notNull(),
|
|
21
|
+
createdAt: integer('created_at').notNull(),
|
|
22
|
+
metadata: text('metadata'),
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
export const prompts = sqliteTable('prompts', {
|
|
26
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
27
|
+
uuid: text('uuid').unique().notNull(),
|
|
28
|
+
sessionId: integer('session_id').notNull(),
|
|
29
|
+
content: text('content').notNull(),
|
|
30
|
+
projectId: text('project_id').notNull(),
|
|
31
|
+
createdAt: integer('created_at').notNull(),
|
|
32
|
+
metadata: text('metadata'),
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
export const projects = sqliteTable('projects', {
|
|
36
|
+
id: integer('id').primaryKey({ autoIncrement: true }),
|
|
37
|
+
name: text('name').unique().notNull(),
|
|
38
|
+
createdAt: integer('created_at').notNull(),
|
|
39
|
+
metadata: text('metadata'),
|
|
40
|
+
});
|
package/src/index.ts
ADDED
package/src/types.ts
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
export interface Observation {
|
|
2
|
+
id: number;
|
|
3
|
+
uuid: string;
|
|
4
|
+
sessionId: number;
|
|
5
|
+
title: string;
|
|
6
|
+
content: string;
|
|
7
|
+
type: 'decision' | 'bug' | 'discovery' | 'note';
|
|
8
|
+
topicKey: string | null;
|
|
9
|
+
projectId: string;
|
|
10
|
+
createdAt: Date;
|
|
11
|
+
metadata: Record<string, unknown>;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface Session {
|
|
15
|
+
id: number;
|
|
16
|
+
uuid: string;
|
|
17
|
+
projectId: string;
|
|
18
|
+
startedAt: Date;
|
|
19
|
+
endedAt: Date | null;
|
|
20
|
+
metadata: Record<string, unknown>;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface Prompt {
|
|
24
|
+
id: number;
|
|
25
|
+
uuid: string;
|
|
26
|
+
sessionId: number;
|
|
27
|
+
content: string;
|
|
28
|
+
projectId: string;
|
|
29
|
+
createdAt: Date;
|
|
30
|
+
metadata: Record<string, unknown>;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export interface Project {
|
|
34
|
+
id: number;
|
|
35
|
+
name: string;
|
|
36
|
+
createdAt: Date;
|
|
37
|
+
metadata: Record<string, unknown>;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export interface SearchParams {
|
|
41
|
+
query?: string;
|
|
42
|
+
type?: Observation['type'];
|
|
43
|
+
projectId?: string;
|
|
44
|
+
topicKey?: string;
|
|
45
|
+
limit?: number;
|
|
46
|
+
offset?: number;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export interface SearchResult {
|
|
50
|
+
observations: Observation[];
|
|
51
|
+
total: number;
|
|
52
|
+
}
|
package/tsconfig.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"declaration": true,
|
|
4
|
+
"outDir": "./dist",
|
|
5
|
+
"rootDir": "./src",
|
|
6
|
+
"target": "ES2022",
|
|
7
|
+
"lib": ["ES2022"],
|
|
8
|
+
"module": "NodeNext",
|
|
9
|
+
"moduleResolution": "NodeNext",
|
|
10
|
+
"resolveJsonModule": true,
|
|
11
|
+
"allowJs": true,
|
|
12
|
+
"checkJs": false,
|
|
13
|
+
"strict": true,
|
|
14
|
+
"noImplicitAny": true,
|
|
15
|
+
"strictNullChecks": true,
|
|
16
|
+
"strictFunctionTypes": true,
|
|
17
|
+
"strictBindCallApply": true,
|
|
18
|
+
"strictPropertyInitialization": true,
|
|
19
|
+
"noImplicitThis": true,
|
|
20
|
+
"alwaysStrict": true,
|
|
21
|
+
"noUnusedLocals": true,
|
|
22
|
+
"noUnusedParameters": true,
|
|
23
|
+
"noImplicitReturns": true,
|
|
24
|
+
"noFallthroughCasesInSwitch": true,
|
|
25
|
+
"esModuleInterop": true,
|
|
26
|
+
"skipLibCheck": true,
|
|
27
|
+
"forceConsistentCasingInFileNames": true,
|
|
28
|
+
"allowSyntheticDefaultImports": true,
|
|
29
|
+
"isolatedModules": true,
|
|
30
|
+
"incremental": true,
|
|
31
|
+
"noUncheckedIndexedAccess": false,
|
|
32
|
+
"noImplicitOverride": false,
|
|
33
|
+
"noPropertyAccessFromIndexSignature": false
|
|
34
|
+
},
|
|
35
|
+
"include": ["src/**/*"],
|
|
36
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx"]
|
|
37
|
+
}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"target": "ES2022",
|
|
4
|
+
"lib": ["ES2022"],
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"resolveJsonModule": true,
|
|
8
|
+
"allowJs": true,
|
|
9
|
+
"checkJs": false,
|
|
10
|
+
"strict": true,
|
|
11
|
+
"noImplicitAny": true,
|
|
12
|
+
"strictNullChecks": true,
|
|
13
|
+
"strictFunctionTypes": true,
|
|
14
|
+
"strictBindCallApply": true,
|
|
15
|
+
"strictPropertyInitialization": true,
|
|
16
|
+
"noImplicitThis": true,
|
|
17
|
+
"alwaysStrict": true,
|
|
18
|
+
"noUnusedLocals": true,
|
|
19
|
+
"noUnusedParameters": true,
|
|
20
|
+
"noImplicitReturns": true,
|
|
21
|
+
"noFallthroughCasesInSwitch": true,
|
|
22
|
+
"esModuleInterop": true,
|
|
23
|
+
"skipLibCheck": true,
|
|
24
|
+
"forceConsistentCasingInFileNames": true,
|
|
25
|
+
"allowSyntheticDefaultImports": true,
|
|
26
|
+
"isolatedModules": true,
|
|
27
|
+
"incremental": true
|
|
28
|
+
},
|
|
29
|
+
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.test.tsx", "packages/web-ui"]
|
|
30
|
+
}
|