feat(platform-service): migrate 6 repositories to @bytelyst/datastore (flags, settings, comments, votes, products, themes) — 756/756 tests pass

This commit is contained in:
saravanakumardb1 2026-03-02 00:51:03 -08:00
parent dfa5eb73fa
commit 4d126cb051
13 changed files with 212 additions and 266 deletions

View File

@ -10,7 +10,6 @@
export interface BaseDocument {
id: string;
productId: string;
[key: string]: unknown;
}
// ── Filter operators ────────────────────────────────────────────────────────

3
pnpm-lock.yaml generated
View File

@ -558,6 +558,9 @@ importers:
'@bytelyst/cosmos':
specifier: workspace:*
version: link:../../packages/cosmos
'@bytelyst/datastore':
specifier: workspace:*
version: link:../../packages/datastore
'@bytelyst/errors':
specifier: workspace:*
version: link:../../packages/errors

View File

@ -19,6 +19,7 @@
"@bytelyst/blob": "workspace:*",
"@bytelyst/config": "workspace:*",
"@bytelyst/cosmos": "workspace:*",
"@bytelyst/datastore": "workspace:*",
"@bytelyst/errors": "workspace:*",
"@bytelyst/events": "workspace:*",
"@bytelyst/fastify-core": "workspace:*",

View File

@ -0,0 +1,67 @@
/**
* Cloud-agnostic datastore bridge for platform-service.
*
* Wraps @bytelyst/datastore with platform-service container registry config.
* Repositories import getCollection() from here instead of getContainer() from cosmos.
*
* Migration: Replace `import { getContainer } from '../../lib/cosmos.js'`
* with `import { getCollection } from '../../lib/datastore.js'`
*/
import {
type DatastoreProvider,
type DocumentCollection,
type BaseDocument,
setDatastore,
CosmosDatastoreProvider,
MemoryDatastoreProvider,
} from '@bytelyst/datastore';
let _provider: DatastoreProvider | null = null;
/**
* Initialize the datastore provider.
* Call once at service startup (before any repository calls).
*/
export function initDatastore(): DatastoreProvider {
if (_provider) return _provider;
const dbProvider = (process.env.DB_PROVIDER || 'cosmos').toLowerCase();
if (dbProvider === 'memory') {
_provider = new MemoryDatastoreProvider();
} else {
_provider = new CosmosDatastoreProvider();
}
setDatastore(_provider);
return _provider;
}
/**
* Inject a provider directly (for testing).
*/
export function setProvider(provider: DatastoreProvider): void {
_provider = provider;
}
/**
* Get a typed collection from the datastore.
* Drop-in replacement for getContainer() returns a DocumentCollection instead of a Cosmos Container.
*/
export function getCollection<T extends BaseDocument = BaseDocument>(
name: string,
partitionKeyPath: string = '/productId'
): DocumentCollection<T> {
if (!_provider) {
initDatastore();
}
return _provider!.getCollection<T>(name, partitionKeyPath);
}
/**
* @internal for testing only
*/
export function _resetDatastoreProvider(): void {
_provider = null;
}

View File

