@furystack/core
Version:
Core FuryStack package
124 lines • 4.98 kB
JavaScript
import { createInjector } from '@furystack/inject';
import { usingAsync } from '@furystack/utils';
import { describe, expect, it, vi } from 'vitest';
import { TestClass } from './create-physical-store-tests.js';
import { defineStore } from './define-store.js';
import { InMemoryStore } from './in-memory-store.js';
class Test {
}
describe('defineStore', () => {
it('resolves to the physical store produced by its factory', async () => {
const TestStore = defineStore({
name: 'test/TestStore',
model: Test,
primaryKey: 'id',
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
});
await usingAsync(createInjector(), async (i) => {
expect(i.get(TestStore)).toBeInstanceOf(InMemoryStore);
});
});
it('caches the store as a singleton across resolutions', async () => {
const TestStore = defineStore({
name: 'test/SingletonStore',
model: Test,
primaryKey: 'id',
factory: () => new InMemoryStore({ model: Test, primaryKey: 'id' }),
});
await usingAsync(createInjector(), async (i) => {
expect(i.get(TestStore)).toBe(i.get(TestStore));
});
});
it('exposes the model and primary key on the token for metadata-driven consumers', () => {
const TestStore = defineStore({
name: 'test/MetaStore',
model: TestClass,
primaryKey: 'id',
factory: () => new InMemoryStore({ model: TestClass, primaryKey: 'id' }),
});
expect(TestStore.model).toBe(TestClass);
expect(TestStore.primaryKey).toBe('id');
});
it('invokes the factory only once even when resolved from multiple scopes', async () => {
const factory = vi.fn(() => new InMemoryStore({ model: Test, primaryKey: 'id' }));
const TestStore = defineStore({
name: 'test/OnceStore',
model: Test,
primaryKey: 'id',
factory,
});
await usingAsync(createInjector(), async (i) => {
await usingAsync(i.createScope(), async (child) => {
i.get(TestStore);
child.get(TestStore);
});
});
expect(factory).toHaveBeenCalledTimes(1);
});
it('disposes a sync-disposable store when the injector is disposed', async () => {
const disposeSpy = vi.fn();
class SyncStore extends InMemoryStore {
[Symbol.dispose] = () => {
disposeSpy();
super[Symbol.dispose]();
};
}
const TestStore = defineStore({
name: 'test/SyncDisposeStore',
model: Test,
primaryKey: 'id',
factory: () => new SyncStore({ model: Test, primaryKey: 'id' }),
});
const i = createInjector();
i.get(TestStore);
await i[Symbol.asyncDispose]();
expect(disposeSpy).toHaveBeenCalledTimes(1);
});
it('disposes an async-disposable store when the injector is disposed', async () => {
const disposeSpy = vi.fn(() => Promise.resolve());
class AsyncStore extends InMemoryStore {
[Symbol.asyncDispose] = disposeSpy;
}
const TestStore = defineStore({
name: 'test/AsyncDisposeStore',
model: Test,
primaryKey: 'id',
factory: () => new AsyncStore({ model: Test, primaryKey: 'id' }),
});
const i = createInjector();
i.get(TestStore);
await i[Symbol.asyncDispose]();
expect(disposeSpy).toHaveBeenCalledTimes(1);
});
it('aggregates errors from multiple failing store disposals', async () => {
class FailingAsyncStore extends InMemoryStore {
[Symbol.asyncDispose] = () => Promise.reject(new Error('async-fail'));
}
class FailingSyncStore extends InMemoryStore {
[Symbol.dispose] = () => {
throw new Error('sync-fail');
};
}
const AsyncFail = defineStore({
name: 'test/AsyncFailStore',
model: Test,
primaryKey: 'id',
factory: () => new FailingAsyncStore({ model: Test, primaryKey: 'id' }),
});
const SyncFail = defineStore({
name: 'test/SyncFailStore',
model: Test,
primaryKey: 'id',
factory: () => new FailingSyncStore({ model: Test, primaryKey: 'id' }),
});
const i = createInjector();
i.get(AsyncFail);
i.get(SyncFail);
const aggregate = await i[Symbol.asyncDispose]().then(() => undefined, (error) => error);
expect(aggregate).toBeInstanceOf(AggregateError);
const messages = aggregate.errors.map((e) => e.message);
expect(messages).toContain('async-fail');
expect(messages).toContain('sync-fail');
});
});
//# sourceMappingURL=define-store.spec.js.map