feat(cosmos): add @bytelyst/cosmos package
- Singleton CosmosClient with env var config (COSMOS_ENDPOINT, COSMOS_KEY, COSMOS_DATABASE) - Simple getContainer() for services - Container registry with registerContainers(), getRegisteredContainer(), initializeAllContainers() for dashboards - ContainerConfig type with partitionKeyPath and optional defaultTtl - _resetClient() and _resetRegistry() for test isolation - Peer dep: @azure/cosmos >=4.0.0
This commit is contained in:
parent
9c0ab36171
commit
2e9dcf49a8
21
packages/cosmos/package.json
Normal file
21
packages/cosmos/package.json
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
{
|
||||||
|
"name": "@bytelyst/cosmos",
|
||||||
|
"version": "0.1.0",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"files": ["dist"],
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc",
|
||||||
|
"test": "vitest run"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"@azure/cosmos": ">=4.0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
54
packages/cosmos/src/client.ts
Normal file
54
packages/cosmos/src/client.ts
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
/**
|
||||||
|
* Azure Cosmos DB client singleton.
|
||||||
|
*
|
||||||
|
* Reads COSMOS_ENDPOINT, COSMOS_KEY, and COSMOS_DATABASE from process.env.
|
||||||
|
* Provides getCosmosClient(), getDatabase(), and getContainer() for simple usage.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { CosmosClient, Database, Container } from "@azure/cosmos";
|
||||||
|
|
||||||
|
let _client: CosmosClient | null = null;
|
||||||
|
let _database: Database | null = null;
|
||||||
|
|
||||||
|
function getEnvOrThrow(name: string): string {
|
||||||
|
const value = process.env[name];
|
||||||
|
if (!value) {
|
||||||
|
throw new Error(`Environment variable ${name} is required`);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getCosmosClient(): CosmosClient {
|
||||||
|
if (!_client) {
|
||||||
|
_client = new CosmosClient({
|
||||||
|
endpoint: getEnvOrThrow("COSMOS_ENDPOINT"),
|
||||||
|
key: getEnvOrThrow("COSMOS_KEY"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return _client;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function getDatabase(): Database {
|
||||||
|
if (!_database) {
|
||||||
|
const dbId = process.env.COSMOS_DATABASE || "lysnrai";
|
||||||
|
_database = getCosmosClient().database(dbId);
|
||||||
|
}
|
||||||
|
return _database;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a container by name. Uses the singleton database.
|
||||||
|
* For simple services that don't need a container registry.
|
||||||
|
*/
|
||||||
|
export function getContainer(name: string): Container {
|
||||||
|
return getDatabase().container(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the singleton (useful for testing).
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function _resetClient(): void {
|
||||||
|
_client = null;
|
||||||
|
_database = null;
|
||||||
|
}
|
||||||
71
packages/cosmos/src/containers.ts
Normal file
71
packages/cosmos/src/containers.ts
Normal file
@ -0,0 +1,71 @@
|
|||||||
|
/**
|
||||||
|
* Container registry for dashboards that need partition key validation
|
||||||
|
* and createIfNotExists support.
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { Container, PartitionKeyDefinition } from "@azure/cosmos";
|
||||||
|
import { getDatabase, getCosmosClient } from "./client.js";
|
||||||
|
import type { ContainerConfig } from "./types.js";
|
||||||
|
|
||||||
|
const _registry: Map<string, ContainerConfig> = new Map();
|
||||||
|
const _containerCache: Map<string, Container> = new Map();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register containers with their partition key configuration.
|
||||||
|
* Call once at app startup before any getRegisteredContainer() calls.
|
||||||
|
*/
|
||||||
|
export function registerContainers(
|
||||||
|
definitions: Record<string, ContainerConfig>,
|
||||||
|
): void {
|
||||||
|
for (const [name, config] of Object.entries(definitions)) {
|
||||||
|
_registry.set(name, config);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a container that was previously registered.
|
||||||
|
* Throws if the container name is unknown.
|
||||||
|
*/
|
||||||
|
export function getRegisteredContainer(name: string): Container {
|
||||||
|
if (!_registry.has(name)) {
|
||||||
|
throw new Error(
|
||||||
|
`Unknown container '${name}'. Valid: ${[..._registry.keys()].join(", ")}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
let container = _containerCache.get(name);
|
||||||
|
if (!container) {
|
||||||
|
container = getDatabase().container(name);
|
||||||
|
_containerCache.set(name, container);
|
||||||
|
}
|
||||||
|
return container;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create all registered containers if they don't exist.
|
||||||
|
* Call from a seed script or on first deploy.
|
||||||
|
*/
|
||||||
|
export async function initializeAllContainers(): Promise<void> {
|
||||||
|
const client = getCosmosClient();
|
||||||
|
const dbId = process.env.COSMOS_DATABASE || "lysnrai";
|
||||||
|
const { database } = await client.databases.createIfNotExists({ id: dbId });
|
||||||
|
|
||||||
|
for (const [name, config] of _registry.entries()) {
|
||||||
|
await database.containers.createIfNotExists({
|
||||||
|
id: name,
|
||||||
|
partitionKey: {
|
||||||
|
paths: [config.partitionKeyPath],
|
||||||
|
} as PartitionKeyDefinition,
|
||||||
|
...(config.defaultTtl != null && { defaultTtl: config.defaultTtl }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reset the registry (useful for testing).
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
export function _resetRegistry(): void {
|
||||||
|
_registry.clear();
|
||||||
|
_containerCache.clear();
|
||||||
|
}
|
||||||
8
packages/cosmos/src/index.ts
Normal file
8
packages/cosmos/src/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export { getCosmosClient, getDatabase, getContainer, _resetClient } from "./client.js";
|
||||||
|
export {
|
||||||
|
registerContainers,
|
||||||
|
getRegisteredContainer,
|
||||||
|
initializeAllContainers,
|
||||||
|
_resetRegistry,
|
||||||
|
} from "./containers.js";
|
||||||
|
export type { ContainerConfig } from "./types.js";
|
||||||
4
packages/cosmos/src/types.ts
Normal file
4
packages/cosmos/src/types.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export interface ContainerConfig {
|
||||||
|
partitionKeyPath: string;
|
||||||
|
defaultTtl?: number | null;
|
||||||
|
}
|
||||||
9
packages/cosmos/tsconfig.json
Normal file
9
packages/cosmos/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsconfig.base.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"outDir": "dist",
|
||||||
|
"rootDir": "src"
|
||||||
|
},
|
||||||
|
"include": ["src"],
|
||||||
|
"exclude": ["src/**/*.test.ts"]
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user