diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3b050e1..1defccf 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -21,7 +21,7 @@ jobs: timeout-minutes: 10 strategy: matrix: - node: [22, 24] + node: [22, 24, 26] services: postgres: image: postgres:17 @@ -63,3 +63,5 @@ jobs: run: npm install - name: Run linter run: npm run lint + - name: Type check + run: npm run test:types diff --git a/index.d.ts b/index.d.ts new file mode 100644 index 0000000..1838e79 --- /dev/null +++ b/index.d.ts @@ -0,0 +1,40 @@ +declare function createLeaderElector ( + options: createLeaderElector.LeaderElectorOptions +): createLeaderElector.LeaderElector + +declare namespace createLeaderElector { + export interface Pool { + connect (): Promise + query (text: string, ...args: unknown[]): Promise + } + + export interface Logger { + info: (...args: unknown[]) => void + debug: (...args: unknown[]) => void + warn: (...args: unknown[]) => void + error: (...args: unknown[]) => void + } + + export interface NotificationChannel { + channel: string + onNotification: (payload: TPayload) => void | Promise + } + + export interface LeaderElectorOptions { + pool: Pool + lock: number + poll?: number + channels?: NotificationChannel[] + log?: Logger + onLeadershipChange?: ((isLeader: boolean) => void) | null + } + + export interface LeaderElector { + start (): Promise + stop (): Promise + notify (payload: unknown, channelName: string): Promise + isLeader (): boolean + } +} + +export = createLeaderElector diff --git a/package.json b/package.json index c375948..c5a8905 100644 --- a/package.json +++ b/package.json @@ -3,8 +3,10 @@ "version": "0.2.0", "description": "PostgreSQL advisory lock-based leader election with notification channels", "main": "index.js", + "types": "index.d.ts", "scripts": { "test": "node --test test/*.test.js", + "test:types": "tsc -p tsconfig.json", "lint": "standard ." }, "dependencies": { @@ -12,7 +14,8 @@ "pino": "^10.3.1" }, "devDependencies": { - "standard": "^17.1.2" + "standard": "^17.1.2", + "typescript": "^5.9.3" }, "engines": { "node": ">=22.0.0" diff --git a/test/types.test-d.ts b/test/types.test-d.ts new file mode 100644 index 0000000..95ff3de --- /dev/null +++ b/test/types.test-d.ts @@ -0,0 +1,36 @@ +import createLeaderElector = require('../index') + +const fakePool: createLeaderElector.Pool = { + connect: async () => ({}), + query: async () => ({}) +} + +const elector: createLeaderElector.LeaderElector = createLeaderElector({ + pool: fakePool, + lock: 42, + poll: 1000, + channels: [ + { + channel: 'deferred_messages', + onNotification: async (payload: unknown) => { void payload } + } + ], + log: { + info: () => {}, + debug: () => {}, + warn: () => {}, + error: () => {} + }, + onLeadershipChange: (isLeader: boolean) => { void isLeader } +}) + +const _minimal: createLeaderElector.LeaderElector = createLeaderElector({ + pool: fakePool, + lock: 1 +}) + +const _start: Promise = elector.start() +const _stop: Promise = elector.stop() +const _notify: Promise = elector.notify({ id: 1 }, 'channel') +const _isLeader: boolean = elector.isLeader() +void _minimal; void _start; void _stop; void _notify; void _isLeader diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..164cfa1 --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,11 @@ +{ + "compilerOptions": { + "module": "nodenext", + "moduleResolution": "nodenext", + "target": "esnext", + "strict": true, + "noEmit": true, + "skipLibCheck": true + }, + "include": ["index.d.ts", "test/types.test-d.ts"] +}