@ -1,45 +1,36 @@
/**
* Comments repository Cosmos DB CRUD.
* Comments repository cloud-agnostic via @bytelyst/datastore.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { CommentDoc } from './types.js';
function container() {
return getContainer('tracker_comments');
function collection() {
return getCollection<CommentDoc>('tracker_comments', '/id');
}
export async function listByItem(itemId: string): Promise<CommentDoc[]> {
const { resources } = await container()
.items.query<CommentDoc>({
query: 'SELECT * FROM c WHERE c.itemId = @itemId ORDER BY c.createdAt ASC',
parameters: [{ name: '@itemId', value: itemId }],
})
.fetchAll();
return resources;
return collection().findMany({
filter: { itemId },
sort: { createdAt: 1 },
});
}
export async function getById(id: string): Promise<CommentDoc | null> {
try {
const { resource } = await container().item(id, id).read<CommentDoc>();
return resource ?? null;
return await collection().findById(id, id);
} catch {
return null;
}
}
export async function create(doc: CommentDoc): Promise<CommentDoc> {
const { resource } = await container().items.create(doc);
return resource as CommentDoc;
return collection().create(doc);
}
export async function update(id: string, updates: Partial<CommentDoc>): Promise<CommentDoc | null> {
try {
const { resource: existing } = await container().item(id, id).read<CommentDoc>();
if (!existing) return null;
const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };
const { resource } = await container().item(id, id).replace(merged);
return resource as CommentDoc;
return await collection().update(id, id, { ...updates, updatedAt: new Date().toISOString() });
} catch {
return null;
}
@ -47,7 +38,7 @@ export async function update(id: string, updates: Partial<CommentDoc>): Promise<
export async function remove(id: string): Promise<boolean> {
try {
await container().item(id, id).delete();
await collection().delete(id, id);
return true;
} catch {
return false;
@ -55,11 +46,5 @@ export async function remove(id: string): Promise<boolean> {
}
export async function countByItem(itemId: string): Promise<number> {
const { resources } = await container()
.items.query<number>({
query: 'SELECT VALUE COUNT(1) FROM c WHERE c.itemId = @itemId',
parameters: [{ name: '@itemId', value: itemId }],
})
.fetchAll();
return resources[0] ?? 0;
return collection().count({ itemId });
}

View File

@ -1,40 +1,29 @@
/**
* Feature flags repository Cosmos DB CRUD.
* Feature flags repository cloud-agnostic via @bytelyst/datastore.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { FeatureFlagDoc } from './types.js';
function container() {
return getContainer('feature_flags');
function collection() {
return getCollection<FeatureFlagDoc>('feature_flags', '/productId');
}
export async function list(productId: string): Promise<FeatureFlagDoc[]> {
const { resources } = await container()
.items.query<FeatureFlagDoc>({
query: 'SELECT * FROM c WHERE c.productId = @productId ORDER BY c.key ASC',
parameters: [{ name: '@productId', value: productId }],
})
.fetchAll();
return resources;
return collection().findMany({
filter: { productId },
sort: { key: 1 },
});
}
export async function getByKey(key: string, productId: string): Promise<FeatureFlagDoc | null> {
const { resources } = await container()
.items.query<FeatureFlagDoc>({
query: 'SELECT * FROM c WHERE c.productId = @productId AND c.key = @key',
parameters: [
{ name: '@productId', value: productId },
{ name: '@key', value: key },
],
})
.fetchAll();
return resources[0] ?? null;
return collection().findOne({
filter: { productId, key },
});
}
export async function create(doc: FeatureFlagDoc): Promise<FeatureFlagDoc> {
const { resource } = await container().items.create(doc);
return resource as FeatureFlagDoc;
return collection().create(doc);
}
export async function update(
@ -42,11 +31,7 @@ export async function update(
updates: Partial<FeatureFlagDoc>
): Promise<FeatureFlagDoc | null> {
try {
const { resource: existing } = await container().item(id, id).read<FeatureFlagDoc>();
if (!existing) return null;
const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };
const { resource } = await container().item(id, id).replace(merged);
return resource as FeatureFlagDoc;
return await collection().update(id, id, { ...updates, updatedAt: new Date().toISOString() });
} catch {
return null;
}
@ -54,7 +39,7 @@ export async function update(
export async function remove(id: string): Promise<boolean> {
try {
await container().item(id, id).delete();
await collection().delete(id, id);
return true;
} catch {
return false;

View File

@ -1,24 +1,10 @@
/**
* Repository tests for products module mocked Cosmos DB.
* Repository tests for products module in-memory datastore.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockFetchAll = vi.fn();
const mockCreate = vi.fn();
const mockRead = vi.fn();
const mockReplace = vi.fn();
vi.mock('../../lib/cosmos.js', () => ({
getContainer: vi.fn(() => ({
items: {
query: () => ({ fetchAll: mockFetchAll }),
create: mockCreate,
},
item: () => ({ read: mockRead, replace: mockReplace }),
})),
}));
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import { MemoryDatastoreProvider } from '@bytelyst/datastore';
import { setProvider, _resetDatastoreProvider } from '../../lib/datastore.js';
import { getAll, getById, create, update } from './repository.js';
import type { ProductDoc } from './types.js';
@ -37,20 +23,27 @@ const baseProduct: ProductDoc = {
updatedAt: '2026-02-16T00:00:00Z',
};
let memProvider: MemoryDatastoreProvider;
describe('products repository', () => {
beforeEach(() => {
vi.clearAllMocks();
memProvider = new MemoryDatastoreProvider();
setProvider(memProvider);
});
afterAll(() => {
_resetDatastoreProvider();
});
describe('getAll', () => {
it('returns all products', async () => {
mockFetchAll.mockResolvedValue({ resources: [baseProduct] });
await create(baseProduct);
const result = await getAll();
expect(result).toEqual([baseProduct]);
expect(result).toHaveLength(1);
expect(result[0]!.id).toBe('lysnrai');
});
it('returns empty array when no products', async () => {
mockFetchAll.mockResolvedValue({ resources: [] });
const result = await getAll();
expect(result).toEqual([]);
});
@ -58,27 +51,19 @@ describe('products repository', () => {
describe('getById', () => {
it('returns product when found', async () => {
mockRead.mockResolvedValue({ resource: baseProduct });
await create(baseProduct);
const result = await getById('lysnrai');
expect(result).toEqual(baseProduct);
});
it('returns null when not found', async () => {
mockRead.mockRejectedValue(new Error('Not found'));
const result = await getById('nonexistent');
expect(result).toBeNull();
});
it('returns null when resource is undefined', async () => {
mockRead.mockResolvedValue({ resource: undefined });
const result = await getById('lysnrai');
expect(result).toBeNull();
});
});
describe('create', () => {
it('creates and returns product', async () => {
mockCreate.mockResolvedValue({ resource: baseProduct });
const result = await create(baseProduct);
expect(result).toEqual(baseProduct);
});
@ -86,21 +71,14 @@ describe('products repository', () => {
describe('update', () => {
it('merges updates and returns product', async () => {
mockRead.mockResolvedValue({ resource: baseProduct });
const updated = { ...baseProduct, displayName: 'LysnrAI Pro' };
mockReplace.mockResolvedValue({ resource: updated });
await create(baseProduct);
const result = await update('lysnrai', { displayName: 'LysnrAI Pro' });
expect(result).toEqual(updated);
expect(result).not.toBeNull();
expect(result!.displayName).toBe('LysnrAI Pro');
expect(result!.id).toBe('lysnrai');
});
it('returns null when not found', async () => {
mockRead.mockResolvedValue({ resource: undefined });
const result = await update('nonexistent', { displayName: 'Test' });
expect(result).toBeNull();
});
it('returns null on error', async () => {
mockRead.mockRejectedValue(new Error('Not found'));
const result = await update('nonexistent', { displayName: 'Test' });
expect(result).toBeNull();
});

View File

@ -1,36 +1,31 @@
/**
* Products repository Cosmos DB CRUD.
* Products repository cloud-agnostic via @bytelyst/datastore.
* Products are the central registry; productId is the partition key.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { ProductDoc } from './types.js';
function container() {
return getContainer('products');
function collection() {
return getCollection<ProductDoc>('products', '/productId');
}
export async function getAll(): Promise<ProductDoc[]> {
const { resources } = await container()
.items.query<ProductDoc>({
query: 'SELECT * FROM c ORDER BY c.displayName ASC',
})
.fetchAll();
return resources;
return collection().findMany({
sort: { displayName: 1 },
});
}
export async function getById(productId: string): Promise<ProductDoc | null> {
try {
const { resource } = await container().item(productId, productId).read<ProductDoc>();
return resource ?? null;
return await collection().findById(productId, productId);
} catch {
return null;
}
}
export async function create(doc: ProductDoc): Promise<ProductDoc> {
const { resource } = await container().items.create(doc);
return resource as ProductDoc;
return collection().create(doc);
}
export async function update(
@ -38,11 +33,10 @@ export async function update(
updates: Partial<ProductDoc>
): Promise<ProductDoc | null> {
try {
const { resource: existing } = await container().item(productId, productId).read<ProductDoc>();
if (!existing) return null;
const merged = { ...existing, ...updates, updatedAt: new Date().toISOString() };
const { resource } = await container().item(productId, productId).replace(merged);
return resource as ProductDoc;
return await collection().update(productId, productId, {
...updates,
updatedAt: new Date().toISOString(),
});
} catch {
return null;
}

View File

@ -1,19 +1,10 @@
/**
* Repository tests for settings module mocked Cosmos DB.
* Repository tests for settings module in-memory datastore.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockRead = vi.fn();
const mockUpsert = vi.fn();
vi.mock('../../lib/cosmos.js', () => ({
getContainer: vi.fn(() => ({
items: { upsert: mockUpsert },
item: () => ({ read: mockRead }),
})),
}));
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import { MemoryDatastoreProvider } from '@bytelyst/datastore';
import { setProvider, _resetDatastoreProvider } from '../../lib/datastore.js';
import { getSettingsId, getByUserId, upsert } from './repository.js';
import type { UserSettingsDoc } from './types.js';
@ -29,7 +20,11 @@ const baseSettings: UserSettingsDoc = {
describe('settings repository', () => {
beforeEach(() => {
vi.clearAllMocks();
setProvider(new MemoryDatastoreProvider());
});
afterAll(() => {
_resetDatastoreProvider();
});
describe('getSettingsId', () => {
@ -44,19 +39,12 @@ describe('settings repository', () => {
describe('getByUserId', () => {
it('returns settings when found', async () => {
mockRead.mockResolvedValue({ resource: baseSettings });
await upsert(baseSettings);
const result = await getByUserId('user_1', 'lysnrai');
expect(result).toEqual(baseSettings);
});
it('returns null when not found', async () => {
mockRead.mockRejectedValue(new Error('Not found'));
const result = await getByUserId('user_1', 'lysnrai');
expect(result).toBeNull();
});
it('returns null when resource is undefined', async () => {
mockRead.mockResolvedValue({ resource: undefined });
const result = await getByUserId('user_1', 'lysnrai');
expect(result).toBeNull();
});
@ -64,7 +52,6 @@ describe('settings repository', () => {
describe('upsert', () => {
it('upserts and returns settings', async () => {
mockUpsert.mockResolvedValue({ resource: baseSettings });
const result = await upsert(baseSettings);
expect(result).toEqual(baseSettings);
});

View File

@ -1,12 +1,12 @@
/**
* User settings repository Cosmos DB CRUD for settings + device overrides.
* User settings repository cloud-agnostic via @bytelyst/datastore.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { UserSettingsDoc } from './types.js';
function container() {
return getContainer('settings');
function collection() {
return getCollection<UserSettingsDoc>('settings', '/userId');
}
export function getSettingsId(productId: string, userId: string): string {
@ -19,14 +19,12 @@ export async function getByUserId(
): Promise<UserSettingsDoc | null> {
const id = getSettingsId(productId, userId);
try {
const { resource } = await container().item(id, userId).read<UserSettingsDoc>();
return resource ?? null;
return await collection().findById(id, userId);
} catch {
return null;
}
}
export async function upsert(doc: UserSettingsDoc): Promise<UserSettingsDoc> {
const { resource } = await container().items.upsert<UserSettingsDoc>(doc);
return resource!;
return collection().upsert(doc);
}

View File

@ -1,65 +1,53 @@
/**
* Theme repository Cosmos DB.
* Theme repository cloud-agnostic via @bytelyst/datastore.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { ThemeDoc } from './types.js';
function container() {
return getContainer('themes');
function collection() {
return getCollection<ThemeDoc>('themes', '/id');
}
export async function getAll(productId: string): Promise<ThemeDoc[]> {
const { resources } = await container()
.items.query<ThemeDoc>({
query:
"SELECT * FROM c WHERE c.productId = @productId AND c.type = 'theme' ORDER BY c.created_at DESC",
parameters: [{ name: '@productId', value: productId }],
})
.fetchAll();
return resources;
return collection().findMany({
filter: { productId, type: 'theme' },
sort: { created_at: -1 },
});
}
export async function getById(id: string): Promise<ThemeDoc | null> {
try {
const { resource } = await container().item(id, id).read<ThemeDoc>();
return resource ?? null;
return await collection().findById(id, id);
} catch {
return null;
}
}
export async function getActive(productId: string): Promise<ThemeDoc | null> {
const { resources } = await container()
.items.query<ThemeDoc>({
query:
"SELECT * FROM c WHERE c.productId = @productId AND c.type = 'theme' AND c.is_active = true ORDER BY c.is_default DESC, c.created_at DESC OFFSET 0 LIMIT 1",
parameters: [{ name: '@productId', value: productId }],
})
.fetchAll();
return resources[0] ?? null;
return collection().findOne({
filter: { productId, type: 'theme', is_active: true },
sort: { is_default: -1, created_at: -1 },
limit: 1,
});
}
export async function create(theme: ThemeDoc): Promise<ThemeDoc> {
const { resource } = await container().items.create(theme);
return resource as ThemeDoc;
return collection().create(theme);
}
export async function update(
id: string,
updates: Record<string, unknown>
): Promise<ThemeDoc | null> {
const existing = await getById(id);
if (!existing) return null;
const merged = {
...existing,
...updates,
updated_at: new Date().toISOString(),
};
const { resource } = await container().item(id, id).replace(merged);
return resource as ThemeDoc;
try {
return await collection().update(id, id, {
...updates,
updated_at: new Date().toISOString(),
} as Partial<ThemeDoc>);
} catch {
return null;
}
}
export async function setActive(id: string, productId: string): Promise<boolean> {
@ -67,25 +55,15 @@ export async function setActive(id: string, productId: string): Promise<boolean>
if (!theme) return false;
// Deactivate all others for this product
const { resources: activeThemes } = await container()
.items.query<ThemeDoc>({
query:
"SELECT * FROM c WHERE c.productId = @productId AND c.type = 'theme' AND c.is_active = true AND c.id != @excludeId",
parameters: [
{ name: '@productId', value: productId },
{ name: '@excludeId', value: id },
],
})
.fetchAll();
const activeThemes = await collection().findMany({
filter: { productId, type: 'theme', is_active: true, id: { $ne: id } },
});
for (const item of activeThemes) {
await container()
.item(item.id, item.id)
.replace({
...item,
is_active: false,
updated_at: new Date().toISOString(),
});
await collection().update(item.id, item.id, {
is_active: false,
updated_at: new Date().toISOString(),
} as Partial<ThemeDoc>);
}
// Activate the target theme
@ -95,7 +73,7 @@ export async function setActive(id: string, productId: string): Promise<boolean>
export async function remove(id: string): Promise<boolean> {
try {
await container().item(id, id).delete();
await collection().delete(id, id);
return true;
} catch {
return false;

View File

@ -1,23 +1,10 @@
/**
* Repository tests for votes module mocked Cosmos DB.
* Repository tests for votes module in-memory datastore.
*/
import { describe, it, expect, vi, beforeEach } from 'vitest';
const mockFetchAll = vi.fn();
const mockCreate = vi.fn();
const mockDelete = vi.fn();
vi.mock('../../lib/cosmos.js', () => ({
getContainer: vi.fn(() => ({
items: {
query: () => ({ fetchAll: mockFetchAll }),
create: mockCreate,
},
item: () => ({ delete: mockDelete }),
})),
}));
import { describe, it, expect, beforeEach, afterAll } from 'vitest';
import { MemoryDatastoreProvider } from '@bytelyst/datastore';
import { setProvider, _resetDatastoreProvider } from '../../lib/datastore.js';
import { getByItemAndUser, countByItem, listByItem, create, remove } from './repository.js';
import type { VoteDoc } from './types.js';
@ -31,18 +18,21 @@ const baseVote: VoteDoc = {
describe('votes repository', () => {
beforeEach(() => {
vi.clearAllMocks();
setProvider(new MemoryDatastoreProvider());
});
afterAll(() => {
_resetDatastoreProvider();
});
describe('getByItemAndUser', () => {
it('returns vote when found', async () => {
mockFetchAll.mockResolvedValue({ resources: [baseVote] });
await create(baseVote);
const result = await getByItemAndUser('item_1', 'user_1');
expect(result).toEqual(baseVote);
});
it('returns null when not found', async () => {
mockFetchAll.mockResolvedValue({ resources: [] });
const result = await getByItemAndUser('item_1', 'user_1');
expect(result).toBeNull();
});
@ -50,13 +40,13 @@ describe('votes repository', () => {
describe('countByItem', () => {
it('returns count', async () => {
mockFetchAll.mockResolvedValue({ resources: [5] });
await create(baseVote);
await create({ ...baseVote, id: 'vote_2', userId: 'user_2' });
const result = await countByItem('item_1');
expect(result).toBe(5);
expect(result).toBe(2);
});
it('returns 0 when no votes', async () => {
mockFetchAll.mockResolvedValue({ resources: [] });
const result = await countByItem('item_1');
expect(result).toBe(0);
});
@ -64,13 +54,13 @@ describe('votes repository', () => {
describe('listByItem', () => {
it('returns votes for item', async () => {
mockFetchAll.mockResolvedValue({ resources: [baseVote] });
await create(baseVote);
const result = await listByItem('item_1');
expect(result).toEqual([baseVote]);
expect(result).toHaveLength(1);
expect(result[0]!.id).toBe('vote_1');
});
it('returns empty array when no votes', async () => {
mockFetchAll.mockResolvedValue({ resources: [] });
const result = await listByItem('item_1');
expect(result).toEqual([]);
});
@ -78,7 +68,6 @@ describe('votes repository', () => {
describe('create', () => {
it('creates and returns vote', async () => {
mockCreate.mockResolvedValue({ resource: baseVote });
const result = await create(baseVote);
expect(result).toEqual(baseVote);
});
@ -86,15 +75,14 @@ describe('votes repository', () => {
describe('remove', () => {
it('deletes and returns true', async () => {
mockDelete.mockResolvedValue(undefined);
await create(baseVote);
const result = await remove('vote_1');
expect(result).toBe(true);
});
it('returns false on error', async () => {
mockDelete.mockRejectedValue(new Error('Not found'));
const result = await remove('vote_1');
expect(result).toBe(false);
it('returns true even for nonexistent (idempotent delete)', async () => {
const result = await remove('nonexistent');
expect(result).toBe(true);
});
});
});

View File

@ -1,55 +1,38 @@
/**
* Votes repository Cosmos DB CRUD.
* Votes repository cloud-agnostic via @bytelyst/datastore.
*/
import { getContainer } from '../../lib/cosmos.js';
import { getCollection } from '../../lib/datastore.js';
import type { VoteDoc } from './types.js';
function container() {
return getContainer('tracker_votes');
function collection() {
return getCollection<VoteDoc>('tracker_votes', '/id');
}
export async function getByItemAndUser(itemId: string, userId: string): Promise<VoteDoc | null> {
const { resources } = await container()
.items.query<VoteDoc>({
query: 'SELECT * FROM c WHERE c.itemId = @itemId AND c.userId = @userId',
parameters: [
{ name: '@itemId', value: itemId },
{ name: '@userId', value: userId },
],
})
.fetchAll();
return resources[0] ?? null;
return collection().findOne({
filter: { itemId, userId },
});
}
export async function countByItem(itemId: string): Promise<number> {
const { resources } = await container()
.items.query<number>({
query: 'SELECT VALUE COUNT(1) FROM c WHERE c.itemId = @itemId',
parameters: [{ name: '@itemId', value: itemId }],
})
.fetchAll();
return resources[0] ?? 0;
return collection().count({ itemId });
}
export async function listByItem(itemId: string): Promise<VoteDoc[]> {
const { resources } = await container()
.items.query<VoteDoc>({
query: 'SELECT * FROM c WHERE c.itemId = @itemId ORDER BY c.createdAt DESC',
parameters: [{ name: '@itemId', value: itemId }],
})
.fetchAll();
return resources;
return collection().findMany({
filter: { itemId },
sort: { createdAt: -1 },
});
}
export async function create(doc: VoteDoc): Promise<VoteDoc> {
const { resource } = await container().items.create(doc);
return resource as VoteDoc;
return collection().create(doc);
}
export async function remove(id: string): Promise<boolean> {
try {
await container().item(id, id).delete();
await collection().delete(id, id);
return true;
} catch {
return false;