diff --git a/packages/pulse/src/adaptive-tick.test.ts b/packages/pulse/src/adaptive-tick.test.ts index d61f65f..5ae5442 100644 --- a/packages/pulse/src/adaptive-tick.test.ts +++ b/packages/pulse/src/adaptive-tick.test.ts @@ -40,7 +40,7 @@ describe('Adaptive tick frequency', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); @@ -89,13 +89,13 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 300)); // Check if we have any tick events at all - let tickEvents = store.queryByKind('tick'); + let tickEvents = await store.queryByKind('tick'); console.log(`Found ${tickEvents.length} tick events`); if (tickEvents.length === 0) { // Wait a bit more await new Promise((r) => setTimeout(r, 300)); - tickEvents = store.queryByKind('tick'); + tickEvents = await store.queryByKind('tick'); console.log(`After waiting longer: ${tickEvents.length} tick events`); } @@ -154,7 +154,7 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 1000)); - const tickEvents = store.queryByKind('tick'); + const tickEvents = await store.queryByKind('tick'); for (const tickEvent of tickEvents) { if (tickEvent.meta) { @@ -182,8 +182,8 @@ describe('Adaptive tick frequency', () => { it('should use custom hasActiveWork function', async () => { // Pre-populate with active topics data - const activeTopicsHash = store.putObject(['topic1', 'topic2']); - store.appendEvent({ + const activeTopicsHash = await store.putObject(['topic1', 'topic2']); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'activeTopics', @@ -220,7 +220,7 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 500)); - const tickEvents = store.queryByKind('tick'); + const tickEvents = await store.queryByKind('tick'); for (const tickEvent of tickEvents) { if (tickEvent.meta) { @@ -269,7 +269,7 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 400)); - const tickEvents = store.queryByKind('tick'); + const tickEvents = await store.queryByKind('tick'); for (const tickEvent of tickEvents) { if (tickEvent.meta) { @@ -325,8 +325,8 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 300)); // Manually write a vital event to trigger wake - const wakeData = store.putObject({ wake: true }); - store.appendEvent({ + const wakeData = await store.putObject({ wake: true }); + await store.appendEvent({ occurredAt: Date.now(), kind: 'vital', key: 'wake_signal', @@ -335,7 +335,7 @@ describe('Adaptive tick frequency', () => { await new Promise((r) => setTimeout(r, 200)); - const tickEvents = store.queryByKind('tick'); + const tickEvents = await store.queryByKind('tick'); for (const tickEvent of tickEvents) { if (tickEvent.meta) { diff --git a/packages/pulse/src/defs.test.ts b/packages/pulse/src/defs.test.ts index 0a72731..9c79fb6 100644 --- a/packages/pulse/src/defs.test.ts +++ b/packages/pulse/src/defs.test.ts @@ -26,16 +26,16 @@ describe('Definition Layer', () => { let tempDir: string; let db: Database; - beforeEach(() => { + beforeEach(async () => { tempDir = mkdtempSync(join(tmpdir(), 'pulse-defs-')); const systemDbPath = join(tempDir, '_system.db'); db = new Database(systemDbPath, { create: true }); db.exec('PRAGMA journal_mode = WAL'); - initDefsSchema(db); + await initDefsSchema(db); }); - afterEach(() => { - db?.close(); + afterEach(async () => { + await db?.close(); if (existsSync(tempDir)) { rmSync(tempDir, { recursive: true, force: true }); } @@ -43,8 +43,8 @@ describe('Definition Layer', () => { // ── Test 1: object_defs registration ────────────────────────── - it('1. registers object definitions', () => { - const objDef = registerObjectDef(db, { + it('1. registers object definitions', async () => { + const objDef = await registerObjectDef(db, { name: 'User', codeRev: 'v1.0.0', }); @@ -55,16 +55,16 @@ describe('Definition Layer', () => { expect(objDef.createdAt).toBeGreaterThan(0); // Verify it's stored - const retrieved = getObjectDef(db, 'User', 'v1.0.0'); + const retrieved = await getObjectDef(db, 'User', 'v1.0.0'); expect(retrieved).toEqual(objDef); }); // ── Test 2: event_defs registration + content hash ─────────── - it('2. registers event definitions with content-based hash', () => { + it('2. registers event definitions with content-based hash', async () => { const schema = { type: 'object', properties: { name: { type: 'string' } } }; - const eventDef = registerEventDef(db, { + const eventDef = await registerEventDef(db, { name: 'UserCreated', schema, codeRev: 'v1.0.0', @@ -77,7 +77,7 @@ describe('Definition Layer', () => { expect(eventDef.hash).toHaveLength(64); // SHA-256 hex // Same content should produce same hash - const eventDef2 = registerEventDef(db, { + const eventDef2 = await registerEventDef(db, { name: 'UserCreated', schema, codeRev: 'v1.1.0', // Different code_rev, same content @@ -88,14 +88,14 @@ describe('Definition Layer', () => { // ── Test 3: event_def version chain ─────────────────────────── - it('3. supports event_def version chains via parent_hash', () => { - const v1 = registerEventDef(db, { + it('3. supports event_def version chains via parent_hash', async () => { + const v1 = await registerEventDef(db, { name: 'UserUpdated', schema: { type: 'object', properties: { name: { type: 'string' } } }, codeRev: 'v1.0.0', }); - const v2 = registerEventDef(db, { + const v2 = await registerEventDef(db, { name: 'UserUpdated', schema: { type: 'object', @@ -112,8 +112,8 @@ describe('Definition Layer', () => { expect(v2.hash).not.toBe(v1.hash); // Different content = different hash // Verify retrieval - const retrievedV1 = getEventDef(db, 'UserUpdated', 'v1.0.0'); - const retrievedV2 = getEventDef(db, 'UserUpdated', 'v2.0.0'); + const retrievedV1 = await getEventDef(db, 'UserUpdated', 'v1.0.0'); + const retrievedV2 = await getEventDef(db, 'UserUpdated', 'v2.0.0'); expect(retrievedV1?.hash).toBe(v1.hash); expect(retrievedV2?.hash).toBe(v2.hash); @@ -147,7 +147,7 @@ describe('Definition Layer', () => { expect(typeof projDef.hash).toBe('string'); // Verify retrieval includes sources - const retrieved = getProjectionDef(db, 'UserCount', 'v1.0.0'); + const retrieved = await getProjectionDef(db, 'UserCount', 'v1.0.0'); expect(retrieved).toEqual(projDef); }); @@ -155,15 +155,15 @@ describe('Definition Layer', () => { it('5. enforces (name, code_rev) UNIQUE constraints', async () => { // Object defs - registerObjectDef(db, { name: 'Product', codeRev: 'v1.0.0' }); - expect(() => { - registerObjectDef(db, { name: 'Product', codeRev: 'v1.0.0' }); + await registerObjectDef(db, { name: 'Product', codeRev: 'v1.0.0' }); + await expect(async () => { + await registerObjectDef(db, { name: 'Product', codeRev: 'v1.0.0' }); }).toThrow('Object definition already exists: Product@v1.0.0'); // Event defs - registerEventDef(db, { name: 'ProductCreated', codeRev: 'v1.0.0' }); - expect(() => { - registerEventDef(db, { name: 'ProductCreated', codeRev: 'v1.0.0' }); + await registerEventDef(db, { name: 'ProductCreated', codeRev: 'v1.0.0' }); + await expect(async () => { + await registerEventDef(db, { name: 'ProductCreated', codeRev: 'v1.0.0' }); }).toThrow('Event definition already exists: ProductCreated@v1.0.0'); // Projection defs @@ -192,9 +192,9 @@ describe('Definition Layer', () => { it('6. queries definitions by code_rev', async () => { // Register multiple definitions across different code_rev values - registerEventDef(db, { name: 'OrderCreated', codeRev: 'v1.0.0' }); - registerEventDef(db, { name: 'OrderUpdated', codeRev: 'v1.0.0' }); - registerEventDef(db, { name: 'OrderDeleted', codeRev: 'v2.0.0' }); + await registerEventDef(db, { name: 'OrderCreated', codeRev: 'v1.0.0' }); + await registerEventDef(db, { name: 'OrderUpdated', codeRev: 'v1.0.0' }); + await registerEventDef(db, { name: 'OrderDeleted', codeRev: 'v2.0.0' }); await registerProjectionDef(db, { name: 'OrderStats', @@ -209,8 +209,8 @@ describe('Definition Layer', () => { codeRev: 'v2.0.0', }); - const v1Events = listEventDefs(db, { codeRev: 'v1.0.0' }); - const v2Events = listEventDefs(db, { codeRev: 'v2.0.0' }); + const v1Events = await listEventDefs(db, { codeRev: 'v1.0.0' }); + const v2Events = await listEventDefs(db, { codeRev: 'v2.0.0' }); expect(v1Events).toHaveLength(2); expect(v1Events.map((e) => e.name).sort()).toEqual([ @@ -221,8 +221,8 @@ describe('Definition Layer', () => { expect(v2Events).toHaveLength(1); expect(v2Events[0].name).toBe('OrderDeleted'); - const v1Projections = listProjectionDefs(db, { codeRev: 'v1.0.0' }); - const v2Projections = listProjectionDefs(db, { codeRev: 'v2.0.0' }); + const v1Projections = await listProjectionDefs(db, { codeRev: 'v1.0.0' }); + const v2Projections = await listProjectionDefs(db, { codeRev: 'v2.0.0' }); expect(v1Projections).toHaveLength(1); expect(v1Projections[0].name).toBe('OrderStats'); @@ -298,12 +298,12 @@ describe('Definition Layer', () => { it('9. comprehensive integration test', async () => { // Register a complete set of definitions - const objDef = registerObjectDef(db, { + const objDef = await registerObjectDef(db, { name: 'Invoice', codeRev: 'v1.2.0', }); - const eventDef = registerEventDef(db, { + const eventDef = await registerEventDef(db, { name: 'InvoiceCreated', schema: { type: 'object', @@ -340,13 +340,13 @@ describe('Definition Layer', () => { }); // Verify all definitions can be retrieved - expect(getObjectDef(db, 'Invoice', 'v1.2.0')).toEqual(objDef); - expect(getEventDef(db, 'InvoiceCreated', 'v1.2.0')).toEqual(eventDef); - expect(getProjectionDef(db, 'InvoiceSummary', 'v1.2.0')).toEqual(projDef); + expect(await getObjectDef(db, 'Invoice', 'v1.2.0')).toEqual(objDef); + expect(await getEventDef(db, 'InvoiceCreated', 'v1.2.0')).toEqual(eventDef); + expect(await getProjectionDef(db, 'InvoiceSummary', 'v1.2.0')).toEqual(projDef); // Verify listing works - const events = listEventDefs(db, { codeRev: 'v1.2.0' }); - const projections = listProjectionDefs(db, { codeRev: 'v1.2.0' }); + const events = await listEventDefs(db, { codeRev: 'v1.2.0' }); + const projections = await listProjectionDefs(db, { codeRev: 'v1.2.0' }); expect(events.find((e) => e.name === 'InvoiceCreated')).toBeDefined(); expect(projections.find((p) => p.name === 'InvoiceSummary')).toBeDefined(); diff --git a/packages/pulse/src/e2e/t11-council-v2.test.ts b/packages/pulse/src/e2e/t11-council-v2.test.ts index 61213cd..05fbe64 100644 --- a/packages/pulse/src/e2e/t11-council-v2.test.ts +++ b/packages/pulse/src/e2e/t11-council-v2.test.ts @@ -24,9 +24,9 @@ describe('Council v2 E2E', () => { }); } - afterEach(() => { + afterEach(async () => { try { - store?.close(); + await store?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); @@ -37,8 +37,8 @@ describe('Council v2 E2E', () => { description: string, repoDir: string, ) { - const hash = store.putObject(description); - store.appendEvent({ + const hash = await store.putObject(description); + await store.appendEvent({ occurredAt: Date.now(), kind: 'coding.__start__', key: topicId, @@ -80,13 +80,13 @@ describe('Council v2 E2E', () => { } // Verify both closed - const closedEvents = store.queryByKind('coding.closer'); + const closedEvents = await store.queryByKind('coding.closer'); expect(closedEvents.length).toBe(2); const closedIds = closedEvents.map((e) => e.key); expect(closedIds.sort()).toEqual(['task-a', 'task-b']); // task-b should have been coded twice - const codedEvents = store.queryByKind('coding.coder'); + const codedEvents = await store.queryByKind('coding.coder'); const taskBCoded = codedEvents.filter((e) => e.key === 'task-b'); expect(taskBCoded.length).toBe(2); diff --git a/packages/pulse/src/e2e/t7-survival.test.ts b/packages/pulse/src/e2e/t7-survival.test.ts index 9f6fbc3..09ea7fd 100644 --- a/packages/pulse/src/e2e/t7-survival.test.ts +++ b/packages/pulse/src/e2e/t7-survival.test.ts @@ -53,8 +53,8 @@ function writeVitalAndRebuild( key: string, data: unknown, ): SurvivalSnapshot { - const hash = stores.vitals.putObject(data); - stores.vitals.appendEvent({ + const hash = await stores.vitals.putObject(data); + await stores.vitals.appendEvent({ occurredAt: Date.now(), kind: 'vital', key, @@ -76,9 +76,9 @@ describe('E2E survival chain', () => { stores = makeStores(tmpDir); }); - afterEach(() => { - stores.system.close(); - stores.vitals.close(); + afterEach(async () => { + await stores.system.close(); + await stores.vitals.close(); rmSync(tmpDir, { recursive: true, force: true }); }); @@ -225,7 +225,7 @@ describe('E2E survival chain', () => { const now = Date.now(); // Record a promote event within the rollback window - stores.system.appendEvent({ + await stores.system.appendEvent({ occurredAt: now - 60_000, // 1 minute ago kind: 'promote', codeRev: 'v2', @@ -234,7 +234,7 @@ describe('E2E survival chain', () => { // Record enough error events to exceed ROLLBACK_ERROR_THRESHOLD for (let i = 0; i < ROLLBACK_ERROR_THRESHOLD; i++) { - stores.system.appendEvent({ + await stores.system.appendEvent({ occurredAt: now - 30_000 + i * 1000, kind: 'error', meta: JSON.stringify({ message: `error ${i}` }), @@ -265,7 +265,7 @@ describe('E2E survival chain', () => { // Simulate MAX_RESTART_COUNT restarts already recorded in events const now = Date.now(); for (let i = 0; i < MAX_RESTART_COUNT; i++) { - stores.system.appendEvent({ + await stores.system.appendEvent({ occurredAt: now - (MAX_RESTART_COUNT - i) * 10_000, kind: 'effect', meta: JSON.stringify({ type: 'restart-service', service: 'upulse' }), @@ -354,8 +354,8 @@ describe('E2E survival chain', () => { const initialData = { diskPct: 40, memoryPct: 50, cpuPct: 10, swapPct: 0 }; const alarmedData = { diskPct: 96, memoryPct: 50, cpuPct: 10, swapPct: 0 }; // disk crisis - const h0 = stores.vitals.putObject(initialData); - stores.vitals.appendEvent({ + const h0 = await stores.vitals.putObject(initialData); + await stores.vitals.appendEvent({ occurredAt: Date.now() - 5000, kind: 'vital', key: 'system', @@ -370,8 +370,8 @@ describe('E2E survival chain', () => { wakeTriggered = true; }; - const h1 = stores.vitals.putObject(alarmedData); - stores.vitals.appendEvent({ + const h1 = await stores.vitals.putObject(alarmedData); + await stores.vitals.appendEvent({ occurredAt: Date.now(), kind: 'vital', key: 'system', @@ -382,7 +382,7 @@ describe('E2E survival chain', () => { .queryByKind('vital', { key: 'system', limit: 12 }) .map((v) => ({ ...v, - data: v.hash ? stores.vitals.getObject(v.hash) : null, + await data: v.hash ? stores.vitals.getObject(v.hash) : null, })); const shouldWakeResult = latestVitals.some( (v) => v.data && (v.data as { diskPct?: number }).diskPct! > 95, @@ -426,8 +426,8 @@ describe('E2E survival chain', () => { ['llm', llmData], ['network', networkData], ] as const) { - const hash = stores.vitals.putObject(data); - stores.vitals.appendEvent({ + const hash = await stores.vitals.putObject(data); + await stores.vitals.appendEvent({ occurredAt: Date.now(), kind: 'vital', key, diff --git a/packages/pulse/src/e2e/t9-int-id.test.ts b/packages/pulse/src/e2e/t9-int-id.test.ts index b867cd7..8478d41 100644 --- a/packages/pulse/src/e2e/t9-int-id.test.ts +++ b/packages/pulse/src/e2e/t9-int-id.test.ts @@ -54,15 +54,15 @@ describe('E2E T9: INT ID lifecycle', () => { }); }); - afterEach(() => { - store.close(); + afterEach(async () => { + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); // ── 1. Basic event ID properties ────────────────────────────── - test('1: appendEvent returns positive integer id', () => { - const event = store.appendEvent({ + test('1: appendEvent returns positive integer id', async () => { + const event = await store.appendEvent({ occurredAt: Date.now(), kind: 'tick', key: 'sys', @@ -72,10 +72,10 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 2. Sequential monotonic IDs ────────────────────────────── - test('2: 100 events produce strictly increasing integer IDs', () => { + test('2: 100 events produce strictly increasing integer IDs', async () => { const ids: number[] = []; for (let i = 0; i < 100; i++) { - const e = store.appendEvent({ + const e = await store.appendEvent({ occurredAt: Date.now(), kind: i % 3 === 0 ? 'tick' : i % 3 === 1 ? 'collect' : 'effect', key: `k${i}`, @@ -93,14 +93,14 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 3. Batch insert preserves int IDs ────────────────────────── - test('3: appendEvents batch returns sequential integer IDs', () => { + test('3: appendEvents batch returns sequential integer IDs', async () => { const batch = Array.from({ length: 50 }, (_, i) => ({ occurredAt: Date.now() + i, kind: 'vital' as const, key: `sensor_${i}`, })); - const results = store.appendEvents(batch); + const results = await store.appendEvents(batch); expect(results.length).toBe(50); for (const r of results) { @@ -114,15 +114,15 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 4. Object ID is integer ──────────────────────────────────── - test('4: createObject returns integer id, getObjectInstance roundtrips', () => { - const objId = store.createObject({ + test('4: createObject returns integer id, getObjectInstance roundtrips', async () => { + const objId = await store.createObject({ objectType: 'session', externalId: 'sess_abc', codeRev: 'v1', }); assertIntId(objId); - const instance = store.getObjectInstance(objId); + const instance = await store.getObjectInstance(objId); expect(instance).not.toBeNull(); expect(instance!.id).toBe(objId); assertIntId(instance!.id); @@ -130,15 +130,15 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 5. Event→Object FK roundtrip ────────────────────────────── - test('5: event.objectId FK references object.id as integer', () => { - const objId = store.createObject({ + test('5: event.objectId FK references object.id as integer', async () => { + const objId = await store.createObject({ objectType: 'task', externalId: 'task_42', codeRev: 'v1', }); assertIntId(objId); - const event = store.appendEvent({ + const event = await store.appendEvent({ occurredAt: Date.now(), kind: 'task-created', key: 'task_42', @@ -147,7 +147,7 @@ describe('E2E T9: INT ID lifecycle', () => { assertIntId(event.id); expect(event.objectId).toBe(objId); - const latest = store.getLatest('task-created', 'task_42'); + const latest = await store.getLatest('task-created', 'task_42'); expect(latest).not.toBeNull(); assertIntId(latest!.id); expect(latest!.objectId).toBe(objId); @@ -156,11 +156,11 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 6. getAfter cursor with int IDs ──────────────────────────── - test('6: getAfter(id) cursor correctly paginates with int IDs', () => { + test('6: getAfter(id) cursor correctly paginates with int IDs', async () => { const events = []; for (let i = 0; i < 20; i++) { events.push( - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000 + i * 100, kind: i % 2 === 0 ? 'tick' : 'collect', key: `k${i}`, @@ -171,7 +171,7 @@ describe('E2E T9: INT ID lifecycle', () => { const cursor = events[4]!.id; assertIntId(cursor); - const after = store.getAfter(cursor); + const after = await store.getAfter(cursor); expect(after.length).toBe(15); for (const e of after) { @@ -187,14 +187,14 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 7. getAfter with kind filter ────────────────────────────── - test('7: getAfter(id, {kind}) filters correctly with int IDs', () => { - const e1 = store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - store.appendEvent({ occurredAt: 2000, kind: 'collect' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick' }); - store.appendEvent({ occurredAt: 4000, kind: 'effect' }); - store.appendEvent({ occurredAt: 5000, kind: 'tick' }); + test('7: getAfter(id, {kind}) filters correctly with int IDs', async () => { + const e1 = await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 2000, kind: 'collect' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 4000, kind: 'effect' }); + await store.appendEvent({ occurredAt: 5000, kind: 'tick' }); - const afterTicks = store.getAfter(e1.id, { kind: 'tick' }); + const afterTicks = await store.getAfter(e1.id, { kind: 'tick' }); expect(afterTicks.length).toBe(2); for (const e of afterTicks) { @@ -206,17 +206,17 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 8. queryByKind returns int IDs ───────────────────────────── - test('8: queryByKind results all have integer IDs', () => { + test('8: queryByKind results all have integer IDs', async () => { for (let i = 0; i < 10; i++) { - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000 + i * 100, kind: 'vital', key: 'cpu', - hash: store.putObject({ cpu: i * 10 }), + await hash: store.putObject({ cpu: i * 10 }), }); } - const results = store.queryByKind('vital', { key: 'cpu', limit: 5 }); + const results = await store.queryByKind('vital', { key: 'cpu', limit: 5 }); expect(results.length).toBe(5); for (const r of results) { @@ -226,15 +226,15 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 9. getRecent returns int IDs ─────────────────────────────── - test('9: getRecent returns records with integer IDs in desc order', () => { + test('9: getRecent returns records with integer IDs in desc order', async () => { for (let i = 0; i < 15; i++) { - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000 + i * 100, kind: 'tick', }); } - const recent = store.getRecent(10); + const recent = await store.getRecent(10); expect(recent.length).toBe(10); for (const r of recent) { @@ -249,32 +249,32 @@ describe('E2E T9: INT ID lifecycle', () => { // ── 10. IDs survive close/reopen ─────────────────────────────── - test('10: IDs continue incrementing after store close/reopen', () => { - const e1 = store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - const e2 = store.appendEvent({ occurredAt: 2000, kind: 'tick' }); + test('10: IDs continue incrementing after store close/reopen', async () => { + const e1 = await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + const e2 = await store.appendEvent({ occurredAt: 2000, kind: 'tick' }); assertIntId(e1.id); assertIntId(e2.id); const lastId = e2.id; - store.close(); + await store.close(); const store2 = createStore({ eventsDbPath: join(tmpDir, 'events.db'), objectsDir: join(tmpDir, 'objects'), }); - const e3 = store2.appendEvent({ occurredAt: 3000, kind: 'tick' }); + const e3 = await store2.appendEvent({ occurredAt: 3000, kind: 'tick' }); assertIntId(e3.id); expect(e3.id).toBeGreaterThan(lastId); // getAfter from original ID still works - const after = store2.getAfter(e1.id); + const after = await store2.getAfter(e1.id); expect(after.length).toBe(2); for (const e of after) { assertIntId(e.id); } - store2.close(); + await store2.close(); // Reassign so afterEach doesn't double-close store = createStore({ @@ -298,26 +298,26 @@ describe('E2E T9: INT ID with ScopedStore', () => { }); }); - afterEach(() => { - scopedStore.close(); + afterEach(async () => { + await scopedStore.close(); rmSync(tmpDir, { recursive: true, force: true }); }); - test('11: different scopes have independent ID sequences', () => { + test('11: different scopes have independent ID sequences', async () => { const scopeA = scopedStore.scope('alpha'); const scopeB = scopedStore.scope('beta'); const aEvents = []; for (let i = 0; i < 10; i++) { aEvents.push( - scopeA.appendEvent({ occurredAt: Date.now(), kind: 'tick' }), + await scopeA.appendEvent({ occurredAt: Date.now(), kind: 'tick' }), ); } const bEvents = []; for (let i = 0; i < 5; i++) { bEvents.push( - scopeB.appendEvent({ occurredAt: Date.now(), kind: 'tick' }), + await scopeB.appendEvent({ occurredAt: Date.now(), kind: 'tick' }), ); } @@ -337,17 +337,17 @@ describe('E2E T9: INT ID with ScopedStore', () => { } }); - test('12: object IDs are integers in scoped stores', () => { + test('12: object IDs are integers in scoped stores', async () => { const s = scopedStore.scope('test-scope'); - const objId = s.createObject({ + const objId = await s.createObject({ objectType: 'user', externalId: 'u1', codeRev: 'v1', }); assertIntId(objId); - const event = s.appendEvent({ + const event = await s.appendEvent({ occurredAt: Date.now(), kind: 'user-created', key: 'u1', @@ -356,7 +356,7 @@ describe('E2E T9: INT ID with ScopedStore', () => { assertIntId(event.id); expect(event.objectId).toBe(objId); - const instance = s.getObjectInstance(objId); + const instance = await s.getObjectInstance(objId); expect(instance).not.toBeNull(); assertIntId(instance!.id); }); @@ -381,22 +381,22 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { }); }); - afterEach(() => { - store.close(); - vitalsStore.close(); + afterEach(async () => { + await store.close(); + await vitalsStore.close(); rmSync(tmpDir, { recursive: true, force: true }); }); - test('13: promote → collect → rebuildSnapshot uses int IDs throughout', () => { - const promoteEvent = store.appendEvent({ + test('13: promote → collect → rebuildSnapshot uses int IDs throughout', async () => { + const promoteEvent = await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1', }); assertIntId(promoteEvent.id); - const collectHash = store.putObject({ diskPct: 40, memoryPct: 60 }); - const collectEvent = store.appendEvent({ + const collectHash = await store.putObject({ diskPct: 40, memoryPct: 60 }); + const collectEvent = await store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'system', @@ -406,7 +406,7 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { assertIntId(collectEvent.id); expect(collectEvent.id).toBeGreaterThan(promoteEvent.id); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); expect(epoch).not.toBeNull(); assertIntId(epoch!.id); @@ -419,9 +419,9 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { expect(snapshot.system.data.memoryPct).toBe(60); }); - test('14: vitals store events use int IDs, prioritized in snapshot', () => { - const vitalHash = vitalsStore.putObject({ memoryPct: 85 }); - const vitalEvent = vitalsStore.appendEvent({ + test('14: vitals store events use int IDs, prioritized in snapshot', async () => { + const vitalHash = await vitalsStore.putObject({ memoryPct: 85 }); + const vitalEvent = await vitalsStore.appendEvent({ occurredAt: 3000, kind: 'vital', key: 'system', @@ -429,8 +429,8 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { }); assertIntId(vitalEvent.id); - const collectHash = store.putObject({ memoryPct: 50 }); - store.appendEvent({ + const collectHash = await store.putObject({ memoryPct: 50 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', @@ -451,12 +451,12 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { }); test('15: rules see int IDs via effects written to store', async () => { - const vitalHash = vitalsStore.putObject({ + const vitalHash = await vitalsStore.putObject({ diskPct: 40, memoryPct: 60, cpuPct: 20, }); - vitalsStore.appendEvent({ + await vitalsStore.appendEvent({ occurredAt: Date.now(), kind: 'vital', key: 'system', @@ -490,8 +490,8 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { // Write effects back to store (simulating what runPulse does) for (const effect of effects) { - const effectHash = store.putObject(effect); - const effectEvent = store.appendEvent({ + const effectHash = await store.putObject(effect); + const effectEvent = await store.appendEvent({ occurredAt: Date.now(), kind: 'effect', key: effectHash, @@ -503,17 +503,17 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { } // Verify effect events have int IDs - const effectEvents = store.queryByKind('effect'); + const effectEvents = await store.queryByKind('effect'); expect(effectEvents.length).toBeGreaterThan(0); for (const e of effectEvents) { assertIntId(e.id); } }); - test('16: rollback epoch references int IDs', () => { - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - const h1 = store.putObject({ value: 'v1-state' }); - store.appendEvent({ + test('16: rollback epoch references int IDs', async () => { + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + const h1 = await store.putObject({ value: 'v1-state' }); + await store.appendEvent({ occurredAt: 1100, kind: 'collect', key: 'data', @@ -521,9 +521,9 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { codeRev: 'v1', }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - const h2 = store.putObject({ value: 'v2-state' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + const h2 = await store.putObject({ value: 'v2-state' }); + await store.appendEvent({ occurredAt: 2100, kind: 'collect', key: 'data', @@ -531,20 +531,20 @@ describe('E2E T9: INT ID through rebuildSnapshot + rules', () => { codeRev: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 3000, kind: 'rollback', codeRev: 'v1', meta: JSON.stringify({ to: 'v1', from: 'v2' }), }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); expect(epoch).not.toBeNull(); assertIntId(epoch!.id); expect(epoch!.codeRev).toBe('v1'); // getAfter on epoch.id returns correct events - const afterEpoch = store.getAfter(epoch!.id, { + const afterEpoch = await store.getAfter(epoch!.id, { kind: 'collect', codeRev: 'v1', }); diff --git a/packages/pulse/src/executor-loop.test.ts b/packages/pulse/src/executor-loop.test.ts index f86af8c..47e072f 100644 --- a/packages/pulse/src/executor-loop.test.ts +++ b/packages/pulse/src/executor-loop.test.ts @@ -25,8 +25,8 @@ function makeStore(tmpDir: string, name = 'events'): PulseStore { } function writeEffectEvent(store: PulseStore, effect: TestEffect): string { - const hash = store.putObject(effect); - const event = store.appendEvent({ + const hash = await store.putObject(effect); + const event = await store.appendEvent({ occurredAt: Date.now(), kind: 'effect', key: hash, @@ -47,8 +47,8 @@ describe('executorLoop', () => { store = makeStore(tmpDir); }); - afterEach(() => { - store.close(); + afterEach(async () => { + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); @@ -158,12 +158,12 @@ describe('executorLoop', () => { await loopDone; // effect-executing should have been written with key = effectEventId - const executing = store.getLatest('effect-executing', effectEventId); + const executing = await store.getLatest('effect-executing', effectEventId); expect(executing).not.toBeNull(); expect(executing?.key).toBe(effectEventId); // effect-acked should have been written with key = effectEventId - const acked = store.getLatest('effect-acked', effectEventId); + const acked = await store.getLatest('effect-acked', effectEventId); expect(acked).not.toBeNull(); expect(acked?.key).toBe(effectEventId); }); @@ -189,7 +189,7 @@ describe('executorLoop', () => { await loopDone; // effect-failed should be recorded - const failed = store.getLatest('effect-failed', effectEventId); + const failed = await store.getLatest('effect-failed', effectEventId); expect(failed).not.toBeNull(); expect(failed?.key).toBe(effectEventId); @@ -198,7 +198,7 @@ describe('executorLoop', () => { expect(meta.error).toContain('executor boom'); // effect-acked must NOT be written - const acked = store.getLatest('effect-acked', effectEventId); + const acked = await store.getLatest('effect-acked', effectEventId); expect(acked).toBeNull(); }); diff --git a/packages/pulse/src/gc.test.ts b/packages/pulse/src/gc.test.ts index 3d6c0ac..cd43790 100644 --- a/packages/pulse/src/gc.test.ts +++ b/packages/pulse/src/gc.test.ts @@ -41,52 +41,52 @@ describe('gcVitals', () => { teardown(); }); - it('should archive events older than 7 days', () => { + it('should archive events older than 7 days', async () => { const { vitals } = env; const now = Date.now(); const eightDaysAgo = now - 8 * 86_400_000; // Insert old events for (let i = 0; i < 10; i++) { - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: eightDaysAgo + i * 1000, kind: 'vital', key: 'system', }); } // Insert recent events - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: now - 1000, kind: 'vital', key: 'system', }); - const result = gcVitals(vitals); + const result = await gcVitals(vitals); expect(result.archivedCount).toBe(10); // Recent event should remain - const remaining = vitals.queryByKind('vital', {}); + const remaining = await vitals.queryByKind('vital', {}); expect(remaining.length).toBe(1); }); - it('should downsample events in 1h-24h tier to 1 per 5 min', () => { + it('should downsample events in 1h-24h tier to 1 per 5 min', async () => { const { vitals } = env; const now = Date.now(); const twoHoursAgo = now - 2 * 3_600_000; // Insert 120 events over 2 hours (1 per minute) for (let i = 0; i < 120; i++) { - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: twoHoursAgo + i * 60_000, kind: 'vital', key: 'system', }); } - const before = vitals.queryByKind('vital', {}).length; - const result = gcVitals(vitals); + const before = (await vitals.queryByKind('vital', {})).length; + const result = await gcVitals(vitals); expect(result.downsampledCount).toBeGreaterThan(0); - const after = vitals.queryByKind('vital', {}).length; + const after = (await vitals.queryByKind('vital', {})).length; // Should have fewer events after downsample expect(after).toBeLessThan(before); // Events in the <1h window should be untouched (60 events), @@ -96,22 +96,22 @@ describe('gcVitals', () => { expect(after).toBeGreaterThanOrEqual(60); }); - it('should downsample events in 24h-7d tier to 1 per hour', () => { + it('should downsample events in 24h-7d tier to 1 per hour', async () => { const { vitals } = env; const now = Date.now(); const threeDaysAgo = now - 3 * 86_400_000; // Insert 72 events over 3 days (1 per hour) for (let i = 0; i < 72; i++) { - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: threeDaysAgo + i * 3_600_000, kind: 'vital', key: 'system', }); } - const _before = vitals.queryByKind('vital', {}); - const result = gcVitals(vitals); + const _before = await vitals.queryByKind('vital', {}); + const result = await gcVitals(vitals); // Events in 24h-7d tier should be kept at 1/hour (already at that rate) // Events in 1h-24h tier should be kept at 1/5min @@ -121,30 +121,30 @@ describe('gcVitals', () => { ).toBeGreaterThanOrEqual(0); }); - it('should not touch events less than 1h old', () => { + it('should not touch events less than 1h old', async () => { const { vitals } = env; const now = Date.now(); // Insert recent events (last 30 min, every 15s) for (let i = 0; i < 120; i++) { - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: now - 30 * 60_000 + i * 15_000, kind: 'vital', key: 'system', }); } - const result = gcVitals(vitals); + const result = await gcVitals(vitals); expect(result.downsampledCount).toBe(0); expect(result.archivedCount).toBe(0); - const remaining = vitals.queryByKind('vital', {}); + const remaining = await vitals.queryByKind('vital', {}); expect(remaining.length).toBe(120); }); - it('should handle empty store gracefully', () => { + it('should handle empty store gracefully', async () => { const { vitals } = env; - const result = gcVitals(vitals); + const result = await gcVitals(vitals); expect(result.downsampledCount).toBe(0); expect(result.archivedCount).toBe(0); }); @@ -161,12 +161,12 @@ describe('gcOrphanObjects', () => { teardown(); }); - it('should delete objects not referenced by any event', () => { + it('should delete objects not referenced by any event', async () => { const { system, objectsDir } = env; // Create a referenced object - const hash = system.putObject({ data: 'referenced' }); - system.appendEvent({ + const hash = await system.putObject({ data: 'referenced' }); + await system.appendEvent({ occurredAt: Date.now(), kind: 'collect', key: 'test', @@ -176,11 +176,11 @@ describe('gcOrphanObjects', () => { // Create an orphan object file writeFileSync(join(objectsDir, 'orphan123.json'), '{"data":"orphan"}'); - const deleted = gcOrphanObjects([system], objectsDir); + const deleted = await gcOrphanObjects([system], objectsDir); expect(deleted).toBe(1); // Referenced object should still exist - const obj = system.getObject(hash); + const obj = await system.getObject(hash); expect(obj).not.toBeNull(); // Orphan should be gone @@ -188,40 +188,40 @@ describe('gcOrphanObjects', () => { expect(files).not.toContain('orphan123.json'); }); - it('should not delete objects referenced by events', () => { + it('should not delete objects referenced by events', async () => { const { system, objectsDir } = env; - const hash1 = system.putObject({ data: 'a' }); - const hash2 = system.putObject({ data: 'b' }); - system.appendEvent({ + const hash1 = await system.putObject({ data: 'a' }); + const hash2 = await system.putObject({ data: 'b' }); + await system.appendEvent({ occurredAt: Date.now(), kind: 'collect', key: 'a', hash: hash1, }); - system.appendEvent({ + await system.appendEvent({ occurredAt: Date.now(), kind: 'effect', key: 'b', hash: hash2, }); - const deleted = gcOrphanObjects([system], objectsDir); + const deleted = await gcOrphanObjects([system], objectsDir); expect(deleted).toBe(0); const files = readdirSync(objectsDir); expect(files.length).toBe(2); }); - it('should handle empty objects directory', () => { + it('should handle empty objects directory', async () => { const { system, objectsDir } = env; - const deleted = gcOrphanObjects([system], objectsDir); + const deleted = await gcOrphanObjects([system], objectsDir); expect(deleted).toBe(0); }); - it('should handle non-existent objects directory', () => { + it('should handle non-existent objects directory', async () => { const { system } = env; - const deleted = gcOrphanObjects([system], '/nonexistent/path'); + const deleted = await gcOrphanObjects([system], '/nonexistent/path'); expect(deleted).toBe(0); }); }); @@ -237,13 +237,13 @@ describe('runGc', () => { teardown(); }); - it('should run full GC cycle and write gc event to system store', () => { + it('should run full GC cycle and write gc event to system store', async () => { const { vitals, system, objectsDir } = env; const now = Date.now(); // Add some old vitals for (let i = 0; i < 5; i++) { - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: now - 8 * 86_400_000 + i * 1000, kind: 'vital', key: 'system', @@ -253,7 +253,7 @@ describe('runGc', () => { // Add orphan object writeFileSync(join(objectsDir, 'deadbeef.json'), '{}'); - const result = runGc({ + const result = await runGc({ vitalsStore: vitals, systemStore: system, allStores: [system, vitals], @@ -265,7 +265,7 @@ describe('runGc', () => { expect(result.durationMs).toBeGreaterThanOrEqual(0); // Check gc event was written - const gcEvents = system.queryByKind('gc', {}); + const gcEvents = await system.queryByKind('gc', {}); expect(gcEvents.length).toBe(1); expect(gcEvents[0].key).toBe('vitals'); const meta = JSON.parse(gcEvents[0].meta!); @@ -285,12 +285,12 @@ describe('createGcTrigger', () => { teardown(); }); - it('should trigger GC after N ticks', () => { + it('should trigger GC after N ticks', async () => { const { vitals, system, objectsDir } = env; const now = Date.now(); // Add old data to clean up - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: now - 8 * 86_400_000, kind: 'vital', key: 'system', @@ -307,26 +307,30 @@ describe('createGcTrigger', () => { // Tick 1, 2: no GC trigger(); trigger(); - expect(system.queryByKind('gc', {}).length).toBe(0); + await new Promise(r => setTimeout(r, 50)); + expect((await system.queryByKind('gc', {})).length).toBe(0); // Tick 3: GC fires trigger(); - expect(system.queryByKind('gc', {}).length).toBe(1); + await new Promise(r => setTimeout(r, 200)); + expect((await system.queryByKind('gc', {})).length).toBe(1); // Tick 4, 5: no GC again trigger(); trigger(); - expect(system.queryByKind('gc', {}).length).toBe(1); + await new Promise(r => setTimeout(r, 50)); + expect((await system.queryByKind('gc', {})).length).toBe(1); // Tick 6: GC fires again trigger(); - expect(system.queryByKind('gc', {}).length).toBe(2); + await new Promise(r => setTimeout(r, 200)); + expect((await system.queryByKind('gc', {})).length).toBe(2); }); - it('should be a no-op when gc is disabled', () => { + it('should be a no-op when gc is disabled', async () => { const { vitals, system, objectsDir } = env; - vitals.appendEvent({ + await vitals.appendEvent({ occurredAt: Date.now() - 8 * 86_400_000, kind: 'vital', key: 'system', @@ -341,8 +345,9 @@ describe('createGcTrigger', () => { }); for (let i = 0; i < 300; i++) trigger(); - expect(system.queryByKind('gc', {}).length).toBe(0); + await new Promise(r => setTimeout(r, 50)); + expect((await system.queryByKind('gc', {})).length).toBe(0); // Old event should still be there - expect(vitals.queryByKind('vital', {}).length).toBe(1); + expect((await vitals.queryByKind('vital', {})).length).toBe(1); }); }); diff --git a/packages/pulse/src/index.test.ts b/packages/pulse/src/index.test.ts index 4706309..f33d09b 100644 --- a/packages/pulse/src/index.test.ts +++ b/packages/pulse/src/index.test.ts @@ -203,13 +203,13 @@ describe('rebuildSnapshot', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); - it('rebuilds snapshot from collect events', () => { - const hash = store.putObject({ memoryPct: 72, cpuIdlePct: 85 }); - store.appendEvent({ + it('rebuilds snapshot from collect events', async () => { + const hash = await store.putObject({ memoryPct: 72, cpuIdlePct: 85 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', @@ -227,16 +227,16 @@ describe('rebuildSnapshot', () => { expect(snapshot.system.refreshedAt).toBe(1000); }); - it('returns latest collect for each key', () => { - const h1 = store.putObject({ value: 1 }); - const h2 = store.putObject({ value: 2 }); - store.appendEvent({ + it('returns latest collect for each key', async () => { + const h1 = await store.putObject({ value: 1 }); + const h2 = await store.putObject({ value: 2 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', hash: h1, }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'system', @@ -261,16 +261,16 @@ describe('rebuildSnapshot', () => { expect(snapshot.nonexistent).toBe(undefined); }); - it('multiple sense keys each get their latest collect', () => { - const h1 = store.putObject({ mem: 50 }); - const h2 = store.putObject({ cpu: 80 }); - store.appendEvent({ + it('multiple sense keys each get their latest collect', async () => { + const h1 = await store.putObject({ mem: 50 }); + const h2 = await store.putObject({ cpu: 80 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'memory', hash: h1, }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1500, kind: 'collect', key: 'cpu', @@ -313,14 +313,14 @@ describe('runPulse effects', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); it('all effects (including collect) are passed to execute', async () => { // Pre-populate with data so snapshot has something - const initHash = store.putObject({ v: 1 }); - store.appendEvent({ + const initHash = await store.putObject({ v: 1 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'sys', @@ -367,7 +367,7 @@ describe('runPulse effects', () => { expect(executedEffects.some((e) => e.kind === 'notify')).toBeTruthy(); // Verify effect events were recorded - const effectEvents = store.queryByKind('effect'); + const effectEvents = await store.queryByKind('effect'); expect(effectEvents.length >= 1).toBeTruthy(); }); @@ -382,7 +382,7 @@ describe('runPulse effects', () => { await new Promise((r) => setTimeout(r, 150)); - const tickEvents = store.queryByKind('tick'); + const tickEvents = await store.queryByKind('tick'); expect(tickEvents.length >= 1).toBeTruthy(); }); }); @@ -423,8 +423,8 @@ describe('Moore machine property', () => { try { // Pre-populate with initial value - const initHash = store.putObject({ v: 1 }); - store.appendEvent({ + const initHash = await store.putObject({ v: 1 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'sys', @@ -440,8 +440,8 @@ describe('Moore machine property', () => { expect(before.sys.data.v).toBe(1); // Simulate what runPulse does: execute a collect effect - const newHash = store.putObject({ v: 999 }); - store.appendEvent({ + const newHash = await store.putObject({ v: 999 }); + await store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'sys', @@ -455,7 +455,7 @@ describe('Moore machine property', () => { const after = rebuildSnapshot(store, ['sys']); expect(after.sys.data.v).toBe(999); } finally { - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); } }); @@ -490,7 +490,7 @@ describe('findEffectiveEpoch', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); @@ -498,44 +498,44 @@ describe('findEffectiveEpoch', () => { expect(findEffectiveEpoch(store)).toBe(null); }); - it('with promote → returns it', () => { - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - const epoch = findEffectiveEpoch(store); + it('with promote → returns it', async () => { + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + const epoch = await findEffectiveEpoch(store); expect(epoch).toBeTruthy(); expect(epoch?.codeRev).toBe('v1'); }); - it('multiple promotes → returns latest', () => { - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - const epoch = findEffectiveEpoch(store); + it('multiple promotes → returns latest', async () => { + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + const epoch = await findEffectiveEpoch(store); expect(epoch).toBeTruthy(); expect(epoch?.codeRev).toBe('v2'); }); - it('with rollback → returns rolled-back-to promote', () => { - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + it('with rollback → returns rolled-back-to promote', async () => { + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 3000, kind: 'rollback', codeRev: 'v1', meta: JSON.stringify({ to: 'v1', from: 'v2' }), }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); expect(epoch).toBeTruthy(); expect(epoch?.codeRev).toBe('v1'); }); - it('rollback with only codeRev (no meta.to) → uses codeRev', () => { - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + it('rollback with only codeRev (no meta.to) → uses codeRev', async () => { + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 3000, kind: 'rollback', codeRev: 'v1', }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); expect(epoch).toBeTruthy(); expect(epoch?.codeRev).toBe('v1'); }); @@ -557,24 +557,24 @@ describe('rebuildSnapshot with epoch', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); - it('only reads events after epoch with matching code_rev', () => { - const h1 = store.putObject({ value: 'v1-data' }); - const h2 = store.putObject({ value: 'v2-data' }); + it('only reads events after epoch with matching code_rev', async () => { + const h1 = await store.putObject({ value: 'v1-data' }); + const h2 = await store.putObject({ value: 'v2-data' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 1100, kind: 'collect', key: 'system', hash: h1, codeRev: 'v1', }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 2100, kind: 'collect', key: 'system', @@ -583,7 +583,7 @@ describe('rebuildSnapshot with epoch', () => { }); // Using v1 epoch should get v1 data - const epochV1 = store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); + const epochV1 = await store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); const snapshotV1 = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -591,7 +591,7 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshotV1.system.data.value).toBe('v1-data'); // Using v2 epoch should get v2 data - const epochV2 = store.getLatestWhere({ kind: 'promote', codeRev: 'v2' }); + const epochV2 = await store.getLatestWhere({ kind: 'promote', codeRev: 'v2' }); const snapshotV2 = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -599,20 +599,20 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshotV2.system.data.value).toBe('v2-data'); }); - it('v2 events not visible in v1 epoch', () => { - const h1 = store.putObject({ value: 'v1-data' }); - const h2 = store.putObject({ value: 'v2-data' }); + it('v2 events not visible in v1 epoch', async () => { + const h1 = await store.putObject({ value: 'v1-data' }); + const h2 = await store.putObject({ value: 'v2-data' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 1100, kind: 'collect', key: 'system', hash: h1, codeRev: 'v1', }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 2100, kind: 'collect', key: 'system', @@ -621,7 +621,7 @@ describe('rebuildSnapshot with epoch', () => { }); // v1 epoch should NOT see v2 collect (different code_rev) - const epochV1 = store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); + const epochV1 = await store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -629,11 +629,11 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshot.system.data.value).toBe('v1-data'); }); - it('migrate events used for initial snapshot after promote', () => { - const hMigrated = store.putObject({ value: 'migrated' }); + it('migrate events used for initial snapshot after promote', async () => { + const hMigrated = await store.putObject({ value: 'migrated' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 1001, kind: 'migrate', key: 'system', @@ -641,7 +641,7 @@ describe('rebuildSnapshot with epoch', () => { codeRev: 'v2', }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -649,11 +649,11 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshot.system.data.value).toBe('migrated'); }); - it('init events used when no collect or migrate exist', () => { - const hInit = store.putObject({ value: 'initial' }); + it('init events used when no collect or migrate exist', async () => { + const hInit = await store.putObject({ value: 'initial' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 1001, kind: 'init', key: 'system', @@ -661,7 +661,7 @@ describe('rebuildSnapshot with epoch', () => { codeRev: 'v1', }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -669,27 +669,27 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshot.system.data.value).toBe('initial'); }); - it('collect takes priority over migrate and init', () => { - const hInit = store.putObject({ value: 'initial' }); - const hMigrate = store.putObject({ value: 'migrated' }); - const hCollect = store.putObject({ value: 'collected' }); + it('collect takes priority over migrate and init', async () => { + const hInit = await store.putObject({ value: 'initial' }); + const hMigrate = await store.putObject({ value: 'migrated' }); + const hCollect = await store.putObject({ value: 'collected' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 1001, kind: 'init', key: 'system', hash: hInit, codeRev: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1002, kind: 'migrate', key: 'system', hash: hMigrate, codeRev: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1003, kind: 'collect', key: 'system', @@ -697,7 +697,7 @@ describe('rebuildSnapshot with epoch', () => { codeRev: 'v2', }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -705,16 +705,16 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshot.system.data.value).toBe('collected'); }); - it('no epoch → falls back to latest collect (backward compat)', () => { - const h1 = store.putObject({ value: 'first' }); - const h2 = store.putObject({ value: 'second' }); - store.appendEvent({ + it('no epoch → falls back to latest collect (backward compat)', async () => { + const h1 = await store.putObject({ value: 'first' }); + const h2 = await store.putObject({ value: 'second' }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', hash: h1, }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'system', @@ -728,34 +728,34 @@ describe('rebuildSnapshot with epoch', () => { expect(snapshot.system.data.value).toBe('second'); }); - it('rollback epoch reads correct version data', () => { - const h1 = store.putObject({ value: 'v1-data' }); - const h2 = store.putObject({ value: 'v2-data' }); + it('rollback epoch reads correct version data', async () => { + const h1 = await store.putObject({ value: 'v1-data' }); + const h2 = await store.putObject({ value: 'v2-data' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 1100, kind: 'collect', key: 'system', hash: h1, codeRev: 'v1', }); - store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 2100, kind: 'collect', key: 'system', hash: h2, codeRev: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 3000, kind: 'rollback', codeRev: 'v1', meta: JSON.stringify({ to: 'v1', from: 'v2' }), }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); expect(epoch).toBeTruthy(); expect(epoch?.codeRev).toBe('v1'); @@ -788,22 +788,22 @@ describe('rebuildSnapshot vitals priority', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); - vitalsStore.close(); + await store.close(); + await vitalsStore.close(); rmSync(tmpDir, { recursive: true, force: true }); }); - it('reads latest from vitals store over system events', () => { - const hEvent = store.putObject({ value: 'from-event' }); - const hVital = vitalsStore.putObject({ value: 'from-vital' }); + it('reads latest from vitals store over system events', async () => { + const hEvent = await store.putObject({ value: 'from-event' }); + const hVital = await vitalsStore.putObject({ value: 'from-vital' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', hash: hEvent, }); - vitalsStore.appendEvent({ + await vitalsStore.appendEvent({ occurredAt: 2000, kind: 'vital', key: 'system', @@ -818,9 +818,9 @@ describe('rebuildSnapshot vitals priority', () => { expect(snapshot.system.refreshedAt).toBe(2000); }); - it('falls back to system events when vitals empty', () => { - const h = store.putObject({ value: 'event-only' }); - store.appendEvent({ + it('falls back to system events when vitals empty', async () => { + const h = await store.putObject({ value: 'event-only' }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'system', @@ -834,26 +834,26 @@ describe('rebuildSnapshot vitals priority', () => { expect(snapshot.system.data.value).toBe('event-only'); }); - it('vitals priority with epoch — still reads vitals first', () => { - const hVital = vitalsStore.putObject({ value: 'vital-data' }); - const hEvent = store.putObject({ value: 'event-data' }); + it('vitals priority with epoch — still reads vitals first', async () => { + const hVital = await vitalsStore.putObject({ value: 'vital-data' }); + const hEvent = await store.putObject({ value: 'event-data' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 1100, kind: 'collect', key: 'system', hash: hEvent, codeRev: 'v1', }); - vitalsStore.appendEvent({ + await vitalsStore.appendEvent({ occurredAt: 1200, kind: 'vital', key: 'system', hash: hVital, }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -861,11 +861,11 @@ describe('rebuildSnapshot vitals priority', () => { expect(snapshot.system.data.value).toBe('vital-data'); }); - it('falls back to migrate events when no vitals', () => { - const hMigrate = store.putObject({ value: 'migrated' }); + it('falls back to migrate events when no vitals', async () => { + const hMigrate = await store.putObject({ value: 'migrated' }); - store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 1001, kind: 'migrate', key: 'system', @@ -873,7 +873,7 @@ describe('rebuildSnapshot vitals priority', () => { codeRev: 'v2', }); - const epoch = findEffectiveEpoch(store); + const epoch = await findEffectiveEpoch(store); const snapshot = rebuildSnapshot<{ timestamp: number; system: Sensed<{ value: string }>; @@ -898,13 +898,13 @@ describe('runPulse execute-driven collect', () => { afterEach(async () => { await new Promise((r) => setTimeout(r, 200)); - store.close(); + await store.close(); rmSync(tmpDir, { recursive: true, force: true }); }); it('execute can write events for collect effects', async () => { - const initHash = store.putObject({ v: 1 }); - store.appendEvent({ + const initHash = await store.putObject({ v: 1 }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'sys', @@ -928,8 +928,8 @@ describe('runPulse execute-driven collect', () => { for (const effect of effects) { if (effect.kind === 'collect' && effect.key === 'sys') { const data = { v: 99 }; - const hash = store.putObject(data); - store.appendEvent({ + const hash = await store.putObject(data); + await store.appendEvent({ occurredAt: Date.now(), kind: 'collect', key: 'sys', @@ -946,7 +946,7 @@ describe('runPulse execute-driven collect', () => { await new Promise((r) => setTimeout(r, 300)); - const collectEvents = store.queryByKind('collect', { key: 'sys' }); + const collectEvents = await store.queryByKind('collect', { key: 'sys' }); expect(collectEvents.length >= 2).toBeTruthy(); }); }); @@ -968,14 +968,14 @@ describe('runPulse with ScopedStore', () => { afterEach(async () => { // Wait for async watcher/tick loops to settle before closing DBs await new Promise((r) => setTimeout(r, 200)); - scopedStore.close(); + await scopedStore.close(); rmSync(tmpDir, { recursive: true, force: true }); }); it('tick and effect events are written to _system scope', async () => { const systemStore = scopedStore.scope('_system'); - const initHash = systemStore.putObject({ v: 1 }); - systemStore.appendEvent({ + const initHash = await systemStore.putObject({ v: 1 }); + await systemStore.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'sys', @@ -1003,15 +1003,15 @@ describe('runPulse with ScopedStore', () => { await new Promise((r) => setTimeout(r, 150)); - const tickEvents = systemStore.queryByKind('tick'); + const tickEvents = await systemStore.queryByKind('tick'); expect(tickEvents.length >= 1).toBeTruthy(); - const effectEvents = systemStore.queryByKind('effect'); + const effectEvents = await systemStore.queryByKind('effect'); expect(effectEvents.length >= 1).toBeTruthy(); // _vitals scope should NOT have tick/effect events const vitalsStore = scopedStore.scope('_vitals'); - const vitalsTicks = vitalsStore.queryByKind('tick'); + const vitalsTicks = await vitalsStore.queryByKind('tick'); expect(vitalsTicks.length).toBe(0); }); @@ -1039,13 +1039,13 @@ describe('runPulse with ScopedStore', () => { await new Promise((r) => setTimeout(r, 150)); // Vitals should be in _vitals scope - const vitalEvents = vitalsStore.queryByKind('vital', { + const vitalEvents = await vitalsStore.queryByKind('vital', { key: 'test_metric', }); expect(vitalEvents.length >= 1).toBeTruthy(); // _system scope should NOT have vital events from the watcher - const systemVitals = systemStore.queryByKind('vital', { + const systemVitals = await systemStore.queryByKind('vital', { key: 'test_metric', }); expect(systemVitals.length).toBe(0); @@ -1056,8 +1056,8 @@ describe('runPulse with ScopedStore', () => { const vitalsStore = scopedStore.scope('_vitals'); // Write a vital to _vitals scope - const vitalHash = vitalsStore.putObject({ v: 99 }); - vitalsStore.appendEvent({ + const vitalHash = await vitalsStore.putObject({ v: 99 }); + await vitalsStore.appendEvent({ occurredAt: 2000, kind: 'vital', key: 'sys', @@ -1065,8 +1065,8 @@ describe('runPulse with ScopedStore', () => { }); // Write an older collect to _system scope - const collectHash = systemStore.putObject({ v: 1 }); - systemStore.appendEvent({ + const collectHash = await systemStore.putObject({ v: 1 }); + await systemStore.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'sys', @@ -1134,6 +1134,6 @@ describe('runPulse with ScopedStore', () => { expect(executedEffects.length >= 1).toBeTruthy(); expect(executedEffects.some((e) => e.kind === 'notify')).toBeTruthy(); - store.close(); + await store.close(); }); }); diff --git a/packages/pulse/src/objects.test.ts b/packages/pulse/src/objects.test.ts index dd6e5f0..1026446 100644 --- a/packages/pulse/src/objects.test.ts +++ b/packages/pulse/src/objects.test.ts @@ -29,13 +29,13 @@ describe('objects table (createStore)', () => { }); }); - afterEach(() => { - store.close(); + afterEach(async () => { + await store.close(); rmSync(dir, { recursive: true, force: true }); }); - it('1. createObject returns an integer id', () => { - const id = store.createObject({ + it('1. createObject returns an integer id', async () => { + const id = await store.createObject({ objectType: 'user', codeRev: 'v1', }); @@ -44,13 +44,13 @@ describe('objects table (createStore)', () => { expect(id).toBeGreaterThan(0); }); - it('2. getObjectInstance retrieves the created object', () => { - const id = store.createObject({ + it('2. getObjectInstance retrieves the created object', async () => { + const id = await store.createObject({ objectType: 'user', externalId: 'usr_123', codeRev: 'v1', }); - const obj = store.getObjectInstance(id); + const obj = await store.getObjectInstance(id); expect(obj).not.toBeNull(); expect(obj!.id).toBe(id); expect(obj!.objectType).toBe('user'); @@ -59,31 +59,31 @@ describe('objects table (createStore)', () => { expect(typeof obj!.createdAt).toBe('number'); }); - it('3. queryObjectsByType filters by type', () => { - store.createObject({ objectType: 'user', externalId: 'a', codeRev: 'v1' }); - store.createObject({ objectType: 'user', externalId: 'b', codeRev: 'v1' }); - store.createObject({ + it('3. queryObjectsByType filters by type', async () => { + await store.createObject({ objectType: 'user', externalId: 'a', codeRev: 'v1' }); + await store.createObject({ objectType: 'user', externalId: 'b', codeRev: 'v1' }); + await store.createObject({ objectType: 'order', externalId: 'o1', codeRev: 'v1', }); - const users = store.queryObjectsByType('user'); + const users = await store.queryObjectsByType('user'); expect(users.length).toBe(2); expect(users.every((o) => o.objectType === 'user')).toBe(true); - const orders = store.queryObjectsByType('order'); + const orders = await store.queryObjectsByType('order'); expect(orders.length).toBe(1); expect(orders[0]!.objectType).toBe('order'); }); - it('4. externalId + same type is idempotent (returns existing id)', () => { - const id1 = store.createObject({ + it('4. externalId + same type is idempotent (returns existing id)', async () => { + const id1 = await store.createObject({ objectType: 'user', externalId: 'usr_dup', codeRev: 'v1', }); - const id2 = store.createObject({ + const id2 = await store.createObject({ objectType: 'user', externalId: 'usr_dup', codeRev: 'v2', @@ -91,18 +91,18 @@ describe('objects table (createStore)', () => { expect(id1).toBe(id2); // Only one row should exist - const users = store.queryObjectsByType('user'); + const users = await store.queryObjectsByType('user'); expect(users.length).toBe(1); }); - it('5. appendEvent with objectId links event to object', () => { - const objId = store.createObject({ + it('5. appendEvent with objectId links event to object', async () => { + const objId = await store.createObject({ objectType: 'task', externalId: 'tsk_1', codeRev: 'v1', }); - const event = store.appendEvent({ + const event = await store.appendEvent({ occurredAt: Date.now(), kind: 'task-created', key: 'tsk_1', @@ -112,13 +112,13 @@ describe('objects table (createStore)', () => { expect(event.objectId).toBe(objId); // Verify through getLatest - const latest = store.getLatest('task-created', 'tsk_1'); + const latest = await store.getLatest('task-created', 'tsk_1'); expect(latest).not.toBeNull(); expect(latest!.objectId).toBe(objId); }); - it('7. appendEvent without objectId keeps object_id null (backward compat)', () => { - const event = store.appendEvent({ + it('7. appendEvent without objectId keeps object_id null (backward compat)', async () => { + const event = await store.appendEvent({ occurredAt: Date.now(), kind: 'tick', key: 'sys', @@ -126,18 +126,18 @@ describe('objects table (createStore)', () => { expect(event.objectId).toBeUndefined(); - const latest = store.getLatest('tick', 'sys'); + const latest = await store.getLatest('tick', 'sys'); expect(latest).not.toBeNull(); expect(latest!.objectId).toBeUndefined(); }); - it('getObjectInstance returns null for non-existent id', () => { - expect(store.getObjectInstance(99999)).toBeNull(); + it('getObjectInstance returns null for non-existent id', async () => { + await expect(store.getObjectInstance(99999)).toBeNull(); }); - it('createObject without externalId creates distinct rows', () => { - const id1 = store.createObject({ objectType: 'ephemeral', codeRev: 'v1' }); - const id2 = store.createObject({ objectType: 'ephemeral', codeRev: 'v1' }); + it('createObject without externalId creates distinct rows', async () => { + const id1 = await store.createObject({ objectType: 'ephemeral', codeRev: 'v1' }); + const id2 = await store.createObject({ objectType: 'ephemeral', codeRev: 'v1' }); expect(id1).not.toBe(id2); }); }); @@ -156,46 +156,46 @@ describe('objects table (ScopedStore)', () => { }); }); - afterEach(() => { - scopedStore.close(); + afterEach(async () => { + await scopedStore.close(); rmSync(dir, { recursive: true, force: true }); }); - it('6. objects in different scopes are independent', () => { + it('6. objects in different scopes are independent', async () => { const scopeA = scopedStore.scope('scope-a'); const scopeB = scopedStore.scope('scope-b'); - scopeA.createObject({ + await scopeA.createObject({ objectType: 'user', externalId: 'u1', codeRev: 'v1', }); - scopeA.createObject({ + await scopeA.createObject({ objectType: 'user', externalId: 'u2', codeRev: 'v1', }); - scopeB.createObject({ + await scopeB.createObject({ objectType: 'user', externalId: 'u1', codeRev: 'v1', }); - const usersA = scopeA.queryObjectsByType('user'); - const usersB = scopeB.queryObjectsByType('user'); + const usersA = await scopeA.queryObjectsByType('user'); + const usersB = await scopeB.queryObjectsByType('user'); expect(usersA.length).toBe(2); expect(usersB.length).toBe(1); }); - it('appendEvent with objectId in scoped store', () => { + it('appendEvent with objectId in scoped store', async () => { const s = scopedStore.scope('test-scope'); - const objId = s.createObject({ + const objId = await s.createObject({ objectType: 'item', externalId: 'item_1', codeRev: 'v1', }); - const event = s.appendEvent({ + const event = await s.appendEvent({ occurredAt: Date.now(), kind: 'item-updated', key: 'item_1', @@ -203,7 +203,7 @@ describe('objects table (ScopedStore)', () => { }); expect(event.objectId).toBe(objId); - const latest = s.getLatest('item-updated', 'item_1'); + const latest = await s.getLatest('item-updated', 'item_1'); expect(latest!.objectId).toBe(objId); }); }); diff --git a/packages/pulse/src/persona.test.ts b/packages/pulse/src/persona.test.ts index 79d9167..9da37c3 100644 --- a/packages/pulse/src/persona.test.ts +++ b/packages/pulse/src/persona.test.ts @@ -32,7 +32,7 @@ function makeMockStore( } describe('buildPersonasFromEvents', () => { - test('registers a single persona', () => { + test('registers a single persona', async () => { const store = makeMockStore([ { id: 1, @@ -46,7 +46,7 @@ describe('buildPersonasFromEvents', () => { }), }, ]); - const personas = buildPersonasFromEvents(store); + const personas = await buildPersonasFromEvents(store); expect(personas.size).toBe(1); const p = personas.get('xiaoju')!; expect(p.personaId).toBe('xiaoju'); @@ -57,7 +57,7 @@ describe('buildPersonasFromEvents', () => { expect(p.updatedAt).toBe(1000); }); - test('registers multiple personas', () => { + test('registers multiple personas', async () => { const store = makeMockStore([ { id: 1, @@ -82,13 +82,13 @@ describe('buildPersonasFromEvents', () => { }), }, ]); - const personas = buildPersonasFromEvents(store); + const personas = await buildPersonasFromEvents(store); expect(personas.size).toBe(2); expect(personas.has('xiaoju')).toBe(true); expect(personas.has('cursor')).toBe(true); }); - test('idempotent overwrite on duplicate register', () => { + test('idempotent overwrite on duplicate register', async () => { const store = makeMockStore([ { id: 1, @@ -113,7 +113,7 @@ describe('buildPersonasFromEvents', () => { }), }, ]); - const personas = buildPersonasFromEvents(store); + const personas = await buildPersonasFromEvents(store); expect(personas.size).toBe(1); const p = personas.get('xiaoju')!; expect(p.name).toBe('小橘 v2'); @@ -122,7 +122,7 @@ describe('buildPersonasFromEvents', () => { expect(p.registeredAt).toBe(2000); }); - test('persona-updated partial update', () => { + test('persona-updated partial update', async () => { const store = makeMockStore([ { id: 1, @@ -142,7 +142,7 @@ describe('buildPersonasFromEvents', () => { meta: JSON.stringify({ personaId: 'xiaoju', container: 'cursor' }), }, ]); - const personas = buildPersonasFromEvents(store); + const personas = await buildPersonasFromEvents(store); const p = personas.get('xiaoju')!; expect(p.container).toBe('cursor'); expect(p.capabilities).toEqual(['ops']); // unchanged @@ -151,9 +151,9 @@ describe('buildPersonasFromEvents', () => { expect(p.registeredAt).toBe(1000); }); - test('empty event stream returns empty Map', () => { + test('empty event stream returns empty Map', async () => { const store = makeMockStore([]); - const personas = buildPersonasFromEvents(store); + const personas = await buildPersonasFromEvents(store); expect(personas.size).toBe(0); }); }); diff --git a/packages/pulse/src/projection-engine.test.ts b/packages/pulse/src/projection-engine.test.ts index 4c06eb5..e3040f5 100644 --- a/packages/pulse/src/projection-engine.test.ts +++ b/packages/pulse/src/projection-engine.test.ts @@ -66,7 +66,7 @@ function insertTestEvent( return Number(result.lastInsertRowid); } -beforeEach(() => { +beforeEach(async () => { // Create temp directory tempDir = mkdtempSync(join(tmpdir(), 'pulse-test-')); @@ -76,12 +76,12 @@ beforeEach(() => { scopeDb.exec('PRAGMA journal_mode = WAL'); scopeDb.exec(EVENTS_SCHEMA); scopeDb.exec(PROJECTIONS_SCHEMA); - initDefsSchema(scopeDb); + await initDefsSchema(scopeDb); }); -afterEach(() => { +afterEach(async () => { // Close databases - if (scopeDb) scopeDb.close(); + if (scopeDb) await scopeDb.close(); // Clean up temp directory rmSync(tempDir, { recursive: true, force: true }); @@ -274,7 +274,7 @@ describe('Projection Engine', () => { await resetProjections(scopeDb, newCodeRev); // Check that projection was recalculated with new logic - const newState = getProjectionState(scopeDb, 'value-tracker'); + const newState = await getProjectionState(scopeDb, 'value-tracker'); expect(newState).not.toBeNull(); expect(newState!.value).toEqual({ product: 6000, count: 3 }); // 10 * 20 * 30 = 6000 expect(newState!.codeRev).toBe(newCodeRev); @@ -316,12 +316,12 @@ describe('Projection Engine', () => { // Start with new code_rev await resetProjections(scopeDb, newCodeRev); - let state = getProjectionState(scopeDb, 'rollback-test'); + let state = await getProjectionState(scopeDb, 'rollback-test'); expect(state!.value).toEqual({ type: 'count', value: 2 }); // counting events // Rollback to old code_rev await resetProjections(scopeDb, oldCodeRev); - state = getProjectionState(scopeDb, 'rollback-test'); + state = await getProjectionState(scopeDb, 'rollback-test'); expect(state!.value).toEqual({ type: 'sum', value: 15 }); // summing values: 5 + 10 expect(state!.codeRev).toBe(oldCodeRev); }); @@ -517,7 +517,7 @@ describe('Integration Tests', () => { // Promote to V2 await resetProjections(scopeDb, v2); - state = getProjectionState(scopeDb, 'workflow-test')!; + state = await getProjectionState(scopeDb, 'workflow-test')!; expect(state.value).toBe(30); // 1 * 5 * 3 * 2 = 30 expect(state.codeRev).toBe(v2); @@ -528,7 +528,7 @@ describe('Integration Tests', () => { // Rollback to V1 await resetProjections(scopeDb, v1); - state = getProjectionState(scopeDb, 'workflow-test')!; + state = await getProjectionState(scopeDb, 'workflow-test')!; expect(state.value).toBe(20); // 0 + 5 + 3 + 2 + 10 = 20 expect(state.codeRev).toBe(v1); }); diff --git a/packages/pulse/src/review-fixes.test.ts b/packages/pulse/src/review-fixes.test.ts index 9c299e7..3882aaf 100644 --- a/packages/pulse/src/review-fixes.test.ts +++ b/packages/pulse/src/review-fixes.test.ts @@ -44,7 +44,7 @@ describe('Review Fixes', () => { const testDir = '/tmp/pulse-review-fixes-test'; const scopeDbPath = `${testDir}/test-scope.db`; - beforeEach(() => { + beforeEach(async () => { // Clean up any existing test data try { rmSync(testDir, { recursive: true, force: true }); @@ -76,11 +76,11 @@ describe('Review Fixes', () => { updated_at INTEGER NOT NULL ) `); - initDefsSchema(scopeDb); + await initDefsSchema(scopeDb); }); - afterEach(() => { - scopeDb?.close(); + afterEach(async () => { + await scopeDb?.close(); try { rmSync(testDir, { recursive: true, force: true }); } catch {} diff --git a/packages/pulse/src/rule-projections.test.ts b/packages/pulse/src/rule-projections.test.ts index 834f685..3de47b4 100644 --- a/packages/pulse/src/rule-projections.test.ts +++ b/packages/pulse/src/rule-projections.test.ts @@ -46,7 +46,7 @@ beforeEach(async () => { afterEach(async () => { // Wait for async runPulseV2 loops to settle before cleanup await new Promise((r) => setTimeout(r, 200)); - scopedStore?.close(); + await scopedStore?.close(); rmSync(tempDir, { recursive: true }); }); @@ -71,7 +71,7 @@ describe('RuleDef Declaration', () => { expect(ruleDef.rule).toBe(testRule); }); - test('should validate projection path format', () => { + test('should validate projection path format', async () => { const ruleDef: RuleDef = { name: 'invalid-paths', projections: ['valid/path', 'invalid-path', 'also/valid/path'], @@ -79,7 +79,7 @@ describe('RuleDef Declaration', () => { }; // Test that buildSnapshotFromProjections handles invalid format gracefully - const snapshot = buildSnapshotFromProjections( + const snapshot = await buildSnapshotFromProjections( scopedStore, ruleDef.projections, ); @@ -99,7 +99,7 @@ describe('Cross-Scope Projection Reading', () => { const vitalsScope = scopedStore.scope('_vitals'); const nekoScope = scopedStore.scope('neko'); - vitalsScope.appendEvent({ + await vitalsScope.appendEvent({ occurredAt: Date.now(), kind: 'metric', key: 'cpu', @@ -107,7 +107,7 @@ describe('Cross-Scope Projection Reading', () => { codeRev: CODEREV, }); - nekoScope.appendEvent({ + await nekoScope.appendEvent({ occurredAt: Date.now(), kind: 'session', codeRev: CODEREV, @@ -147,7 +147,7 @@ describe('Cross-Scope Projection Reading', () => { await foldProjection(nekoDb, 'neko', 'session_count', CODEREV); // Build snapshot from projections - const snapshot = buildSnapshotFromProjections(scopedStore, [ + const snapshot = await buildSnapshotFromProjections(scopedStore, [ '_vitals/cpu_usage', 'neko/session_count', ]); @@ -163,7 +163,7 @@ describe('Cross-Scope Projection Reading', () => { describe('Snapshot Key Format', () => { test('should use scope/name format consistently as keys', async () => { const testScope = scopedStore.scope('test_scope'); - testScope.appendEvent({ + await testScope.appendEvent({ occurredAt: Date.now(), kind: 'test', codeRev: CODEREV, @@ -184,7 +184,7 @@ describe('Snapshot Key Format', () => { await foldProjection(testDb, 'test_scope', 'test_metric', CODEREV); - const snapshot = buildSnapshotFromProjections(scopedStore, [ + const snapshot = await buildSnapshotFromProjections(scopedStore, [ 'test_scope/test_metric', ]); @@ -275,7 +275,7 @@ describe('Onion Middleware Order', () => { // ── Test 6: Graceful Unregistered Projection Handling ───────── describe('Graceful Unregistered Projection Handling', () => { - test('should handle missing projections with null value and warning', () => { + test('should handle missing projections with null value and warning', async () => { // Mock console.warn to capture warnings const originalWarn = console.warn; const warnings: string[] = []; @@ -283,7 +283,7 @@ describe('Graceful Unregistered Projection Handling', () => { warnings.push(args.join(' ')); }; - const snapshot = buildSnapshotFromProjections(scopedStore, [ + const snapshot = await buildSnapshotFromProjections(scopedStore, [ 'nonexistent/projection', 'another/missing', ]); @@ -307,7 +307,7 @@ describe('Graceful Unregistered Projection Handling', () => { console.warn = originalWarn; }); - test('should handle scope database errors gracefully', () => { + test('should handle scope database errors gracefully', async () => { const originalWarn = console.warn; const warnings: string[] = []; console.warn = (...args: any[]) => { @@ -315,7 +315,7 @@ describe('Graceful Unregistered Projection Handling', () => { }; // Force an error by trying to access invalid scope name - const snapshot = buildSnapshotFromProjections(scopedStore, [ + const snapshot = await buildSnapshotFromProjections(scopedStore, [ 'invalid-scope-name!/projection', ]); @@ -339,7 +339,7 @@ describe('E2E Tick Loop', () => { async () => { // Write initial event (triggers scope db creation with def tables) const testScope = scopedStore.scope('test'); - testScope.appendEvent({ + await testScope.appendEvent({ occurredAt: Date.now(), kind: 'increment', codeRev: CODEREV, diff --git a/packages/pulse/src/scoped-store.test.ts b/packages/pulse/src/scoped-store.test.ts index 3f25f95..c33c3f0 100644 --- a/packages/pulse/src/scoped-store.test.ts +++ b/packages/pulse/src/scoped-store.test.ts @@ -23,8 +23,8 @@ describe('createScopedStore', () => { scopedStore = createScopedStore({ basePath, objectsDir }); }); - afterEach(() => { - scopedStore.close(); + afterEach(async () => { + await scopedStore.close(); rmSync(dir, { recursive: true, force: true }); }); @@ -37,9 +37,9 @@ describe('createScopedStore', () => { // ── 2. scope() returns a usable PulseStore ────────────────── - it('2. scope("_system") returns a PulseStore that can append/query events', () => { + it('2. scope("_system") returns a PulseStore that can append/query events', async () => { const store = scopedStore.scope('_system'); - const rec = store.appendEvent({ + const rec = await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'sys', @@ -48,7 +48,7 @@ describe('createScopedStore', () => { expect(typeof rec.id).toBe('number'); expect(rec.kind).toBe('tick'); - const latest = store.getLatest('tick'); + const latest = await store.getLatest('tick'); expect(latest).toBeTruthy(); expect(latest?.occurredAt).toBe(1000); }); @@ -73,18 +73,18 @@ describe('createScopedStore', () => { // ── 5. scope isolation ────────────────────────────────────── - it('5. events in different scopes are isolated', () => { + it('5. events in different scopes are isolated', async () => { const system = scopedStore.scope('_system'); const neko = scopedStore.scope('neko'); - system.appendEvent({ occurredAt: 1000, kind: 'tick' }); - system.appendEvent({ occurredAt: 2000, kind: 'tick' }); - neko.appendEvent({ occurredAt: 3000, kind: 'tick' }); + await system.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await system.appendEvent({ occurredAt: 2000, kind: 'tick' }); + await neko.appendEvent({ occurredAt: 3000, kind: 'tick' }); - expect(system.getRecent(10).length).toBe(2); - expect(neko.getRecent(10).length).toBe(1); - expect(system.hasEvents()).toBe(true); - expect(neko.hasEvents()).toBe(true); + await expect(system.getRecent(10).length).toBe(2); + await expect(neko.getRecent(10).length).toBe(1); + await expect(system.hasEvents()).toBe(true); + await expect(neko.hasEvents()).toBe(true); }); // ── 6. listScopes() ───────────────────────────────────────── @@ -111,28 +111,28 @@ describe('createScopedStore', () => { // ── 8. CAS shared across scopes ──────────────────────────── - it('8. CAS objects/ directory is shared across scopes', () => { + it('8. CAS objects/ directory is shared across scopes', async () => { const system = scopedStore.scope('_system'); const neko = scopedStore.scope('neko'); const data = { shared: true, value: 42 }; - const hash = system.putObject(data); + const hash = await system.putObject(data); - const fromNeko = neko.getObject(hash); + const fromNeko = await neko.getObject(hash); expect(fromNeko).toEqual(data); }); // ── 9. ScopedStore-level CAS ──────────────────────────────── - it('9. ScopedStore.putObject/getObject work independently of scopes', () => { + it('9. ScopedStore.putObject/getObject work independently of scopes', async () => { const data = { top: 'level' }; - const hash = scopedStore.putObject(data); + const hash = await scopedStore.putObject(data); - expect(scopedStore.getObject(hash)).toEqual(data); + await expect(scopedStore.getObject(hash)).toEqual(data); // Also accessible from any scope const store = scopedStore.scope('_system'); - expect(store.getObject(hash)).toEqual(data); + await expect(store.getObject(hash)).toEqual(data); }); // ── 10. scope name validation: valid names ────────────────── @@ -175,63 +175,63 @@ describe('createScopedStore', () => { // ── 14. close() closes all open scope dbs ─────────────────── - it('14. close() closes all open scope databases', () => { + it('14. close() closes all open scope databases', async () => { const system = scopedStore.scope('_system'); const neko = scopedStore.scope('neko'); - system.appendEvent({ occurredAt: 1000, kind: 'tick' }); - neko.appendEvent({ occurredAt: 2000, kind: 'tick' }); + await system.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await neko.appendEvent({ occurredAt: 2000, kind: 'tick' }); - scopedStore.close(); + await scopedStore.close(); // After close, accessing the db should throw expect(() => - system.appendEvent({ occurredAt: 3000, kind: 'tick' }), + await system.appendEvent({ occurredAt: 3000, kind: 'tick' }), ).toThrow(); expect(() => - neko.appendEvent({ occurredAt: 3000, kind: 'tick' }), + await neko.appendEvent({ occurredAt: 3000, kind: 'tick' }), ).toThrow(); }); // ── 15. full PulseStore API on scope ──────────────────────── - it('15. scope PulseStore supports appendEvents (batch)', () => { + it('15. scope PulseStore supports appendEvents (batch)', async () => { const store = scopedStore.scope('_system'); - const results = store.appendEvents([ + const results = await store.appendEvents([ { occurredAt: 1000, kind: 'tick' }, { occurredAt: 2000, kind: 'collect', key: 'cpu' }, { occurredAt: 3000, kind: 'effect' }, ]); expect(results.length).toBe(3); - expect(store.getRecent(10).length).toBe(3); + await expect(store.getRecent(10).length).toBe(3); }); // ── 16. scope PulseStore supports queryByKind ─────────────── - it('16. scope PulseStore supports queryByKind with filters', () => { + it('16. scope PulseStore supports queryByKind with filters', async () => { const store = scopedStore.scope('_system'); - store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'v1' }); - store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'cpu' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick', codeRev: 'v2' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'v1' }); + await store.appendEvent({ occurredAt: 2000, kind: 'collect', key: 'cpu' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick', codeRev: 'v2' }); - const ticks = store.queryByKind('tick'); + const ticks = await store.queryByKind('tick'); expect(ticks.length).toBe(2); - const v1Ticks = store.queryByKind('tick', { codeRev: 'v1' }); + const v1Ticks = await store.queryByKind('tick', { codeRev: 'v1' }); expect(v1Ticks.length).toBe(1); expect(v1Ticks[0]?.occurredAt).toBe(1000); }); // ── 17. scope PulseStore supports getAfter ────────────────── - it('17. scope PulseStore supports getAfter', () => { + it('17. scope PulseStore supports getAfter', async () => { const store = scopedStore.scope('_system'); - const e1 = store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick' }); - store.appendEvent({ occurredAt: 3000, kind: 'collect' }); + const e1 = await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 3000, kind: 'collect' }); - const after = store.getAfter(e1.id); + const after = await store.getAfter(e1.id); expect(after.length).toBe(2); expect(after[0]?.occurredAt).toBe(2000); expect(after[1]?.occurredAt).toBe(3000); @@ -239,20 +239,20 @@ describe('createScopedStore', () => { // ── 18. scope PulseStore supports getLatestWhere ──────────── - it('18. scope PulseStore supports getLatestWhere', () => { + it('18. scope PulseStore supports getLatestWhere', async () => { const store = scopedStore.scope('_system'); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'promote', codeRev: 'v1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'promote', codeRev: 'v2', }); - const latest = store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); + const latest = await store.getLatestWhere({ kind: 'promote', codeRev: 'v1' }); expect(latest).toBeTruthy(); expect(latest?.codeRev).toBe('v1'); expect(latest?.occurredAt).toBe(1000); @@ -260,31 +260,31 @@ describe('createScopedStore', () => { // ── 19. archiveEvents / downsampleEvents on scope PulseStore ── - it('19. scope PulseStore supports archiveEvents and downsampleEvents', () => { + it('19. scope PulseStore supports archiveEvents and downsampleEvents', async () => { const store = scopedStore.scope('_vitals'); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'vital', key: 'cpu', hash: 'h1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'vital', key: 'cpu', hash: 'h2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 5000, kind: 'vital', key: 'cpu', hash: 'h3', }); - const archived = store.archiveEvents(3000); + const archived = await store.archiveEvents(3000); expect(archived).toBe(2); - const remaining = store.queryByKind('vital', { key: 'cpu' }); + const remaining = await store.queryByKind('vital', { key: 'cpu' }); expect(remaining.length).toBe(1); expect(remaining[0]?.hash).toBe('h3'); }); @@ -303,20 +303,20 @@ describe('createStore backward compatibility', () => { rmSync(dir, { recursive: true, force: true }); }); - it('20. createStore accepts (and ignores) legacy vitalsDbPath option', () => { + it('20. createStore accepts (and ignores) legacy vitalsDbPath option', async () => { const store = createStore({ eventsDbPath: join(dir, 'events.db'), vitalsDbPath: join(dir, 'vitals.db'), objectsDir: join(dir, 'objects'), }); - const rec = store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + const rec = await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); expect(typeof rec.id).toBe('number'); const data = { test: true }; - const hash = store.putObject(data); - expect(store.getObject(hash)).toEqual(data); + const hash = await store.putObject(data); + await expect(store.getObject(hash)).toEqual(data); - store.close(); + await store.close(); }); }); diff --git a/packages/pulse/src/store.test.ts b/packages/pulse/src/store.test.ts index bb53f6e..545b30c 100644 --- a/packages/pulse/src/store.test.ts +++ b/packages/pulse/src/store.test.ts @@ -27,13 +27,13 @@ describe('createStore (events table + CAS)', () => { // ── 1. Schema 创建 ─────────────────────────────────────────── - it('1. Schema: createStore creates DB with events table', () => { + it('1. Schema: createStore creates DB with events table', async () => { const store = createStore({ eventsDbPath, objectsDir }); expect(existsSync(eventsDbPath)).toBeTruthy(); expect(existsSync(objectsDir)).toBeTruthy(); // Verify events table exists by querying it - store.getRecent(1); // would throw if table doesn't exist - store.close(); + await store.getRecent(1); // would throw if table doesn't exist + await store.close(); }); // ── 2. appendEvent ──────────────────────────────────────────── @@ -56,12 +56,12 @@ describe('createStore (events table + CAS)', () => { // ── 3. ID 唯一性 ─────────────────────────────────────────── - it('3. ID uniqueness: 100 consecutive appends produce 100 unique ids', () => { + it('3. ID uniqueness: 100 consecutive appends produce 100 unique ids', async () => { const store = createStore({ eventsDbPath, objectsDir }); const ids = new Set(); for (let i = 0; i < 100; i++) { - const rec = store.appendEvent({ + const rec = await store.appendEvent({ occurredAt: 1000, kind: 'tick', }); @@ -69,17 +69,17 @@ describe('createStore (events table + CAS)', () => { } expect(ids.size).toBe(100); - store.close(); + await store.close(); }); // ── 4. AUTOINCREMENT 有序性 ────────────────────────────────── - it('4. AUTOINCREMENT ordering: all ids are unique and sequential', () => { + it('4. AUTOINCREMENT ordering: all ids are unique and sequential', async () => { const store = createStore({ eventsDbPath, objectsDir }); const ids: number[] = []; for (let i = 1; i <= 20; i++) { - const rec = store.appendEvent({ + const rec = await store.appendEvent({ occurredAt: i * 1000, kind: 'tick', }); @@ -93,77 +93,77 @@ describe('createStore (events table + CAS)', () => { expect(ids[i]!).toBeGreaterThan(ids[i - 1]!); } // getAfter with the first id should return the rest - const afterFirst = store.getAfter(ids[0]!); + const afterFirst = await store.getAfter(ids[0]!); expect(afterFirst.length).toBe(19); expect(afterFirst[0]!.id).toBe(ids[1]!); - store.close(); + await store.close(); }); // ── 5. getLatest(kind) ──────────────────────────────────────── - it('5. getLatest(kind) returns the most recent event of that kind', () => { + it('5. getLatest(kind) returns the most recent event of that kind', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'a' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'b' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'c' }); - store.appendEvent({ occurredAt: 2500, kind: 'collect' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'a' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'b' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'c' }); + await store.appendEvent({ occurredAt: 2500, kind: 'collect' }); - const latest = store.getLatest('tick'); + const latest = await store.getLatest('tick'); expect(latest).toBeTruthy(); expect(latest?.occurredAt).toBe(3000); expect(latest?.kind).toBe('tick'); expect(latest?.key).toBe('c'); - store.close(); + await store.close(); }); // ── 6. getLatest(kind, key) ─────────────────────────────────── - it('6. getLatest(kind, key) filters by key', () => { + it('6. getLatest(kind, key) filters by key', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'cpu' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'mem' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'cpu' }); - store.appendEvent({ occurredAt: 4000, kind: 'tick', key: 'mem' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'cpu' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'mem' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'cpu' }); + await store.appendEvent({ occurredAt: 4000, kind: 'tick', key: 'mem' }); - const latestCpu = store.getLatest('tick', 'cpu'); + const latestCpu = await store.getLatest('tick', 'cpu'); expect(latestCpu).toBeTruthy(); expect(latestCpu?.occurredAt).toBe(3000); expect(latestCpu?.key).toBe('cpu'); - const latestMem = store.getLatest('tick', 'mem'); + const latestMem = await store.getLatest('tick', 'mem'); expect(latestMem).toBeTruthy(); expect(latestMem?.occurredAt).toBe(4000); expect(latestMem?.key).toBe('mem'); - store.close(); + await store.close(); }); // ── 7. getLatestWhere + codeRev ─────────────────────────────── - it('7. getLatestWhere filters by codeRev', () => { + it('7. getLatestWhere filters by codeRev', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'cpu', codeRev: 'v1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'cpu', codeRev: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'cpu', codeRev: 'v1', }); - const latestV2 = store.getLatestWhere({ + const latestV2 = await store.getLatestWhere({ kind: 'tick', key: 'cpu', codeRev: 'v2', @@ -172,7 +172,7 @@ describe('createStore (events table + CAS)', () => { expect(latestV2?.occurredAt).toBe(2000); expect(latestV2?.codeRev).toBe('v2'); - const latestV1 = store.getLatestWhere({ + const latestV1 = await store.getLatestWhere({ kind: 'tick', key: 'cpu', codeRev: 'v1', @@ -180,113 +180,113 @@ describe('createStore (events table + CAS)', () => { expect(latestV1).toBeTruthy(); expect(latestV1?.occurredAt).toBe(3000); expect(latestV1?.codeRev).toBe('v1'); - store.close(); + await store.close(); }); // ── 8. getRecent ────────────────────────────────────────────── - it('8. getRecent returns newest N events in descending order', () => { + it('8. getRecent returns newest N events in descending order', async () => { const store = createStore({ eventsDbPath, objectsDir }); for (let i = 1; i <= 10; i++) { - store.appendEvent({ occurredAt: i * 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: i * 1000, kind: 'tick' }); } - const recent = store.getRecent(3); + const recent = await store.getRecent(3); expect(recent.length).toBe(3); expect(recent[0]?.occurredAt).toBe(10000); expect(recent[1]?.occurredAt).toBe(9000); expect(recent[2]?.occurredAt).toBe(8000); - store.close(); + await store.close(); }); // ── 9. queryByKind ──────────────────────────────────────────── - it('9. queryByKind returns events of specified kind', () => { + it('9. queryByKind returns events of specified kind', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - store.appendEvent({ occurredAt: 2000, kind: 'collect' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick' }); - store.appendEvent({ occurredAt: 4000, kind: 'error' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 2000, kind: 'collect' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 4000, kind: 'error' }); - const ticks = store.queryByKind('tick'); + const ticks = await store.queryByKind('tick'); expect(ticks.length).toBe(2); expect(ticks.every((e) => e.kind === 'tick')).toBeTruthy(); // Newest first expect(ticks[0]?.occurredAt).toBe(3000); expect(ticks[1]?.occurredAt).toBe(1000); - store.close(); + await store.close(); }); // ── 10. queryByKind + since ─────────────────────────────────── - it('10. queryByKind with since filters by occurred_at', () => { + it('10. queryByKind with since filters by occurred_at', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick' }); - store.appendEvent({ occurredAt: 4000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick' }); + await store.appendEvent({ occurredAt: 4000, kind: 'tick' }); - const result = store.queryByKind('tick', { since: 2500 }); + const result = await store.queryByKind('tick', { since: 2500 }); expect(result.length).toBe(2); expect(result[0]?.occurredAt).toBe(4000); expect(result[1]?.occurredAt).toBe(3000); - store.close(); + await store.close(); }); // ── 11. queryByKind + key ───────────────────────────────────── - it('11. queryByKind with key filter', () => { + it('11. queryByKind with key filter', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'cpu' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'mem' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'cpu' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'cpu' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick', key: 'mem' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick', key: 'cpu' }); - const result = store.queryByKind('tick', { key: 'cpu' }); + const result = await store.queryByKind('tick', { key: 'cpu' }); expect(result.length).toBe(2); expect(result.every((e) => e.key === 'cpu')).toBeTruthy(); - store.close(); + await store.close(); }); // ── 12. queryByKind + codeRev ───────────────────────────────── - it('12. queryByKind with codeRev filter', () => { + it('12. queryByKind with codeRev filter', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'abc123' }); - store.appendEvent({ occurredAt: 2000, kind: 'tick', codeRev: 'def456' }); - store.appendEvent({ occurredAt: 3000, kind: 'tick', codeRev: 'abc123' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'abc123' }); + await store.appendEvent({ occurredAt: 2000, kind: 'tick', codeRev: 'def456' }); + await store.appendEvent({ occurredAt: 3000, kind: 'tick', codeRev: 'abc123' }); - const result = store.queryByKind('tick', { codeRev: 'abc123' }); + const result = await store.queryByKind('tick', { codeRev: 'abc123' }); expect(result.length).toBe(2); expect(result.every((e) => e.codeRev === 'abc123')).toBeTruthy(); - store.close(); + await store.close(); }); // ── 13. getAfter ────────────────────────────────────────────── - it('13. getAfter returns events after a specific id', () => { + it('13. getAfter returns events after a specific id', async () => { const store = createStore({ eventsDbPath, objectsDir }); - const e1 = store.appendEvent({ occurredAt: 1000, kind: 'tick' }); - const e2 = store.appendEvent({ occurredAt: 2000, kind: 'tick' }); - const _e3 = store.appendEvent({ occurredAt: 3000, kind: 'tick' }); - const _e4 = store.appendEvent({ occurredAt: 4000, kind: 'collect' }); + const e1 = await store.appendEvent({ occurredAt: 1000, kind: 'tick' }); + const e2 = await store.appendEvent({ occurredAt: 2000, kind: 'tick' }); + const _e3 = await store.appendEvent({ occurredAt: 3000, kind: 'tick' }); + const _e4 = await store.appendEvent({ occurredAt: 4000, kind: 'collect' }); - const after = store.getAfter(e2.id); + const after = await store.getAfter(e2.id); expect(after.length).toBe(2); // ASC order expect(after[0]?.occurredAt).toBe(3000); expect(after[1]?.occurredAt).toBe(4000); // With kind filter - const afterTick = store.getAfter(e1.id, { kind: 'tick' }); + const afterTick = await store.getAfter(e1.id, { kind: 'tick' }); expect(afterTick.length).toBe(2); expect(afterTick.every((e) => e.kind === 'tick')).toBeTruthy(); - store.close(); + await store.close(); }); // ── 14. hasEvents ───────────────────────────────────────────── @@ -326,40 +326,40 @@ describe('createStore (events table + CAS)', () => { // ── 16. CAS 去重 ────────────────────────────────────────────── - it('16. CAS dedup: same data produces same hash', () => { + it('16. CAS dedup: same data produces same hash', async () => { const store = createStore({ eventsDbPath, objectsDir }); const data = { hello: 'world' }; - const hash1 = store.putObject(data); - const hash2 = store.putObject({ hello: 'world' }); + const hash1 = await store.putObject(data); + const hash2 = await store.putObject({ hello: 'world' }); expect(hash1).toBe(hash2); // Different data produces different hash - const hash3 = store.putObject({ hello: 'other' }); + const hash3 = await store.putObject({ hello: 'other' }); expect(hash1).not.toBe(hash3); - store.close(); + await store.close(); }); // ── 17. 多种 kind 并存 ──────────────────────────────────────── - it('17. Multiple kinds coexist: tick/collect/effect/error', () => { + it('17. Multiple kinds coexist: tick/collect/effect/error', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'sys' }); - store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'cpu' }); - store.appendEvent({ occurredAt: 1000, kind: 'effect', key: 'notify' }); - store.appendEvent({ occurredAt: 1000, kind: 'error', key: 'timeout' }); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', key: 'sys' }); + await store.appendEvent({ occurredAt: 1000, kind: 'collect', key: 'cpu' }); + await store.appendEvent({ occurredAt: 1000, kind: 'effect', key: 'notify' }); + await store.appendEvent({ occurredAt: 1000, kind: 'error', key: 'timeout' }); - expect(store.queryByKind('tick').length).toBe(1); - expect(store.queryByKind('collect').length).toBe(1); - expect(store.queryByKind('effect').length).toBe(1); - expect(store.queryByKind('error').length).toBe(1); - expect(store.getRecent().length).toBe(4); - store.close(); + expect((await store.queryByKind('tick')).length).toBe(1); + expect((await store.queryByKind('collect')).length).toBe(1); + expect((await store.queryByKind('effect')).length).toBe(1); + expect((await store.queryByKind('error')).length).toBe(1); + expect((await store.getRecent()).length).toBe(4); + await store.close(); }); // ── 18. appendEvents 批量 ───────────────────────────────────── - it('18. appendEvents batch inserts in a transaction', () => { + it('18. appendEvents batch inserts in a transaction', async () => { const store = createStore({ eventsDbPath, objectsDir }); const events = [ @@ -368,7 +368,7 @@ describe('createStore (events table + CAS)', () => { { occurredAt: 3000, kind: 'effect' as const, key: 'c' }, ]; - const results = store.appendEvents(events); + const results = await store.appendEvents(events); expect(results.length).toBe(3); // Each result should have a unique integer id @@ -376,21 +376,21 @@ describe('createStore (events table + CAS)', () => { expect(ids.size).toBe(3); // All should be retrievable - expect(store.getRecent(10).length).toBe(3); + expect((await store.getRecent(10)).length).toBe(3); // Empty batch doesn't throw - const empty = store.appendEvents([]); + const empty = await store.appendEvents([]); expect(empty.length).toBe(0); - store.close(); + await store.close(); }); // ── Extra: appendEvent with all optional fields ─────────────── - it('appendEvent stores all optional fields (hash, codeRev, meta)', () => { + it('appendEvent stores all optional fields (hash, codeRev, meta)', async () => { const store = createStore({ eventsDbPath, objectsDir }); - const hash = store.putObject({ data: 'test' }); + const hash = await store.putObject({ data: 'test' }); - const rec = store.appendEvent({ + const rec = await store.appendEvent({ occurredAt: 5000, kind: 'tick', key: 'system', @@ -404,35 +404,35 @@ describe('createStore (events table + CAS)', () => { expect(rec.meta).toBe(JSON.stringify({ duration: 42 })); // Verify via getLatest - const latest = store.getLatest('tick', 'system'); + const latest = await store.getLatest('tick', 'system'); expect(latest).toBeTruthy(); expect(latest?.hash).toBe(hash); expect(latest?.codeRev).toBe('abc123'); expect(latest?.meta).toBe(JSON.stringify({ duration: 42 })); - store.close(); + await store.close(); }); - it('getLatest returns null for non-existent kind', () => { + it('getLatest returns null for non-existent kind', async () => { const store = createStore({ eventsDbPath, objectsDir }); - expect(store.getLatest('nonexistent')).toBe(null); - store.close(); + expect(await store.getLatest('nonexistent')).toBe(null); + await store.close(); }); - it('getLatestWhere returns null when no match', () => { + it('getLatestWhere returns null when no match', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'v1' }); - expect(store.getLatestWhere({ kind: 'tick', codeRev: 'v99' })).toBeNull(); - store.close(); + await store.appendEvent({ occurredAt: 1000, kind: 'tick', codeRev: 'v1' }); + expect(await store.getLatestWhere({ kind: 'tick', codeRev: 'v99' })).toBeNull(); + await store.close(); }); - it('queryByKind with limit', () => { + it('queryByKind with limit', async () => { const store = createStore({ eventsDbPath, objectsDir }); for (let i = 1; i <= 10; i++) { - store.appendEvent({ occurredAt: i * 1000, kind: 'tick' }); + await store.appendEvent({ occurredAt: i * 1000, kind: 'tick' }); } - const result = store.queryByKind('tick', { limit: 3 }); + const result = await store.queryByKind('tick', { limit: 3 }); expect(result.length).toBe(3); - store.close(); + await store.close(); }); }); @@ -453,126 +453,126 @@ describe('archiveEvents and downsampleEvents', () => { rmSync(dir, { recursive: true, force: true }); }); - it('archiveEvents deletes events older than threshold', () => { + it('archiveEvents deletes events older than threshold', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1000, kind: 'vital', key: 'cpu', hash: 'h1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 2000, kind: 'vital', key: 'cpu', hash: 'h2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 5000, kind: 'vital', key: 'cpu', hash: 'h3', }); - const archived = store.archiveEvents(3000); + const archived = await store.archiveEvents(3000); expect(archived).toBe(2); - const remaining = store.queryByKind('vital', { key: 'cpu' }); + const remaining = await store.queryByKind('vital', { key: 'cpu' }); expect(remaining.length).toBe(1); expect(remaining[0]?.hash).toBe('h3'); - store.close(); + await store.close(); }); - it('downsampleEvents keeps one per interval window', () => { + it('downsampleEvents keeps one per interval window', async () => { const store = createStore({ eventsDbPath, objectsDir }); // Window 0: [0-999], Window 1: [1000-1999] - store.appendEvent({ + await store.appendEvent({ occurredAt: 100, kind: 'vital', key: 'cpu', hash: 'w0_1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 500, kind: 'vital', key: 'cpu', hash: 'w0_2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 900, kind: 'vital', key: 'cpu', hash: 'w0_3', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1100, kind: 'vital', key: 'cpu', hash: 'w1_1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1500, kind: 'vital', key: 'cpu', hash: 'w1_2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 1900, kind: 'vital', key: 'cpu', hash: 'w1_3', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 5000, kind: 'vital', key: 'cpu', hash: 'recent', }); - const deleted = store.downsampleEvents('vital', 'cpu', 1000, 3000); + const deleted = await store.downsampleEvents('vital', 'cpu', 1000, 3000); expect(deleted).toBe(4); - const remaining = store.queryByKind('vital', { key: 'cpu', limit: 10 }); + const remaining = await store.queryByKind('vital', { key: 'cpu', limit: 10 }); expect(remaining.length).toBe(3); const hashes = remaining.map((v) => v.hash); expect(hashes.includes('w0_3')).toBeTruthy(); expect(hashes.includes('w1_3')).toBeTruthy(); expect(hashes.includes('recent')).toBeTruthy(); - store.close(); + await store.close(); }); - it('downsampleEvents only affects specified kind+key', () => { + it('downsampleEvents only affects specified kind+key', async () => { const store = createStore({ eventsDbPath, objectsDir }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 100, kind: 'vital', key: 'cpu', hash: 'v1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 500, kind: 'vital', key: 'cpu', hash: 'v2', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 100, kind: 'tick', key: 'cpu', hash: 't1', }); - store.appendEvent({ + await store.appendEvent({ occurredAt: 500, kind: 'vital', key: 'mem', hash: 'm1', }); - const deleted = store.downsampleEvents('vital', 'cpu', 1000, 3000); + const deleted = await store.downsampleEvents('vital', 'cpu', 1000, 3000); expect(deleted).toBe(1); - expect(store.queryByKind('tick').length).toBe(1); - expect(store.queryByKind('vital', { key: 'mem' }).length).toBe(1); - store.close(); + expect((await store.queryByKind('tick')).length).toBe(1); + expect((await store.queryByKind('vital', { key: 'mem' })).length).toBe(1); + await store.close(); }); }); diff --git a/packages/pulse/src/workflows/coding-tdd.test.ts b/packages/pulse/src/workflows/coding-tdd.test.ts index 263f47a..30f1fa6 100644 --- a/packages/pulse/src/workflows/coding-tdd.test.ts +++ b/packages/pulse/src/workflows/coding-tdd.test.ts @@ -27,14 +27,14 @@ describe('coding-tdd WorkflowType', () => { function cleanup() { try { - store?.close(); + await store?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); } function trigger(topicId: string, title: string, description: string) { - const hash = store.putObject(description); - store.appendEvent({ + const hash = await store.putObject(description); + await store.appendEvent({ occurredAt: Date.now(), kind: 'coding-tdd.__start__', key: topicId, diff --git a/packages/pulse/src/workflows/coding.test.ts b/packages/pulse/src/workflows/coding.test.ts index 888e2e0..ac9538a 100644 --- a/packages/pulse/src/workflows/coding.test.ts +++ b/packages/pulse/src/workflows/coding.test.ts @@ -24,9 +24,9 @@ describe('CodingTask WorkflowType', () => { }); } - afterEach(() => { + afterEach(async () => { try { - store?.close(); + await store?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); @@ -37,8 +37,8 @@ describe('CodingTask WorkflowType', () => { description: string, repoDir: string, ) { - const hash = store.putObject(description); - store.appendEvent({ + const hash = await store.putObject(description); + await store.appendEvent({ occurredAt: Date.now(), kind: 'coding.__start__', key: topicId, @@ -67,7 +67,7 @@ describe('CodingTask WorkflowType', () => { expect(r1.executed).toMatchObject([ { topicId: 'task-1', role: 'architect' }, ]); - expect(store.queryByKind('coding.architect').length).toBe(1); + await expect(store.queryByKind('coding.architect').length).toBe(1); const r2 = await rule.tick(); expect(r2.executed).toMatchObject([{ topicId: 'task-1', role: 'coder' }]); @@ -117,7 +117,7 @@ describe('CodingTask WorkflowType', () => { await rule.tick(); // coder await rule.tick(); // reviewer (rejects, retryCount=0) - const reviewerEvents = store.queryByKind('coding.reviewer'); + const reviewerEvents = await store.queryByKind('coding.reviewer'); expect(reviewerEvents.length).toBe(1); const meta = JSON.parse(reviewerEvents[0].meta!); expect(meta.verdict).toBe('rejected'); @@ -198,12 +198,12 @@ describe('CodingTask WorkflowType', () => { triggerCoding('task-3', 'CAS test', 'Verify CAS storage', '/tmp/repo'); await rule.tick(); // architect - const events = store.queryByKind('coding.architect'); + const events = await store.queryByKind('coding.architect'); expect(events.length).toBe(1); expect(events[0].hash).toBeTruthy(); // CAS content should be a string (not object) - const content = store.getObject(events[0].hash!); + const content = await store.getObject(events[0].hash!); expect(typeof content).toBe('string'); }); @@ -227,11 +227,11 @@ describe('CodingTask WorkflowType', () => { ); await rule.tick(); - expect(logStore.queryByKind('coding.role-started').length).toBe(1); - expect(logStore.queryByKind('coding.role-completed').length).toBe(1); - expect(store.queryByKind('coding.role-started').length).toBe(0); + await expect(logStore.queryByKind('coding.role-started').length).toBe(1); + await expect(logStore.queryByKind('coding.role-completed').length).toBe(1); + await expect(store.queryByKind('coding.role-started').length).toBe(0); } finally { - logStore.close(); + await logStore.close(); rmSync(logTmpDir, { recursive: true, force: true }); } }); diff --git a/packages/pulse/src/workflows/cursor-health.test.ts b/packages/pulse/src/workflows/cursor-health.test.ts index 5402b13..5d3d07c 100644 --- a/packages/pulse/src/workflows/cursor-health.test.ts +++ b/packages/pulse/src/workflows/cursor-health.test.ts @@ -34,7 +34,7 @@ describe('cursor-health', () => { stmt.run(baseTime + offset); } - db.close(); + await db.close(); return tempDbPath; } diff --git a/packages/pulse/src/workflows/meta.test.ts b/packages/pulse/src/workflows/meta.test.ts index 305461c..180cc73 100644 --- a/packages/pulse/src/workflows/meta.test.ts +++ b/packages/pulse/src/workflows/meta.test.ts @@ -128,8 +128,8 @@ describe('Meta Workflow', () => { const rule = createWorkflowRule(wf, store); // Seed START - const hash = store.putObject('build a demo workflow'); - store.appendEvent({ + const hash = await store.putObject('build a demo workflow'); + await store.appendEvent({ occurredAt: Date.now(), kind: 'meta.__start__', key: 'test-1', @@ -152,10 +152,10 @@ describe('Meta Workflow', () => { 'promoter', ]); - const events = store.getAfter(0); + const events = await store.getAfter(0); expect(events.length).toBe(6); // __start__ + 5 roles - store.close(); + await store.close(); }); test('mock: reviewer rejection → retry coder', async () => { @@ -193,8 +193,8 @@ describe('Meta Workflow', () => { }); const rule = createWorkflowRule(wf, store); - const hash = store.putObject('test retry'); - store.appendEvent({ + const hash = await store.putObject('test retry'); + await store.appendEvent({ occurredAt: Date.now(), kind: 'meta.__start__', key: 'retry-1', @@ -218,6 +218,6 @@ describe('Meta Workflow', () => { 'tester', 'promoter', ]); - store.close(); + await store.close(); }); }); diff --git a/packages/pulse/src/workflows/report.test.ts b/packages/pulse/src/workflows/report.test.ts index 8a81655..9821e2b 100644 --- a/packages/pulse/src/workflows/report.test.ts +++ b/packages/pulse/src/workflows/report.test.ts @@ -16,7 +16,7 @@ function tmpStore(): { store: Store; cleanup: () => void } { eventsDbPath: `${dir}/events.db`, objectsDir: `${dir}/objects`, }); - return { store, cleanup: () => store.close() }; + await return { store, cleanup: () => store.close() }; } describe('Report Workflow', () => { @@ -47,8 +47,8 @@ describe('Report Workflow', () => { { id: 5, role: 'closer', offsetMs: 10000, durationMs: 1000 }, ], }); - const hash = store.putObject(timeline); - store.appendEvent({ + const hash = await store.putObject(timeline); + await store.appendEvent({ occurredAt: Date.now(), kind: 'report.__start__', key: 'test-report', @@ -66,10 +66,10 @@ describe('Report Workflow', () => { expect(r2.executed[0].role).toBe('renderer'); // Verify renderer output is HTML - const events = store.getAfter(0); + const events = await store.getAfter(0); const rendererEvt = events.find((e) => e.kind === 'report.renderer'); expect(rendererEvt).toBeDefined(); - const html = store.getObject(rendererEvt!.hash!); + const html = await store.getObject(rendererEvt!.hash!); expect(typeof html).toBe('string'); expect((html as string).includes('')).toBe(true); diff --git a/packages/pulse/src/workflows/roles/coder-cursor.test.ts b/packages/pulse/src/workflows/roles/coder-cursor.test.ts index cf439d0..2e33ed5 100644 --- a/packages/pulse/src/workflows/roles/coder-cursor.test.ts +++ b/packages/pulse/src/workflows/roles/coder-cursor.test.ts @@ -23,9 +23,9 @@ describe('coder-cursor role', () => { }); } - afterEach(() => { + afterEach(async () => { try { - store?.close(); + await store?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); @@ -41,14 +41,14 @@ describe('coder-cursor role', () => { ) => { const architectMsg = chain.find((m) => m.role === 'architect'); const architectContent = (architectMsg?.content as any) ?? {}; - const hash = s.putObject({ + const hash = await s.putObject({ content: 'Mock implementation done', artifacts: { filesChanged: architectContent.artifacts?.targetFiles ?? [], testsPassed: true, }, }); - s.appendEvent({ + await s.appendEvent({ occurredAt: Date.now(), kind: 'coding.coder', key: topicId, @@ -77,12 +77,12 @@ describe('coder-cursor role', () => { await mockCoderFn(chain, 'coder-test-1', store); - const events = store.getAfter(0); + const events = await store.getAfter(0); const coded = events.find((e) => e.kind === 'coding.coder'); expect(coded).toBeDefined(); expect(coded!.hash).toBeTruthy(); - const content = store.getObject(coded!.hash!) as any; + const content = await store.getObject(coded!.hash!) as any; expect(content.content).toBe('Mock implementation done'); expect(content.artifacts.filesChanged).toEqual(['src/utils.ts']); expect(content.artifacts.testsPassed).toBe(true); diff --git a/packages/pulse/src/workflows/roles/reviewer-cursor.test.ts b/packages/pulse/src/workflows/roles/reviewer-cursor.test.ts index 081461e..093ab83 100644 --- a/packages/pulse/src/workflows/roles/reviewer-cursor.test.ts +++ b/packages/pulse/src/workflows/roles/reviewer-cursor.test.ts @@ -23,9 +23,9 @@ describe('reviewer-cursor role', () => { }); } - afterEach(() => { + afterEach(async () => { try { - store?.close(); + await store?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); @@ -38,8 +38,8 @@ describe('reviewer-cursor role', () => { topicId: string, s: PulseStore, ) => { - const hash = s.putObject({ content: 'LGTM, code looks good. APPROVED.' }); - s.appendEvent({ + const hash = await s.putObject({ content: 'LGTM, code looks good. APPROVED.' }); + await s.appendEvent({ occurredAt: Date.now(), kind: 'coding.reviewer', key: topicId, @@ -77,14 +77,14 @@ describe('reviewer-cursor role', () => { await mockReviewerFn(chain, 'review-test-1', store); - const events = store.getAfter(0); + const events = await store.getAfter(0); const reviewed = events.find((e) => e.kind === 'coding.reviewer'); expect(reviewed).toBeDefined(); expect(reviewed!.hash).toBeTruthy(); const meta = JSON.parse(reviewed!.meta!); expect(meta.verdict).toBe('approved'); - const content = store.getObject(reviewed!.hash!) as any; + const content = await store.getObject(reviewed!.hash!) as any; expect(content.content).toContain('APPROVED'); }); @@ -102,8 +102,8 @@ describe('reviewer-cursor role', () => { .includes('rejected') ? 'rejected' : 'approved'; - const hash = s.putObject({ content: output }); - s.appendEvent({ + const hash = await s.putObject({ content: output }); + await s.appendEvent({ occurredAt: Date.now(), kind: 'coding.reviewer', key: topicId, @@ -133,7 +133,7 @@ describe('reviewer-cursor role', () => { await mockReviewerFn(chain, 'review-test-2', store); - const events = store.getAfter(0); + const events = await store.getAfter(0); const reviewed = events.find((e) => e.kind === 'coding.reviewer'); const meta = JSON.parse(reviewed!.meta!); expect(meta.verdict).toBe('rejected'); diff --git a/packages/pulse/src/workflows/workflow-rule-adapter.test.ts b/packages/pulse/src/workflows/workflow-rule-adapter.test.ts index da33468..4573249 100644 --- a/packages/pulse/src/workflows/workflow-rule-adapter.test.ts +++ b/packages/pulse/src/workflows/workflow-rule-adapter.test.ts @@ -29,12 +29,12 @@ describe('createWorkflowRule', () => { }); } - afterEach(() => { + afterEach(async () => { try { - store?.close(); + await store?.close(); } catch {} try { - logStore?.close(); + await logStore?.close(); } catch {} if (tmpDir) rmSync(tmpDir, { recursive: true, force: true }); }); @@ -45,8 +45,8 @@ describe('createWorkflowRule', () => { content: string, meta?: Record, ) { - const hash = store.putObject(content); - store.appendEvent({ + const hash = await store.putObject(content); + await store.appendEvent({ occurredAt: Date.now(), kind: `${name}.__start__`, key: topicId, @@ -87,21 +87,21 @@ describe('createWorkflowRule', () => { expect(r1.executed).toMatchObject([{ topicId: 't1', role: 'echo' }]); // Verify adapter wrote the event - const echoEvents = store.queryByKind('echo.echo'); + const echoEvents = await store.queryByKind('echo.echo'); expect(echoEvents.length).toBe(1); expect(echoEvents[0].hash).toBeTruthy(); // Verify CAS content - const content = store.getObject(echoEvents[0].hash!); + const content = await store.getObject(echoEvents[0].hash!); expect(content).toBe('Echo: hello'); const r2 = await rule.tick(); expect(r2.executed).toEqual([]); // Verify logging - const started = logStore.queryByKind('echo.role-started'); + const started = await logStore.queryByKind('echo.role-started'); expect(started.length).toBe(1); - expect(store.queryByKind('echo.role-started').length).toBe(0); + await expect(store.queryByKind('echo.role-started').length).toBe(0); }); it('role failure is logged, does not crash tick', async () => { @@ -128,7 +128,7 @@ describe('createWorkflowRule', () => { const r1 = await rule.tick(); expect(r1.executed).toEqual([]); - const failed = logStore.queryByKind('fail.role-failed'); + const failed = await logStore.queryByKind('fail.role-failed'); expect(failed.length).toBe(1); expect(JSON.parse(failed[0].meta!).error).toBe('kaboom'); }); @@ -154,7 +154,7 @@ describe('createWorkflowRule', () => { const r1 = await rule.tick(); expect(r1.executed).toMatchObject([{ topicId: 't1', role: 'echo' }]); - expect(store.queryByKind('echo.role-started').length).toBe(0); + await expect(store.queryByKind('echo.role-started').length).toBe(0); }); it('Moore diff prevents re-execution', async () => { @@ -280,7 +280,7 @@ describe('createWorkflowRule', () => { await rule.tick(); - const events = store.queryByKind('nm.closer'); + const events = await store.queryByKind('nm.closer'); expect(events.length).toBe(1); // adapter writes undefined when meta is null expect(events[0].meta).toBeUndefined();