@blokjs/trigger-pubsub 0.6.18 → 0.6.20

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.
@@ -1,88 +0,0 @@
1
- /**
2
- * v0.7 PR 6 — pub/sub adapter factory.
3
- *
4
- * Resolves a `provider` string to a concrete `PubSubAdapter` instance.
5
- * Used by `PubSubTrigger` (per-workflow provider dispatch) and by the
6
- * `@blokjs/pubsub-publish` helper.
7
- *
8
- * Provider resolution order:
9
- * 1. Explicit `provider` field on the workflow.
10
- * 2. `BLOK_PUBSUB_ADAPTER` env var.
11
- * 3. `"nats"` fallback (cheapest infra; matches the v0.7 plan's
12
- * "default for pub/sub" recommendation).
13
- *
14
- * Each adapter lazy-imports its broker SDK on first use; workflows
15
- * that don't use a given provider don't pay the install cost.
16
- */
17
-
18
- import type { PubSubProvider } from "@blokjs/helper";
19
- import type { PubSubAdapter } from "../PubSubTrigger";
20
- import { AWSSNSAdapter } from "./AWSSNSAdapter";
21
- import { AzureServiceBusAdapter } from "./AzureServiceBusAdapter";
22
- import { GCPPubSubAdapter } from "./GCPPubSubAdapter";
23
- import { KafkaPubSubAdapter } from "./KafkaPubSubAdapter";
24
- import { NATSPubSubAdapter } from "./NATSPubSubAdapter";
25
- import { RedisStreamsPubSubAdapter } from "./RedisStreamsPubSubAdapter";
26
-
27
- export function resolveProvider(provider?: PubSubProvider): PubSubProvider {
28
- if (provider) return provider;
29
- const envValue = process.env.BLOK_PUBSUB_ADAPTER;
30
- if (envValue && isPubSubProvider(envValue)) return envValue;
31
- return "nats";
32
- }
33
-
34
- function isPubSubProvider(value: string): value is PubSubProvider {
35
- return (
36
- value === "nats" ||
37
- value === "redis-streams" ||
38
- value === "kafka" ||
39
- value === "gcp" ||
40
- value === "aws" ||
41
- value === "azure"
42
- );
43
- }
44
-
45
- export function createPubSubAdapter(provider: PubSubProvider): PubSubAdapter {
46
- switch (provider) {
47
- case "nats":
48
- return new NATSPubSubAdapter();
49
- case "redis-streams":
50
- return new RedisStreamsPubSubAdapter();
51
- case "kafka":
52
- return new KafkaPubSubAdapter();
53
- case "gcp":
54
- return new GCPPubSubAdapter();
55
- case "aws":
56
- return new AWSSNSAdapter();
57
- case "azure":
58
- return new AzureServiceBusAdapter();
59
- default: {
60
- const exhaustive: never = provider;
61
- throw new Error(`[blok][pubsub] unknown provider "${exhaustive as string}". Check PubSubProviderSchema.`);
62
- }
63
- }
64
- }
65
-
66
- /**
67
- * Process-singleton adapter pool — one instance per provider. The
68
- * trigger calls `getOrCreateAdapter("nats")` once per workflow, and
69
- * subsequent workflows on the same provider share the broker
70
- * connection.
71
- */
72
- const pool: Map<PubSubProvider, PubSubAdapter> = new Map();
73
-
74
- export function getOrCreateAdapter(provider: PubSubProvider): PubSubAdapter {
75
- let adapter = pool.get(provider);
76
- if (!adapter) {
77
- adapter = createPubSubAdapter(provider);
78
- pool.set(provider, adapter);
79
- }
80
- return adapter;
81
- }
82
-
83
- export function _resetAdapterPoolForTests(): void {
84
- for (const adapter of pool.values()) {
85
- void adapter.disconnect?.().catch(() => {});
86
- }
87
- pool.clear();
88
- }
@@ -1,108 +0,0 @@
1
- /**
2
- * Smoke tests for the v0.7 PR 6 pub/sub adapters (NATS, Redis
3
- * Streams, Kafka) plus the v0.7 publish() backfill on the existing
4
- * 3 (GCP, AWS, Azure). Boundary-only — constructor, provider name,
5
- * initial state, `disconnect()` before connect.
6
- *
7
- * Live broker round-trips need docker-compose CI (see PR 6 plan,
8
- * deferred to follow-up).
9
- */
10
-
11
- import { describe, expect, it } from "vitest";
12
-
13
- import { AWSSNSAdapter } from "./AWSSNSAdapter";
14
- import { AzureServiceBusAdapter } from "./AzureServiceBusAdapter";
15
- import { GCPPubSubAdapter } from "./GCPPubSubAdapter";
16
- import { KafkaPubSubAdapter } from "./KafkaPubSubAdapter";
17
- import { NATSPubSubAdapter } from "./NATSPubSubAdapter";
18
- import { RedisStreamsPubSubAdapter } from "./RedisStreamsPubSubAdapter";
19
-
20
- describe("NATSPubSubAdapter — v0.7 PR 6", () => {
21
- it("reports provider 'nats'", () => {
22
- expect(new NATSPubSubAdapter().provider).toBe("nats");
23
- });
24
-
25
- it("is not connected before connect()", () => {
26
- expect(new NATSPubSubAdapter().isConnected()).toBe(false);
27
- });
28
-
29
- it("disconnect() before connect is a no-op", async () => {
30
- await expect(new NATSPubSubAdapter().disconnect()).resolves.toBeUndefined();
31
- });
32
-
33
- it("reads server list from NATS_SERVERS env var", () => {
34
- process.env.NATS_SERVERS = "nats-a:4222,nats-b:4222";
35
- const adapter = new NATSPubSubAdapter();
36
- expect((adapter as unknown as { config: { servers: string[] } }).config.servers).toEqual([
37
- "nats-a:4222",
38
- "nats-b:4222",
39
- ]);
40
- process.env.NATS_SERVERS = undefined;
41
- });
42
- });
43
-
44
- describe("RedisStreamsPubSubAdapter — v0.7 PR 6", () => {
45
- it("reports provider 'redis-streams'", () => {
46
- expect(new RedisStreamsPubSubAdapter().provider).toBe("redis-streams");
47
- });
48
-
49
- it("is not connected before connect()", () => {
50
- expect(new RedisStreamsPubSubAdapter().isConnected()).toBe(false);
51
- });
52
-
53
- it("disconnect() before connect is a no-op", async () => {
54
- await expect(new RedisStreamsPubSubAdapter().disconnect()).resolves.toBeUndefined();
55
- });
56
-
57
- it("generates a unique consumer name per instance (fan-out isolation)", () => {
58
- const a = new RedisStreamsPubSubAdapter();
59
- const b = new RedisStreamsPubSubAdapter();
60
- expect((a as unknown as { consumerName: string }).consumerName).not.toBe(
61
- (b as unknown as { consumerName: string }).consumerName,
62
- );
63
- });
64
- });
65
-
66
- describe("KafkaPubSubAdapter — v0.7 PR 6", () => {
67
- it("reports provider 'kafka'", () => {
68
- expect(new KafkaPubSubAdapter().provider).toBe("kafka");
69
- });
70
-
71
- it("is not connected before connect()", () => {
72
- expect(new KafkaPubSubAdapter().isConnected()).toBe(false);
73
- });
74
-
75
- it("disconnect() before connect is a no-op", async () => {
76
- await expect(new KafkaPubSubAdapter().disconnect()).resolves.toBeUndefined();
77
- });
78
-
79
- it("honors the explicit broker list override", () => {
80
- const adapter = new KafkaPubSubAdapter({ brokers: ["kafka-prod:9092"] });
81
- expect((adapter as unknown as { config: { brokers: string[] } }).config.brokers).toEqual(["kafka-prod:9092"]);
82
- });
83
- });
84
-
85
- describe("Existing adapters — provider names + publish() surface (v0.7 PR 6 backfill)", () => {
86
- it("GCPPubSubAdapter reports provider 'gcp'", () => {
87
- expect(new GCPPubSubAdapter().provider).toBe("gcp");
88
- });
89
-
90
- it("AWSSNSAdapter reports provider 'aws'", () => {
91
- expect(new AWSSNSAdapter().provider).toBe("aws");
92
- });
93
-
94
- it("AzureServiceBusAdapter reports provider 'azure'", () => {
95
- const adapter = new AzureServiceBusAdapter({
96
- connectionString: "Endpoint=sb://example.servicebus.windows.net/;...",
97
- });
98
- expect(adapter.provider).toBe("azure");
99
- });
100
-
101
- it("all three now expose a publish() method (added in PR 6)", () => {
102
- expect(typeof new GCPPubSubAdapter().publish).toBe("function");
103
- expect(typeof new AWSSNSAdapter().publish).toBe("function");
104
- expect(
105
- typeof new AzureServiceBusAdapter({ connectionString: "Endpoint=sb://x.servicebus.windows.net/;Y" }).publish,
106
- ).toBe("function");
107
- });
108
- });
package/src/index.ts DELETED
@@ -1,67 +0,0 @@
1
- /**
2
- * @blokjs/trigger-pubsub
3
- *
4
- * Pub/Sub-based trigger for Blok workflows. Supports 6 providers:
5
- *
6
- * - **NATS** (Core + JetStream) — cheapest infra; subject wildcards.
7
- * - **Redis Streams** — when Redis is already in stack.
8
- * - **Kafka** — high-throughput streaming.
9
- * - **GCP Pub/Sub** — Google Cloud-locked.
10
- * - **AWS SNS+SQS** — SNS fan-out → SQS queueing.
11
- * - **Azure Service Bus** — Azure Service Bus.
12
- *
13
- * v0.7+ — pick the adapter per workflow via `trigger.pubsub.provider`.
14
- * `BLOK_PUBSUB_ADAPTER` env var sets the default (falls back to NATS).
15
- * Subclasses can still set `protected adapter` directly for back-
16
- * compat with the pre-v0.7 single-adapter pattern.
17
- *
18
- * **Fan-out vs competing-consumer**: omit `consumerGroup` for fan-out
19
- * (every subscriber sees every message); set it for competing-consumer
20
- * (1 of N within group). One field disambiguates the two semantics.
21
- *
22
- * @example v0.7 — NATS subject hierarchy with JSON workflow
23
- * ```json
24
- * {
25
- * "name": "audit-all-order-events",
26
- * "trigger": {
27
- * "pubsub": {
28
- * "provider": "nats",
29
- * "topic": "orders.>",
30
- * "durable": true,
31
- * "startFrom": "earliest"
32
- * }
33
- * },
34
- * "steps": [...]
35
- * }
36
- * ```
37
- */
38
-
39
- // Core exports
40
- export {
41
- PubSubTrigger,
42
- type PubSubAdapter,
43
- type PubSubMessage,
44
- } from "./PubSubTrigger";
45
-
46
- // Adapters
47
- export { AWSSNSAdapter, type AWSSNSConfig } from "./adapters/AWSSNSAdapter";
48
- export { AzureServiceBusAdapter, type AzureServiceBusConfig } from "./adapters/AzureServiceBusAdapter";
49
- export { GCPPubSubAdapter, type GCPPubSubConfig } from "./adapters/GCPPubSubAdapter";
50
- export { KafkaPubSubAdapter, type KafkaPubSubConfig } from "./adapters/KafkaPubSubAdapter";
51
- export { NATSPubSubAdapter, type NATSPubSubConfig } from "./adapters/NATSPubSubAdapter";
52
- export { RedisStreamsPubSubAdapter, type RedisStreamsPubSubConfig } from "./adapters/RedisStreamsPubSubAdapter";
53
-
54
- // v0.7 PR 6 — factory + pool used by PubSubTrigger and exposed for
55
- // helper nodes (`@blokjs/pubsub-publish`).
56
- export {
57
- _resetAdapterPoolForTests,
58
- createPubSubAdapter,
59
- getOrCreateAdapter,
60
- resolveProvider,
61
- } from "./adapters/factory";
62
-
63
- // Re-export types from helper for convenience
64
- export type {
65
- PubSubProvider,
66
- PubSubTriggerOpts,
67
- } from "@blokjs/helper";
@@ -1,8 +0,0 @@
1
- PROJECT_NAME=trigger-pubsub-server
2
- PROJECT_VERSION=0.0.1
3
- PORT=4006
4
- WORKFLOWS_PATH=PROJECT_PATH/workflows
5
- NODES_PATH=PROJECT_PATH/src/nodes
6
- CONSOLE_LOG_ACTIVE=true
7
- APP_NAME=blok-pubsub
8
- DISABLE_TRIGGER_RUN=false # Set to true to disable trigger run and use this project as a module
@@ -1,44 +0,0 @@
1
- {
2
- "name": "blok-pubsub-trigger",
3
- "version": "0.1.0",
4
- "description": "Pub/Sub trigger for Blok workflows",
5
- "type": "module",
6
- "engines": {
7
- "node": ">=18.0.0"
8
- },
9
- "main": "dist/index.js",
10
- "types": "dist/index.d.ts",
11
- "author": "",
12
- "license": "MIT",
13
- "scripts": {
14
- "dev": "bun --watch run src/index.ts",
15
- "start": "bun run dist/index.js",
16
- "build": "rimraf ./dist && tsc",
17
- "test": "vitest run",
18
- "test:dev": "vitest"
19
- },
20
- "devDependencies": {
21
- "@types/node": "^22.15.21",
22
- "@types/uuid": "^11.0.0",
23
- "rimraf": "^6.1.2",
24
- "typescript": "^5.8.3",
25
- "vitest": "^4.0.18"
26
- },
27
- "dependencies": {
28
- "@blokjs/api-call": "^0.6.18",
29
- "@blokjs/helper": "^0.6.18",
30
- "@blokjs/if-else": "^0.6.18",
31
- "@blokjs/runner": "^0.6.18",
32
- "@blokjs/shared": "^0.6.18",
33
- "@blokjs/trigger-pubsub": "^0.6.18",
34
- "@opentelemetry/api": "^1.9.0",
35
- "@opentelemetry/exporter-prometheus": "^0.57.2",
36
- "@opentelemetry/resources": "^1.30.1",
37
- "@opentelemetry/sdk-metrics": "^1.30.1",
38
- "@opentelemetry/sdk-trace-base": "^1.30.1",
39
- "@opentelemetry/semantic-conventions": "^1.39.0",
40
- "uuid": "^11.1.0",
41
- "zod": "^3.24.2"
42
- },
43
- "private": true
44
- }
@@ -1,10 +0,0 @@
1
- import ApiCall from "@blokjs/api-call";
2
- import IfElse from "@blokjs/if-else";
3
- import type { BlokService } from "@blokjs/runner";
4
-
5
- const nodes: Record<string, BlokService<unknown>> = {
6
- "@blokjs/api-call": ApiCall,
7
- "@blokjs/if-else": IfElse,
8
- };
9
-
10
- export default nodes;
@@ -1,8 +0,0 @@
1
- import type Workflows from "./runner/types/Workflows";
2
- import onMessage from "./workflows/messages/on-message";
3
-
4
- const workflows: Workflows = {
5
- "on-message": onMessage,
6
- };
7
-
8
- export default workflows;
@@ -1,41 +0,0 @@
1
- import { DefaultLogger } from "@blokjs/runner";
2
- import { type Span, metrics, trace } from "@opentelemetry/api";
3
- import PubSubServer from "./runner/PubSubServer";
4
-
5
- export default class App {
6
- private pubsubServer: PubSubServer = <PubSubServer>{};
7
- protected trigger_initializer = 0;
8
- protected initializer = 0;
9
- protected tracer = trace.getTracer(
10
- process.env.PROJECT_NAME || "trigger-pubsub-server",
11
- process.env.PROJECT_VERSION || "0.0.1",
12
- );
13
- private logger = new DefaultLogger();
14
- protected app_cold_start = metrics.getMeter("default").createGauge("initialization", {
15
- description: "Application cold start",
16
- });
17
-
18
- constructor() {
19
- this.initializer = performance.now();
20
- this.pubsubServer = new PubSubServer();
21
- }
22
-
23
- async run() {
24
- this.tracer.startActiveSpan("initialization", async (span: Span) => {
25
- await this.pubsubServer.listen();
26
- this.initializer = performance.now() - this.initializer;
27
-
28
- this.logger.log(`Pub/Sub trigger initialized in ${this.initializer.toFixed(2)}ms`);
29
- this.app_cold_start.record(this.initializer, {
30
- pid: process.pid,
31
- env: process.env.NODE_ENV,
32
- app: process.env.APP_NAME,
33
- });
34
- span.end();
35
- });
36
- }
37
- }
38
-
39
- if (process.env.DISABLE_TRIGGER_RUN !== "true") {
40
- new App().run();
41
- }
@@ -1,39 +0,0 @@
1
- import { GCPPubSubAdapter, PubSubTrigger } from "@blokjs/trigger-pubsub";
2
- import nodes from "../Nodes";
3
- import workflows from "../Workflows";
4
-
5
- /**
6
- * PubSubServer - Concrete Pub/Sub trigger implementation
7
- *
8
- * This server extends the abstract PubSubTrigger and provides:
9
- * - A specific adapter (GCP Pub/Sub by default, can be changed to AWS or Azure)
10
- * - Node and workflow registries
11
- *
12
- * To change the provider, replace:
13
- * - GCPPubSubAdapter with AWSSNSAdapter or AzureServiceBusAdapter
14
- * - Update the adapter configuration accordingly
15
- *
16
- * @example AWS SNS/SQS
17
- * ```typescript
18
- * import { AWSSNSAdapter } from "@blokjs/trigger-pubsub";
19
- * protected adapter = new AWSSNSAdapter({
20
- * region: process.env.AWS_REGION || "us-east-1",
21
- * });
22
- * ```
23
- *
24
- * @example Azure Service Bus
25
- * ```typescript
26
- * import { AzureServiceBusAdapter } from "@blokjs/trigger-pubsub";
27
- * protected adapter = new AzureServiceBusAdapter({
28
- * connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION_STRING || "",
29
- * });
30
- * ```
31
- */
32
- export default class PubSubServer extends PubSubTrigger {
33
- protected adapter = new GCPPubSubAdapter({
34
- projectId: process.env.GCP_PROJECT_ID || "my-project",
35
- });
36
-
37
- protected nodes: Record<string, import("@blokjs/runner").BlokService<unknown>> = nodes;
38
- protected workflows: Record<string, import("@blokjs/helper").HelperResponse> = workflows;
39
- }
@@ -1,7 +0,0 @@
1
- import type { HelperResponse } from "@blokjs/helper";
2
-
3
- type Workflows = {
4
- [key: string]: HelperResponse;
5
- };
6
-
7
- export default Workflows;
@@ -1,48 +0,0 @@
1
- import { workflow } from "@blokjs/helper";
2
-
3
- /**
4
- * Example Pub/Sub workflow — fires when a message arrives on a subscription.
5
- *
6
- * Message payload + metadata on ctx.request:
7
- * - ctx.request.body — the message payload
8
- * - ctx.request.headers — message attributes
9
- * - ctx.request.params.topic — topic name
10
- * - ctx.request.params.subscription — subscription name
11
- * - ctx.request.params.messageId — unique message ID
12
- * - ctx.vars._pubsub_message — full broker metadata
13
- *
14
- * Pick a provider in the trigger config:
15
- * provider: "gcp" | "aws" | "azure"
16
- *
17
- * v2 reliability knobs available on each step (uncomment to use):
18
- * idempotencyKey: "$.req.params.messageId" — at-most-once delivery semantics
19
- * retry: { maxAttempts: 3 } — retry on transient failures
20
- */
21
- export default workflow({
22
- name: "On Pub/Sub Message",
23
- version: "1.0.0",
24
- description: "Handles incoming Pub/Sub messages",
25
- trigger: {
26
- pubsub: {
27
- provider: "gcp",
28
- topic: "my-topic",
29
- subscription: "my-subscription",
30
- },
31
- },
32
- steps: [
33
- {
34
- id: "log-message",
35
- use: "@blokjs/api-call",
36
- type: "module",
37
- inputs: {
38
- url: "https://httpbin.org/post",
39
- method: "POST",
40
- body: {
41
- message: "js/ctx.request.body",
42
- topic: "js/ctx.request.params.topic",
43
- messageId: "js/ctx.request.params.messageId",
44
- },
45
- },
46
- },
47
- ],
48
- });
@@ -1,31 +0,0 @@
1
- {
2
- "ts-node": {
3
- "transpileOnly": true
4
- },
5
- "compilerOptions": {
6
- "target": "ES2022",
7
- "module": "es2022",
8
- "moduleResolution": "bundler",
9
- "rootDir": "./src",
10
- "baseUrl": ".",
11
- "paths": {
12
- "@nodes/*": ["./src/nodes/*"],
13
- "@src/*": ["src/*"]
14
- },
15
- "allowJs": true,
16
- "declaration": true,
17
- "declarationMap": true,
18
- "sourceMap": true,
19
- "outDir": "./dist",
20
- "esModuleInterop": true,
21
- "forceConsistentCasingInFileNames": true,
22
- "strict": true,
23
- "noUnusedLocals": true,
24
- "noImplicitReturns": true,
25
- "skipLibCheck": true,
26
- "resolveJsonModule": true
27
- },
28
- "compileOnSave": true,
29
- "include": ["./src", "./src/nodes/**/*.json", "./src/nodes/**/*.md"],
30
- "exclude": ["src/nodes/**/test/*.ts", "**/*.test.ts", "node_modules", "dist"]
31
- }
@@ -1,39 +0,0 @@
1
- import path from "node:path";
2
- import { defineConfig } from "vitest/config";
3
-
4
- export default defineConfig({
5
- test: {
6
- globals: true,
7
- environment: "node",
8
- coverage: {
9
- provider: "istanbul",
10
- reporter: ["text", "json", "html", "lcov"],
11
- exclude: [
12
- "node_modules/",
13
- "dist/",
14
- "**/*.d.ts",
15
- "**/*.config.ts",
16
- "__tests__/",
17
- "src/nodes/",
18
- "src/workflows/",
19
- "src/runner/types/",
20
- "src/runner/metrics/",
21
- ],
22
- thresholds: {
23
- lines: 90,
24
- functions: 90,
25
- branches: 85,
26
- statements: 90,
27
- },
28
- },
29
- include: ["__tests__/**/*.test.ts"],
30
- exclude: ["node_modules", "dist"],
31
- testTimeout: 10000,
32
- hookTimeout: 10000,
33
- },
34
- resolve: {
35
- alias: {
36
- "@": path.resolve(__dirname, "./src"),
37
- },
38
- },
39
- });
package/tsconfig.json DELETED
@@ -1,32 +0,0 @@
1
- {
2
- "ts-node": {
3
- "transpileOnly": true
4
- },
5
- "compilerOptions": {
6
- "target": "ES2022",
7
- "module": "es2022",
8
- "lib": ["ES2022"],
9
- "declaration": true,
10
- "strict": true,
11
- "noImplicitAny": true,
12
- "strictNullChecks": true,
13
- "noImplicitThis": true,
14
- "alwaysStrict": true,
15
- "noUnusedLocals": false,
16
- "noUnusedParameters": false,
17
- "noImplicitReturns": true,
18
- "noFallthroughCasesInSwitch": false,
19
- "inlineSourceMap": true,
20
- "inlineSources": true,
21
- "experimentalDecorators": true,
22
- "emitDecoratorMetadata": true,
23
- "skipLibCheck": true,
24
- "esModuleInterop": true,
25
- "resolveJsonModule": true,
26
- "outDir": "./dist",
27
- "rootDir": "./src",
28
- "moduleResolution": "bundler"
29
- },
30
- "include": ["src/**/*"],
31
- "exclude": ["node_modules", "dist", "**/*.test.ts"]
32
- }