-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add Worker class & Limiter (#1)
- Loading branch information
Showing
10 changed files
with
482 additions
and
22 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
export type Limiter = { | ||
inc(): void | ||
timeout(): number | ||
get(max: number): number | ||
} | ||
|
||
export class IntervalLimiter implements Limiter { | ||
constructor( | ||
protected readonly interval: number, | ||
) {} | ||
|
||
timeout() { | ||
return this.interval | ||
} | ||
|
||
inc() { | ||
// NOOP | ||
} | ||
|
||
get(max: number) { | ||
return max | ||
} | ||
} | ||
|
||
export class FixedWindowLimiter implements Limiter { | ||
protected count = 0 | ||
protected timestamp = 0 | ||
|
||
constructor( | ||
protected readonly max: number, | ||
protected readonly duration: number, | ||
protected readonly interval: number, | ||
) {} | ||
|
||
timeout() { | ||
const now = Date.now() | ||
const timestamp = now - (now % this.duration) | ||
|
||
if (timestamp !== this.timestamp) { | ||
this.count = 0 | ||
this.timestamp = timestamp | ||
} | ||
|
||
if (this.count >= this.max) { | ||
this.count = 0 | ||
this.timestamp = timestamp + this.duration | ||
return Math.max(this.timestamp - now, this.interval) | ||
} | ||
return this.interval | ||
} | ||
|
||
inc() { | ||
this.count += 1 | ||
} | ||
|
||
get(max: number) { | ||
return Math.min(max, this.max - this.count) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
export const sleep = (ms: number) => new Promise<void>((resolve) => { | ||
setTimeout(resolve, ms) | ||
}) | ||
|
||
export const createSubject = (name: string) => { | ||
return `${name}.*` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,97 @@ | ||
import { equal } from 'assert/strict' | ||
import { describe, it, before, after, afterEach, beforeEach } from 'node:test' | ||
|
||
import { connect } from '@nats-io/transport-node' | ||
import { jetstream } from '@nats-io/jetstream' | ||
import { NatsConnection } from '@nats-io/nats-core' | ||
import type { JetStreamClient, JetStreamManager } from '@nats-io/jetstream' | ||
|
||
import { Queue, Worker } from '../src' | ||
|
||
describe('Queue.add()', () => { | ||
let connection: NatsConnection | ||
let client: JetStreamClient | ||
let manager: JetStreamManager | ||
let queue: Queue | ||
|
||
const QUEUE_NAME_1 = 'queue1' | ||
const JOB_NAME_1 = 'job1' | ||
|
||
before(async () => { | ||
connection = await connect({ | ||
servers: '127.0.0.1:4222', | ||
}) | ||
client = jetstream(connection) | ||
manager = await client.jetstreamManager() | ||
}) | ||
|
||
beforeEach(async () => { | ||
queue = new Queue({ | ||
client, | ||
name: QUEUE_NAME_1, | ||
}) | ||
|
||
await queue.setup() | ||
}) | ||
|
||
afterEach(async () => { | ||
await manager.streams.delete(QUEUE_NAME_1) | ||
}) | ||
|
||
after(async () => { | ||
await connection.close() | ||
}) | ||
|
||
it('OK', async () => { | ||
const ack = await queue.add(JOB_NAME_1) | ||
|
||
equal(ack.duplicate, false) | ||
equal(ack.seq, 1) | ||
|
||
const stream = await client.streams.get(QUEUE_NAME_1) | ||
|
||
const { state: { messages } } = await stream.info() | ||
equal(messages, 1) | ||
}) | ||
|
||
it('OK with payload', async () => { | ||
const ack = await queue.add(JOB_NAME_1, { x: 1 }) | ||
|
||
equal(ack.duplicate, false) | ||
equal(ack.seq, 1) | ||
}) | ||
|
||
it('OK with different IDs', async () => { | ||
const ack1 = await queue.add(JOB_NAME_1, 'data', { | ||
id: 'id1', | ||
}) | ||
|
||
equal(ack1.duplicate, false) | ||
equal(ack1.seq, 1) | ||
|
||
const ack2 = await queue.add(JOB_NAME_1, 'data', { | ||
id: 'id2', | ||
}) | ||
|
||
equal(ack2.duplicate, false) | ||
equal(ack2.seq, 2) | ||
}) | ||
|
||
it('OK duplicated IDs', async () => { | ||
const id = '12345' | ||
|
||
const ack1 = await queue.add(JOB_NAME_1, 'data', { | ||
id, | ||
}) | ||
|
||
equal(ack1.duplicate, false) | ||
equal(ack1.seq, 1) | ||
|
||
const ack2 = await queue.add(JOB_NAME_1, 'data', { | ||
id, | ||
}) | ||
|
||
equal(ack2.duplicate, true) | ||
equal(ack2.seq, 1) | ||
}) | ||
}) |
Oops, something went wrong.