@felipe-lib/bulkhead 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +70 -0
- package/README.pt-BR.md +70 -0
- package/dist/index.d.ts +11 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +58 -0
- package/dist/index.js.map +1 -0
- package/dist/types.d.ts +6 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/package.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# bulkhead
|
|
2
|
+
|
|
3
|
+
**Student project** — Bulkhead pattern implementation for TypeScript. Limits concurrent executions and queues excess requests to prevent resource exhaustion.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install bulkhead
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Usage
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Bulkhead } from "bulkhead";
|
|
15
|
+
|
|
16
|
+
// Per-route instances for proper isolation
|
|
17
|
+
const ordersApi = new Bulkhead<Order>(5, 10);
|
|
18
|
+
const paymentsApi = new Bulkhead<Payment>(3, 5);
|
|
19
|
+
|
|
20
|
+
// Example 1: basic usage
|
|
21
|
+
async function getOrder(id: string): Promise<Order> {
|
|
22
|
+
return ordersApi.execute(() => fetch(`/orders/${id}`).then(r => r.json()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Example 2: with error handling
|
|
26
|
+
async function processPayment(data: PaymentData): Promise<Payment> {
|
|
27
|
+
try {
|
|
28
|
+
return await paymentsApi.execute(() =>
|
|
29
|
+
fetch("/payments", {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: JSON.stringify(data),
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
}).then(r => r.json())
|
|
34
|
+
);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (e instanceof Error && e.message === "Queue is full") {
|
|
37
|
+
console.error("Payment service busy — try again later");
|
|
38
|
+
// Fallback: return cached result or throw a friendly error
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Example 3: concurrent requests — bulkhead limits automatically
|
|
45
|
+
async function fetchAll(ids: string[]): Promise<Order[]> {
|
|
46
|
+
const results = await Promise.allSettled(
|
|
47
|
+
ids.map(id => getOrder(id))
|
|
48
|
+
);
|
|
49
|
+
return results
|
|
50
|
+
.filter((r): r is PromiseFulfilledResult<Order> => r.status === "fulfilled")
|
|
51
|
+
.map(r => r.value);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### `new Bulkhead<T>(maxLimit, maxLimitQueue)`
|
|
58
|
+
|
|
59
|
+
Creates a bulkhead instance.
|
|
60
|
+
|
|
61
|
+
- `maxLimit` — max concurrent executions (must be > 0)
|
|
62
|
+
- `maxLimitQueue` — max queue size (must be >= 0)
|
|
63
|
+
|
|
64
|
+
### `bulkhead.execute(fn): Promise<T>`
|
|
65
|
+
|
|
66
|
+
Executes `fn` immediately if under `maxLimit`. Otherwise queues the request and runs it as soon as a slot frees up. Throws `"Queue is full"` when both limits are reached.
|
|
67
|
+
|
|
68
|
+
## License
|
|
69
|
+
|
|
70
|
+
ISC
|
package/README.pt-BR.md
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
# bulkhead
|
|
2
|
+
|
|
3
|
+
**Projeto estudantil** — Implementação do padrão bulkhead para TypeScript. Limita execuções concorrentes e enfileira requisições excedentes para evitar esgotamento de recursos.
|
|
4
|
+
|
|
5
|
+
## Instalação
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install bulkhead
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Uso
|
|
12
|
+
|
|
13
|
+
```typescript
|
|
14
|
+
import { Bulkhead } from "bulkhead";
|
|
15
|
+
|
|
16
|
+
// Instâncias por rota para isolamento adequado
|
|
17
|
+
const ordersApi = new Bulkhead<Order>(5, 10);
|
|
18
|
+
const paymentsApi = new Bulkhead<Payment>(3, 5);
|
|
19
|
+
|
|
20
|
+
// Exemplo 1: uso básico
|
|
21
|
+
async function getOrder(id: string): Promise<Order> {
|
|
22
|
+
return ordersApi.execute(() => fetch(`/orders/${id}`).then(r => r.json()));
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
// Exemplo 2: com tratamento de erro
|
|
26
|
+
async function processPayment(data: PaymentData): Promise<Payment> {
|
|
27
|
+
try {
|
|
28
|
+
return await paymentsApi.execute(() =>
|
|
29
|
+
fetch("/payments", {
|
|
30
|
+
method: "POST",
|
|
31
|
+
body: JSON.stringify(data),
|
|
32
|
+
headers: { "Content-Type": "application/json" },
|
|
33
|
+
}).then(r => r.json())
|
|
34
|
+
);
|
|
35
|
+
} catch (e) {
|
|
36
|
+
if (e instanceof Error && e.message === "Queue is full") {
|
|
37
|
+
console.error("Serviço de pagamento ocupado — tente novamente mais tarde");
|
|
38
|
+
// Fallback: usar resultado em cache ou lançar erro amigável
|
|
39
|
+
}
|
|
40
|
+
throw e;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Exemplo 3: requisições concorrentes — bulkhead limita automaticamente
|
|
45
|
+
async function fetchAll(ids: string[]): Promise<Order[]> {
|
|
46
|
+
const results = await Promise.allSettled(
|
|
47
|
+
ids.map(id => getOrder(id))
|
|
48
|
+
);
|
|
49
|
+
return results
|
|
50
|
+
.filter((r): r is PromiseFulfilledResult<Order> => r.status === "fulfilled")
|
|
51
|
+
.map(r => r.value);
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
## API
|
|
56
|
+
|
|
57
|
+
### `new Bulkhead<T>(maxLimit, maxLimitQueue)`
|
|
58
|
+
|
|
59
|
+
Cria uma instância do bulkhead.
|
|
60
|
+
|
|
61
|
+
- `maxLimit` — máximo de execuções simultâneas (deve ser > 0)
|
|
62
|
+
- `maxLimitQueue` — tamanho máximo da fila (deve ser >= 0)
|
|
63
|
+
|
|
64
|
+
### `bulkhead.execute(fn): Promise<T>`
|
|
65
|
+
|
|
66
|
+
Executa `fn` imediatamente se abaixo de `maxLimit`. Caso contrário, enfileira a requisição e executa assim que uma vaga for liberada. Lança `"Queue is full"` quando ambos os limites são atingidos.
|
|
67
|
+
|
|
68
|
+
## Licença
|
|
69
|
+
|
|
70
|
+
ISC
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class Bulkhead<T = unknown> {
|
|
2
|
+
private currentProcess;
|
|
3
|
+
private maxLimit;
|
|
4
|
+
private maxLimitQueue;
|
|
5
|
+
private queue;
|
|
6
|
+
constructor(maxLimit: number, maxLimitQueue: number);
|
|
7
|
+
execute(fetchPromise: () => Promise<T>): Promise<void | T>;
|
|
8
|
+
private resolveFetchers;
|
|
9
|
+
private processQueue;
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,qBAAa,QAAQ,CAAC,CAAC,GAAG,OAAO;IAC7B,OAAO,CAAC,cAAc,CAAa;IACnC,OAAO,CAAC,QAAQ,CAAc;IAC9B,OAAO,CAAC,aAAa,CAAc;IACnC,OAAO,CAAC,KAAK,CAAsB;IAEnC,YAAY,QAAQ,EAAE,MAAM,EAAE,aAAa,EAAE,MAAM,EAGlD;IAEY,OAAO,CAAC,YAAY,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,qBAiBlD;YAEa,eAAe;YAgBf,YAAY;CAY7B"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
export class Bulkhead {
|
|
2
|
+
currentProcess = 0;
|
|
3
|
+
maxLimit = 10;
|
|
4
|
+
maxLimitQueue = 10;
|
|
5
|
+
queue = [];
|
|
6
|
+
constructor(maxLimit, maxLimitQueue) {
|
|
7
|
+
this.maxLimit = maxLimit;
|
|
8
|
+
this.maxLimitQueue = maxLimitQueue;
|
|
9
|
+
}
|
|
10
|
+
async execute(fetchPromise) {
|
|
11
|
+
if (this.currentProcess < this.maxLimit) {
|
|
12
|
+
return await this.resolveFetchers({ fnFetch: fetchPromise });
|
|
13
|
+
}
|
|
14
|
+
else if (this.currentProcess >= this.maxLimit &&
|
|
15
|
+
this.queue.length < this.maxLimitQueue) {
|
|
16
|
+
return new Promise((resolve, reject) => {
|
|
17
|
+
this.queue.push({
|
|
18
|
+
fnFetch: fetchPromise,
|
|
19
|
+
fnReject: reject,
|
|
20
|
+
fnResolve: resolve,
|
|
21
|
+
});
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
throw new Error("Queue is full");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
async resolveFetchers(item) {
|
|
29
|
+
this.currentProcess++;
|
|
30
|
+
try {
|
|
31
|
+
const data = await item.fnFetch();
|
|
32
|
+
if (item?.fnResolve)
|
|
33
|
+
item.fnResolve(data);
|
|
34
|
+
return data;
|
|
35
|
+
}
|
|
36
|
+
catch (e) {
|
|
37
|
+
if (item?.fnReject)
|
|
38
|
+
return item.fnReject(e);
|
|
39
|
+
throw e;
|
|
40
|
+
}
|
|
41
|
+
finally {
|
|
42
|
+
this.currentProcess--;
|
|
43
|
+
await this.processQueue();
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
async processQueue() {
|
|
47
|
+
if (this.queue.length > 0 && this.currentProcess < this.maxLimit) {
|
|
48
|
+
const item = this.queue.shift();
|
|
49
|
+
if (item)
|
|
50
|
+
return await this.resolveFetchers({
|
|
51
|
+
fnFetch: item.fnFetch,
|
|
52
|
+
fnReject: item.fnReject,
|
|
53
|
+
fnResolve: item.fnResolve,
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,QAAQ;IACT,cAAc,GAAW,CAAC,CAAC;IAC3B,QAAQ,GAAW,EAAE,CAAC;IACtB,aAAa,GAAW,EAAE,CAAC;IAC3B,KAAK,GAAmB,EAAE,CAAC;IAEnC,YAAY,QAAgB,EAAE,aAAqB;QAC/C,IAAI,CAAC,QAAQ,GAAG,QAAQ,CAAC;QACzB,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;IACvC,CAAC;IAEM,KAAK,CAAC,OAAO,CAAC,YAA8B;QAC/C,IAAI,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YACtC,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC,EAAE,OAAO,EAAE,YAAY,EAAE,CAAC,CAAC;QACjE,CAAC;aAAM,IACH,IAAI,CAAC,cAAc,IAAI,IAAI,CAAC,QAAQ;YACpC,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC,aAAa,EACxC,CAAC;YACC,OAAO,IAAI,OAAO,CAAI,CAAC,OAAO,EAAE,MAAM,EAAE,EAAE;gBACtC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC;oBACZ,OAAO,EAAE,YAAY;oBACrB,QAAQ,EAAE,MAAM;oBAChB,SAAS,EAAE,OAAO;iBACrB,CAAC,CAAC;YACP,CAAC,CAAC,CAAC;QACP,CAAC;aAAM,CAAC;YACJ,MAAM,IAAI,KAAK,CAAC,eAAe,CAAC,CAAC;QACrC,CAAC;IACL,CAAC;IAEO,KAAK,CAAC,eAAe,CAAC,IAAkB;QAC5C,IAAI,CAAC,cAAc,EAAE,CAAC;QAEtB,IAAI,CAAC;YACD,MAAM,IAAI,GAAG,MAAM,IAAI,CAAC,OAAO,EAAE,CAAC;YAClC,IAAI,IAAI,EAAE,SAAS;gBAAE,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,CAAC;YAE1C,OAAO,IAAI,CAAC;QAChB,CAAC;QAAC,OAAO,CAAC,EAAE,CAAC;YACT,IAAI,IAAI,EAAE,QAAQ;gBAAE,OAAO,IAAI,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;YAC5C,MAAM,CAAC,CAAC;QACZ,CAAC;gBAAS,CAAC;YACP,IAAI,CAAC,cAAc,EAAE,CAAC;YACtB,MAAM,IAAI,CAAC,YAAY,EAAE,CAAC;QAC9B,CAAC;IACL,CAAC;IACO,KAAK,CAAC,YAAY;QACtB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,IAAI,IAAI,CAAC,cAAc,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC/D,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,EAAE,CAAC;YAEhC,IAAI,IAAI;gBACJ,OAAO,MAAM,IAAI,CAAC,eAAe,CAAC;oBAC9B,OAAO,EAAE,IAAI,CAAC,OAAO;oBACrB,QAAQ,EAAE,IAAI,CAAC,QAAQ;oBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;iBAC5B,CAAC,CAAC;QACX,CAAC;IACL,CAAC;CACJ"}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,SAAS,CAAC,CAAC,GAAG,OAAO;IAClC,OAAO,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,CAAC;IAC1B,QAAQ,CAAC,EAAE,CAAC,MAAM,CAAC,EAAE,GAAG,KAAK,IAAI,CAAC;IAClC,SAAS,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,GAAG,WAAW,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC;CACnD"}
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":""}
|
package/package.json
ADDED
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@felipe-lib/bulkhead",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Bulkhead pattern implementation for TypeScript",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
}
|
|
13
|
+
},
|
|
14
|
+
"files": [
|
|
15
|
+
"dist"
|
|
16
|
+
],
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc",
|
|
19
|
+
"test": "echo \"Error: no test specified\" && exit 1"
|
|
20
|
+
},
|
|
21
|
+
"keywords": [
|
|
22
|
+
"bulkhead",
|
|
23
|
+
"resilience",
|
|
24
|
+
"concurrency",
|
|
25
|
+
"typescript"
|
|
26
|
+
],
|
|
27
|
+
"author": "",
|
|
28
|
+
"license": "ISC",
|
|
29
|
+
"packageManager": "npm@11.16.0",
|
|
30
|
+
"devDependencies": {
|
|
31
|
+
"typescript": "^7.0.2"
|
|
32
|
+
}
|
|
33
|
+
}
|