import { Effect, Schema, Stream, Tracer } from 'effect'; import { describe, expect, it } from 'vitest'; import { EntityESchema, ESchema } from '../../../../eschema/index.js'; import { Memory } from '../../../memory/index.js'; import { StdTable } from '../../table/index.js'; const table = StdTable.make('spans-test') .primary('pk', 'sk') .gsi('GSI1', 'GSI1PK', 'GSI1SK') .build(); const taskSchema = EntityESchema.make('Task', 'taskId', { board: Schema.String, title: Schema.String, }).build(); const task = table .entity(taskSchema) .primary({ pk: ['board'] }) .index('GSI1', 'byBoard', { pk: ['board'], sk: ['title'] }) .build(); const settingsSchema = ESchema.make('Settings', { theme: Schema.String, }).build(); const settings = table.singleEntity(settingsSchema).default({ theme: 'light' }); const recording = () => { const spans: Tracer.NativeSpan[] = []; const tracer = Tracer.make({ span(options) { const span = new Tracer.NativeSpan(options); spans.push(span); return span; }, }); return { spans, tracer }; }; const run = ( effect: Effect.Effect, tracer: Tracer.Tracer, ): Promise => Effect.runPromise(effect.pipe(Effect.withTracer(tracer))); const attributesOf = (span: Tracer.NativeSpan) => Object.fromEntries(span.attributes); describe('StdTable spans', () => { it('records insert and getAndUpdate on a keyed entity', async () => { const { spans, tracer } = recording(); const layer = Memory.make(table).layer; await run( Effect.gen(function* () { yield* task.insert({ taskId: 't1', board: 'b1', title: 'first' }); yield* task.getAndUpdate( { taskId: 't1', board: 'b1' }, { title: 'second' }, ); }).pipe(Effect.provide(layer)), tracer, ); const insert = spans.find((span) => span.name === 'StdTable.insert'); const update = spans.find((span) => span.name === 'StdTable.getAndUpdate'); expect(insert).toBeDefined(); expect(update).toBeDefined(); expect(attributesOf(insert!)).toEqual({ entity: 'Task' }); expect(attributesOf(update!)).toEqual({ entity: 'Task', taskId: 't1', board: 'b1', }); expect(insert!.status._tag).toBe('Ended'); expect(update!.status._tag).toBe('Ended'); }); it('records every keyed entity operation with its key fields', async () => { const { spans, tracer } = recording(); const layer = Memory.make(table).layer; const key = { taskId: 't1', board: 'b1' }; await run( Effect.gen(function* () { yield* task.insert({ ...key, title: 'first' }); yield* task.get(key); yield* task.query('byBoard', { pk: { board: 'b1' }, beginsWith: { title: '' }, }); yield* task.delete(key); yield* task.restore(key); yield* Stream.runDrain(task.subscribe()); yield* task.hardDelete(key, 'I KNOW WHAT I AM DOING'); }).pipe(Effect.provide(layer)), tracer, ); const named = Object.fromEntries( spans.map((span) => [span.name, attributesOf(span)]), ); expect(named['StdTable.get']).toEqual({ entity: 'Task', ...key }); expect(named['StdTable.query']).toEqual({ entity: 'Task', pattern: 'byBoard', }); expect(named['StdTable.delete']).toEqual({ entity: 'Task', ...key }); expect(named['StdTable.restore']).toEqual({ entity: 'Task', ...key }); expect(named['StdTable.hardDelete']).toEqual({ entity: 'Task', ...key }); expect(named['StdTable.subscribe']).toEqual({ entity: 'Task' }); }); it('records single entity operations', async () => { const { spans, tracer } = recording(); const layer = Memory.make(table).layer; await run( Effect.gen(function* () { yield* settings.get(); yield* settings.put({ theme: 'dark' }); yield* settings.getAndUpdate({ theme: 'blue' }); yield* settings.reset(); }).pipe(Effect.provide(layer)), tracer, ); const names = spans.map((span) => span.name); expect(names).toEqual([ 'StdTable.get', 'StdTable.put', 'StdTable.getAndUpdate', 'StdTable.reset', ]); for (const span of spans) expect(attributesOf(span)).toEqual({ entity: 'Settings' }); }); it('records table operations', async () => { const { spans, tracer } = recording(); const layer = Memory.make(table).layer; await run( Effect.gen(function* () { const op = yield* task.insertOp({ taskId: 't1', board: 'b1', title: 'first', }); yield* table.transact([op]); yield* Stream.runCollect(table.scan()); yield* Stream.runDrain(table.subscribe()); }).pipe(Effect.provide(layer)), tracer, ); const named = Object.fromEntries( spans.map((span) => [span.name, attributesOf(span)]), ); expect(named['StdTable.transact']).toEqual({ entity: 'spans-test' }); expect(named['StdTable.scan']).toEqual({ entity: 'spans-test' }); expect(named['StdTable.subscribe']).toEqual({ entity: 'spans-test' }); }); });