@blokjs/trigger-pubsub 0.2.2 → 0.6.1

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.
Files changed (36) hide show
  1. package/__tests__/integration/gcp-pubsub.real-emulator.test.ts +235 -0
  2. package/__tests__/integration/kafka-pubsub.real-kafka.test.ts +269 -0
  3. package/__tests__/integration/nats-pubsub.real-nats.test.ts +138 -0
  4. package/dist/PubSubTrigger.d.ts +43 -4
  5. package/dist/PubSubTrigger.js +74 -21
  6. package/dist/adapters/AWSSNSAdapter.d.ts +16 -0
  7. package/dist/adapters/AWSSNSAdapter.js +52 -9
  8. package/dist/adapters/AzureServiceBusAdapter.d.ts +15 -0
  9. package/dist/adapters/AzureServiceBusAdapter.js +44 -11
  10. package/dist/adapters/GCPPubSubAdapter.d.ts +16 -0
  11. package/dist/adapters/GCPPubSubAdapter.js +42 -8
  12. package/dist/adapters/KafkaPubSubAdapter.d.ts +53 -0
  13. package/dist/adapters/KafkaPubSubAdapter.js +168 -0
  14. package/dist/adapters/NATSPubSubAdapter.d.ts +52 -0
  15. package/dist/adapters/NATSPubSubAdapter.js +260 -0
  16. package/dist/adapters/RedisStreamsPubSubAdapter.d.ts +49 -0
  17. package/dist/adapters/RedisStreamsPubSubAdapter.js +193 -0
  18. package/dist/adapters/factory.d.ts +22 -0
  19. package/dist/adapters/factory.js +80 -0
  20. package/dist/index.d.ts +36 -45
  21. package/dist/index.js +39 -46
  22. package/package.json +22 -10
  23. package/src/PubSubTrigger.ts +89 -24
  24. package/src/adapters/AWSSNSAdapter.ts +76 -12
  25. package/src/adapters/AzureServiceBusAdapter.ts +57 -14
  26. package/src/adapters/GCPPubSubAdapter.ts +50 -10
  27. package/src/adapters/KafkaPubSubAdapter.ts +194 -0
  28. package/src/adapters/NATSPubSubAdapter.ts +326 -0
  29. package/src/adapters/RedisStreamsPubSubAdapter.ts +225 -0
  30. package/src/adapters/factory.test.ts +87 -0
  31. package/src/adapters/factory.ts +88 -0
  32. package/src/adapters/new-adapters.test.ts +108 -0
  33. package/src/index.ts +40 -41
  34. package/template/package.json +6 -6
  35. package/template/src/runner/PubSubServer.ts +2 -2
  36. package/template/src/workflows/messages/on-message.ts +38 -34
@@ -0,0 +1,80 @@
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
+ import { AWSSNSAdapter } from "./AWSSNSAdapter";
18
+ import { AzureServiceBusAdapter } from "./AzureServiceBusAdapter";
19
+ import { GCPPubSubAdapter } from "./GCPPubSubAdapter";
20
+ import { KafkaPubSubAdapter } from "./KafkaPubSubAdapter";
21
+ import { NATSPubSubAdapter } from "./NATSPubSubAdapter";
22
+ import { RedisStreamsPubSubAdapter } from "./RedisStreamsPubSubAdapter";
23
+ export function resolveProvider(provider) {
24
+ if (provider)
25
+ return provider;
26
+ const envValue = process.env.BLOK_PUBSUB_ADAPTER;
27
+ if (envValue && isPubSubProvider(envValue))
28
+ return envValue;
29
+ return "nats";
30
+ }
31
+ function isPubSubProvider(value) {
32
+ return (value === "nats" ||
33
+ value === "redis-streams" ||
34
+ value === "kafka" ||
35
+ value === "gcp" ||
36
+ value === "aws" ||
37
+ value === "azure");
38
+ }
39
+ export function createPubSubAdapter(provider) {
40
+ switch (provider) {
41
+ case "nats":
42
+ return new NATSPubSubAdapter();
43
+ case "redis-streams":
44
+ return new RedisStreamsPubSubAdapter();
45
+ case "kafka":
46
+ return new KafkaPubSubAdapter();
47
+ case "gcp":
48
+ return new GCPPubSubAdapter();
49
+ case "aws":
50
+ return new AWSSNSAdapter();
51
+ case "azure":
52
+ return new AzureServiceBusAdapter();
53
+ default: {
54
+ const exhaustive = provider;
55
+ throw new Error(`[blok][pubsub] unknown provider "${exhaustive}". Check PubSubProviderSchema.`);
56
+ }
57
+ }
58
+ }
59
+ /**
60
+ * Process-singleton adapter pool — one instance per provider. The
61
+ * trigger calls `getOrCreateAdapter("nats")` once per workflow, and
62
+ * subsequent workflows on the same provider share the broker
63
+ * connection.
64
+ */
65
+ const pool = new Map();
66
+ export function getOrCreateAdapter(provider) {
67
+ let adapter = pool.get(provider);
68
+ if (!adapter) {
69
+ adapter = createPubSubAdapter(provider);
70
+ pool.set(provider, adapter);
71
+ }
72
+ return adapter;
73
+ }
74
+ export function _resetAdapterPoolForTests() {
75
+ for (const adapter of pool.values()) {
76
+ void adapter.disconnect?.().catch(() => { });
77
+ }
78
+ pool.clear();
79
+ }
80
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiZmFjdG9yeS5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9hZGFwdGVycy9mYWN0b3J5LnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7R0FlRztBQUlILE9BQU8sRUFBRSxhQUFhLEVBQUUsTUFBTSxpQkFBaUIsQ0FBQztBQUNoRCxPQUFPLEVBQUUsc0JBQXNCLEVBQUUsTUFBTSwwQkFBMEIsQ0FBQztBQUNsRSxPQUFPLEVBQUUsZ0JBQWdCLEVBQUUsTUFBTSxvQkFBb0IsQ0FBQztBQUN0RCxPQUFPLEVBQUUsa0JBQWtCLEVBQUUsTUFBTSxzQkFBc0IsQ0FBQztBQUMxRCxPQUFPLEVBQUUsaUJBQWlCLEVBQUUsTUFBTSxxQkFBcUIsQ0FBQztBQUN4RCxPQUFPLEVBQUUseUJBQXlCLEVBQUUsTUFBTSw2QkFBNkIsQ0FBQztBQUV4RSxNQUFNLFVBQVUsZUFBZSxDQUFDLFFBQXlCO0lBQ3hELElBQUksUUFBUTtRQUFFLE9BQU8sUUFBUSxDQUFDO0lBQzlCLE1BQU0sUUFBUSxHQUFHLE9BQU8sQ0FBQyxHQUFHLENBQUMsbUJBQW1CLENBQUM7SUFDakQsSUFBSSxRQUFRLElBQUksZ0JBQWdCLENBQUMsUUFBUSxDQUFDO1FBQUUsT0FBTyxRQUFRLENBQUM7SUFDNUQsT0FBTyxNQUFNLENBQUM7QUFDZixDQUFDO0FBRUQsU0FBUyxnQkFBZ0IsQ0FBQyxLQUFhO0lBQ3RDLE9BQU8sQ0FDTixLQUFLLEtBQUssTUFBTTtRQUNoQixLQUFLLEtBQUssZUFBZTtRQUN6QixLQUFLLEtBQUssT0FBTztRQUNqQixLQUFLLEtBQUssS0FBSztRQUNmLEtBQUssS0FBSyxLQUFLO1FBQ2YsS0FBSyxLQUFLLE9BQU8sQ0FDakIsQ0FBQztBQUNILENBQUM7QUFFRCxNQUFNLFVBQVUsbUJBQW1CLENBQUMsUUFBd0I7SUFDM0QsUUFBUSxRQUFRLEVBQUUsQ0FBQztRQUNsQixLQUFLLE1BQU07WUFDVixPQUFPLElBQUksaUJBQWlCLEVBQUUsQ0FBQztRQUNoQyxLQUFLLGVBQWU7WUFDbkIsT0FBTyxJQUFJLHlCQUF5QixFQUFFLENBQUM7UUFDeEMsS0FBSyxPQUFPO1lBQ1gsT0FBTyxJQUFJLGtCQUFrQixFQUFFLENBQUM7UUFDakMsS0FBSyxLQUFLO1lBQ1QsT0FBTyxJQUFJLGdCQUFnQixFQUFFLENBQUM7UUFDL0IsS0FBSyxLQUFLO1lBQ1QsT0FBTyxJQUFJLGFBQWEsRUFBRSxDQUFDO1FBQzVCLEtBQUssT0FBTztZQUNYLE9BQU8sSUFBSSxzQkFBc0IsRUFBRSxDQUFDO1FBQ3JDLE9BQU8sQ0FBQyxDQUFDLENBQUM7WUFDVCxNQUFNLFVBQVUsR0FBVSxRQUFRLENBQUM7WUFDbkMsTUFBTSxJQUFJLEtBQUssQ0FBQyxvQ0FBb0MsVUFBb0IsZ0NBQWdDLENBQUMsQ0FBQztRQUMzRyxDQUFDO0lBQ0YsQ0FBQztBQUNGLENBQUM7QUFFRDs7Ozs7R0FLRztBQUNILE1BQU0sSUFBSSxHQUF1QyxJQUFJLEdBQUcsRUFBRSxDQUFDO0FBRTNELE1BQU0sVUFBVSxrQkFBa0IsQ0FBQyxRQUF3QjtJQUMxRCxJQUFJLE9BQU8sR0FBRyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsQ0FBQyxDQUFDO0lBQ2pDLElBQUksQ0FBQyxPQUFPLEVBQUUsQ0FBQztRQUNkLE9BQU8sR0FBRyxtQkFBbUIsQ0FBQyxRQUFRLENBQUMsQ0FBQztRQUN4QyxJQUFJLENBQUMsR0FBRyxDQUFDLFFBQVEsRUFBRSxPQUFPLENBQUMsQ0FBQztJQUM3QixDQUFDO0lBQ0QsT0FBTyxPQUFPLENBQUM7QUFDaEIsQ0FBQztBQUVELE1BQU0sVUFBVSx5QkFBeUI7SUFDeEMsS0FBSyxNQUFNLE9BQU8sSUFBSSxJQUFJLENBQUMsTUFBTSxFQUFFLEVBQUUsQ0FBQztRQUNyQyxLQUFLLE9BQU8sQ0FBQyxVQUFVLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxHQUFHLEVBQUUsR0FBRSxDQUFDLENBQUMsQ0FBQztJQUM3QyxDQUFDO0lBQ0QsSUFBSSxDQUFDLEtBQUssRUFBRSxDQUFDO0FBQ2QsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogdjAuNyBQUiA2IOKAlCBwdWIvc3ViIGFkYXB0ZXIgZmFjdG9yeS5cbiAqXG4gKiBSZXNvbHZlcyBhIGBwcm92aWRlcmAgc3RyaW5nIHRvIGEgY29uY3JldGUgYFB1YlN1YkFkYXB0ZXJgIGluc3RhbmNlLlxuICogVXNlZCBieSBgUHViU3ViVHJpZ2dlcmAgKHBlci13b3JrZmxvdyBwcm92aWRlciBkaXNwYXRjaCkgYW5kIGJ5IHRoZVxuICogYEBibG9ranMvcHVic3ViLXB1Ymxpc2hgIGhlbHBlci5cbiAqXG4gKiBQcm92aWRlciByZXNvbHV0aW9uIG9yZGVyOlxuICogICAxLiBFeHBsaWNpdCBgcHJvdmlkZXJgIGZpZWxkIG9uIHRoZSB3b3JrZmxvdy5cbiAqICAgMi4gYEJMT0tfUFVCU1VCX0FEQVBURVJgIGVudiB2YXIuXG4gKiAgIDMuIGBcIm5hdHNcImAgZmFsbGJhY2sgKGNoZWFwZXN0IGluZnJhOyBtYXRjaGVzIHRoZSB2MC43IHBsYW4nc1xuICogICAgICBcImRlZmF1bHQgZm9yIHB1Yi9zdWJcIiByZWNvbW1lbmRhdGlvbikuXG4gKlxuICogRWFjaCBhZGFwdGVyIGxhenktaW1wb3J0cyBpdHMgYnJva2VyIFNESyBvbiBmaXJzdCB1c2U7IHdvcmtmbG93c1xuICogdGhhdCBkb24ndCB1c2UgYSBnaXZlbiBwcm92aWRlciBkb24ndCBwYXkgdGhlIGluc3RhbGwgY29zdC5cbiAqL1xuXG5pbXBvcnQgdHlwZSB7IFB1YlN1YlByb3ZpZGVyIH0gZnJvbSBcIkBibG9ranMvaGVscGVyXCI7XG5pbXBvcnQgdHlwZSB7IFB1YlN1YkFkYXB0ZXIgfSBmcm9tIFwiLi4vUHViU3ViVHJpZ2dlclwiO1xuaW1wb3J0IHsgQVdTU05TQWRhcHRlciB9IGZyb20gXCIuL0FXU1NOU0FkYXB0ZXJcIjtcbmltcG9ydCB7IEF6dXJlU2VydmljZUJ1c0FkYXB0ZXIgfSBmcm9tIFwiLi9BenVyZVNlcnZpY2VCdXNBZGFwdGVyXCI7XG5pbXBvcnQgeyBHQ1BQdWJTdWJBZGFwdGVyIH0gZnJvbSBcIi4vR0NQUHViU3ViQWRhcHRlclwiO1xuaW1wb3J0IHsgS2Fma2FQdWJTdWJBZGFwdGVyIH0gZnJvbSBcIi4vS2Fma2FQdWJTdWJBZGFwdGVyXCI7XG5pbXBvcnQgeyBOQVRTUHViU3ViQWRhcHRlciB9IGZyb20gXCIuL05BVFNQdWJTdWJBZGFwdGVyXCI7XG5pbXBvcnQgeyBSZWRpc1N0cmVhbXNQdWJTdWJBZGFwdGVyIH0gZnJvbSBcIi4vUmVkaXNTdHJlYW1zUHViU3ViQWRhcHRlclwiO1xuXG5leHBvcnQgZnVuY3Rpb24gcmVzb2x2ZVByb3ZpZGVyKHByb3ZpZGVyPzogUHViU3ViUHJvdmlkZXIpOiBQdWJTdWJQcm92aWRlciB7XG5cdGlmIChwcm92aWRlcikgcmV0dXJuIHByb3ZpZGVyO1xuXHRjb25zdCBlbnZWYWx1ZSA9IHByb2Nlc3MuZW52LkJMT0tfUFVCU1VCX0FEQVBURVI7XG5cdGlmIChlbnZWYWx1ZSAmJiBpc1B1YlN1YlByb3ZpZGVyKGVudlZhbHVlKSkgcmV0dXJuIGVudlZhbHVlO1xuXHRyZXR1cm4gXCJuYXRzXCI7XG59XG5cbmZ1bmN0aW9uIGlzUHViU3ViUHJvdmlkZXIodmFsdWU6IHN0cmluZyk6IHZhbHVlIGlzIFB1YlN1YlByb3ZpZGVyIHtcblx0cmV0dXJuIChcblx0XHR2YWx1ZSA9PT0gXCJuYXRzXCIgfHxcblx0XHR2YWx1ZSA9PT0gXCJyZWRpcy1zdHJlYW1zXCIgfHxcblx0XHR2YWx1ZSA9PT0gXCJrYWZrYVwiIHx8XG5cdFx0dmFsdWUgPT09IFwiZ2NwXCIgfHxcblx0XHR2YWx1ZSA9PT0gXCJhd3NcIiB8fFxuXHRcdHZhbHVlID09PSBcImF6dXJlXCJcblx0KTtcbn1cblxuZXhwb3J0IGZ1bmN0aW9uIGNyZWF0ZVB1YlN1YkFkYXB0ZXIocHJvdmlkZXI6IFB1YlN1YlByb3ZpZGVyKTogUHViU3ViQWRhcHRlciB7XG5cdHN3aXRjaCAocHJvdmlkZXIpIHtcblx0XHRjYXNlIFwibmF0c1wiOlxuXHRcdFx0cmV0dXJuIG5ldyBOQVRTUHViU3ViQWRhcHRlcigpO1xuXHRcdGNhc2UgXCJyZWRpcy1zdHJlYW1zXCI6XG5cdFx0XHRyZXR1cm4gbmV3IFJlZGlzU3RyZWFtc1B1YlN1YkFkYXB0ZXIoKTtcblx0XHRjYXNlIFwia2Fma2FcIjpcblx0XHRcdHJldHVybiBuZXcgS2Fma2FQdWJTdWJBZGFwdGVyKCk7XG5cdFx0Y2FzZSBcImdjcFwiOlxuXHRcdFx0cmV0dXJuIG5ldyBHQ1BQdWJTdWJBZGFwdGVyKCk7XG5cdFx0Y2FzZSBcImF3c1wiOlxuXHRcdFx0cmV0dXJuIG5ldyBBV1NTTlNBZGFwdGVyKCk7XG5cdFx0Y2FzZSBcImF6dXJlXCI6XG5cdFx0XHRyZXR1cm4gbmV3IEF6dXJlU2VydmljZUJ1c0FkYXB0ZXIoKTtcblx0XHRkZWZhdWx0OiB7XG5cdFx0XHRjb25zdCBleGhhdXN0aXZlOiBuZXZlciA9IHByb3ZpZGVyO1xuXHRcdFx0dGhyb3cgbmV3IEVycm9yKGBbYmxva11bcHVic3ViXSB1bmtub3duIHByb3ZpZGVyIFwiJHtleGhhdXN0aXZlIGFzIHN0cmluZ31cIi4gQ2hlY2sgUHViU3ViUHJvdmlkZXJTY2hlbWEuYCk7XG5cdFx0fVxuXHR9XG59XG5cbi8qKlxuICogUHJvY2Vzcy1zaW5nbGV0b24gYWRhcHRlciBwb29sIOKAlCBvbmUgaW5zdGFuY2UgcGVyIHByb3ZpZGVyLiBUaGVcbiAqIHRyaWdnZXIgY2FsbHMgYGdldE9yQ3JlYXRlQWRhcHRlcihcIm5hdHNcIilgIG9uY2UgcGVyIHdvcmtmbG93LCBhbmRcbiAqIHN1YnNlcXVlbnQgd29ya2Zsb3dzIG9uIHRoZSBzYW1lIHByb3ZpZGVyIHNoYXJlIHRoZSBicm9rZXJcbiAqIGNvbm5lY3Rpb24uXG4gKi9cbmNvbnN0IHBvb2w6IE1hcDxQdWJTdWJQcm92aWRlciwgUHViU3ViQWRhcHRlcj4gPSBuZXcgTWFwKCk7XG5cbmV4cG9ydCBmdW5jdGlvbiBnZXRPckNyZWF0ZUFkYXB0ZXIocHJvdmlkZXI6IFB1YlN1YlByb3ZpZGVyKTogUHViU3ViQWRhcHRlciB7XG5cdGxldCBhZGFwdGVyID0gcG9vbC5nZXQocHJvdmlkZXIpO1xuXHRpZiAoIWFkYXB0ZXIpIHtcblx0XHRhZGFwdGVyID0gY3JlYXRlUHViU3ViQWRhcHRlcihwcm92aWRlcik7XG5cdFx0cG9vbC5zZXQocHJvdmlkZXIsIGFkYXB0ZXIpO1xuXHR9XG5cdHJldHVybiBhZGFwdGVyO1xufVxuXG5leHBvcnQgZnVuY3Rpb24gX3Jlc2V0QWRhcHRlclBvb2xGb3JUZXN0cygpOiB2b2lkIHtcblx0Zm9yIChjb25zdCBhZGFwdGVyIG9mIHBvb2wudmFsdWVzKCkpIHtcblx0XHR2b2lkIGFkYXB0ZXIuZGlzY29ubmVjdD8uKCkuY2F0Y2goKCkgPT4ge30pO1xuXHR9XG5cdHBvb2wuY2xlYXIoKTtcbn1cbiJdfQ==
package/dist/index.d.ts CHANGED
@@ -1,55 +1,46 @@
1
1
  /**
2
2
  * @blokjs/trigger-pubsub
3
3
  *
4
- * Pub/Sub-based trigger for Blok workflows.
5
- * Supports multiple pub/sub providers:
6
- * - Google Cloud Pub/Sub
7
- * - AWS SNS/SQS
8
- * - Azure Service Bus
9
- *
10
- * @example GCP Pub/Sub
11
- * ```typescript
12
- * import { PubSubTrigger, GCPPubSubAdapter } from "@blokjs/trigger-pubsub";
13
- *
14
- * class MyPubSubTrigger extends PubSubTrigger {
15
- * protected adapter = new GCPPubSubAdapter({
16
- * projectId: "my-project",
17
- * });
18
- *
19
- * protected nodes = myNodes;
20
- * protected workflows = myWorkflows;
21
- * }
22
- *
23
- * const trigger = new MyPubSubTrigger();
24
- * await trigger.listen();
25
- * ```
26
- *
27
- * @example AWS SNS/SQS
28
- * ```typescript
29
- * import { PubSubTrigger, AWSSNSAdapter } from "@blokjs/trigger-pubsub";
30
- *
31
- * class MyPubSubTrigger extends PubSubTrigger {
32
- * protected adapter = new AWSSNSAdapter({
33
- * region: "us-east-1",
34
- * });
35
- * // ...
36
- * }
37
- * ```
38
- *
39
- * @example Azure Service Bus
40
- * ```typescript
41
- * import { PubSubTrigger, AzureServiceBusAdapter } from "@blokjs/trigger-pubsub";
42
- *
43
- * class MyPubSubTrigger extends PubSubTrigger {
44
- * protected adapter = new AzureServiceBusAdapter({
45
- * connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION_STRING,
46
- * });
47
- * // ...
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": [...]
48
35
  * }
49
36
  * ```
50
37
  */
51
38
  export { PubSubTrigger, type PubSubAdapter, type PubSubMessage, } from "./PubSubTrigger";
52
- export { GCPPubSubAdapter, type GCPPubSubConfig } from "./adapters/GCPPubSubAdapter";
53
39
  export { AWSSNSAdapter, type AWSSNSConfig } from "./adapters/AWSSNSAdapter";
54
40
  export { AzureServiceBusAdapter, type AzureServiceBusConfig } from "./adapters/AzureServiceBusAdapter";
41
+ export { GCPPubSubAdapter, type GCPPubSubConfig } from "./adapters/GCPPubSubAdapter";
42
+ export { KafkaPubSubAdapter, type KafkaPubSubConfig } from "./adapters/KafkaPubSubAdapter";
43
+ export { NATSPubSubAdapter, type NATSPubSubConfig } from "./adapters/NATSPubSubAdapter";
44
+ export { RedisStreamsPubSubAdapter, type RedisStreamsPubSubConfig } from "./adapters/RedisStreamsPubSubAdapter";
45
+ export { _resetAdapterPoolForTests, createPubSubAdapter, getOrCreateAdapter, resolveProvider, } from "./adapters/factory";
55
46
  export type { PubSubProvider, PubSubTriggerOpts, } from "@blokjs/helper";
package/dist/index.js CHANGED
@@ -1,57 +1,50 @@
1
1
  /**
2
2
  * @blokjs/trigger-pubsub
3
3
  *
4
- * Pub/Sub-based trigger for Blok workflows.
5
- * Supports multiple pub/sub providers:
6
- * - Google Cloud Pub/Sub
7
- * - AWS SNS/SQS
8
- * - Azure Service Bus
9
- *
10
- * @example GCP Pub/Sub
11
- * ```typescript
12
- * import { PubSubTrigger, GCPPubSubAdapter } from "@blokjs/trigger-pubsub";
13
- *
14
- * class MyPubSubTrigger extends PubSubTrigger {
15
- * protected adapter = new GCPPubSubAdapter({
16
- * projectId: "my-project",
17
- * });
18
- *
19
- * protected nodes = myNodes;
20
- * protected workflows = myWorkflows;
21
- * }
22
- *
23
- * const trigger = new MyPubSubTrigger();
24
- * await trigger.listen();
25
- * ```
26
- *
27
- * @example AWS SNS/SQS
28
- * ```typescript
29
- * import { PubSubTrigger, AWSSNSAdapter } from "@blokjs/trigger-pubsub";
30
- *
31
- * class MyPubSubTrigger extends PubSubTrigger {
32
- * protected adapter = new AWSSNSAdapter({
33
- * region: "us-east-1",
34
- * });
35
- * // ...
36
- * }
37
- * ```
38
- *
39
- * @example Azure Service Bus
40
- * ```typescript
41
- * import { PubSubTrigger, AzureServiceBusAdapter } from "@blokjs/trigger-pubsub";
42
- *
43
- * class MyPubSubTrigger extends PubSubTrigger {
44
- * protected adapter = new AzureServiceBusAdapter({
45
- * connectionString: process.env.AZURE_SERVICE_BUS_CONNECTION_STRING,
46
- * });
47
- * // ...
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": [...]
48
35
  * }
49
36
  * ```
50
37
  */
51
38
  // Core exports
52
39
  export { PubSubTrigger, } from "./PubSubTrigger";
53
40
  // Adapters
54
- export { GCPPubSubAdapter } from "./adapters/GCPPubSubAdapter";
55
41
  export { AWSSNSAdapter } from "./adapters/AWSSNSAdapter";
56
42
  export { AzureServiceBusAdapter } from "./adapters/AzureServiceBusAdapter";
57
- //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FpREc7QUFFSCxlQUFlO0FBQ2YsT0FBTyxFQUNOLGFBQWEsR0FHYixNQUFNLGlCQUFpQixDQUFDO0FBRXpCLFdBQVc7QUFDWCxPQUFPLEVBQUUsZ0JBQWdCLEVBQXdCLE1BQU0sNkJBQTZCLENBQUM7QUFDckYsT0FBTyxFQUFFLGFBQWEsRUFBcUIsTUFBTSwwQkFBMEIsQ0FBQztBQUM1RSxPQUFPLEVBQUUsc0JBQXNCLEVBQThCLE1BQU0sbUNBQW1DLENBQUMiLCJzb3VyY2VzQ29udGVudCI6WyIvKipcbiAqIEBibG9ranMvdHJpZ2dlci1wdWJzdWJcbiAqXG4gKiBQdWIvU3ViLWJhc2VkIHRyaWdnZXIgZm9yIEJsb2sgd29ya2Zsb3dzLlxuICogU3VwcG9ydHMgbXVsdGlwbGUgcHViL3N1YiBwcm92aWRlcnM6XG4gKiAtIEdvb2dsZSBDbG91ZCBQdWIvU3ViXG4gKiAtIEFXUyBTTlMvU1FTXG4gKiAtIEF6dXJlIFNlcnZpY2UgQnVzXG4gKlxuICogQGV4YW1wbGUgR0NQIFB1Yi9TdWJcbiAqIGBgYHR5cGVzY3JpcHRcbiAqIGltcG9ydCB7IFB1YlN1YlRyaWdnZXIsIEdDUFB1YlN1YkFkYXB0ZXIgfSBmcm9tIFwiQGJsb2tqcy90cmlnZ2VyLXB1YnN1YlwiO1xuICpcbiAqIGNsYXNzIE15UHViU3ViVHJpZ2dlciBleHRlbmRzIFB1YlN1YlRyaWdnZXIge1xuICogICBwcm90ZWN0ZWQgYWRhcHRlciA9IG5ldyBHQ1BQdWJTdWJBZGFwdGVyKHtcbiAqICAgICBwcm9qZWN0SWQ6IFwibXktcHJvamVjdFwiLFxuICogICB9KTtcbiAqXG4gKiAgIHByb3RlY3RlZCBub2RlcyA9IG15Tm9kZXM7XG4gKiAgIHByb3RlY3RlZCB3b3JrZmxvd3MgPSBteVdvcmtmbG93cztcbiAqIH1cbiAqXG4gKiBjb25zdCB0cmlnZ2VyID0gbmV3IE15UHViU3ViVHJpZ2dlcigpO1xuICogYXdhaXQgdHJpZ2dlci5saXN0ZW4oKTtcbiAqIGBgYFxuICpcbiAqIEBleGFtcGxlIEFXUyBTTlMvU1FTXG4gKiBgYGB0eXBlc2NyaXB0XG4gKiBpbXBvcnQgeyBQdWJTdWJUcmlnZ2VyLCBBV1NTTlNBZGFwdGVyIH0gZnJvbSBcIkBibG9ranMvdHJpZ2dlci1wdWJzdWJcIjtcbiAqXG4gKiBjbGFzcyBNeVB1YlN1YlRyaWdnZXIgZXh0ZW5kcyBQdWJTdWJUcmlnZ2VyIHtcbiAqICAgcHJvdGVjdGVkIGFkYXB0ZXIgPSBuZXcgQVdTU05TQWRhcHRlcih7XG4gKiAgICAgcmVnaW9uOiBcInVzLWVhc3QtMVwiLFxuICogICB9KTtcbiAqICAgLy8gLi4uXG4gKiB9XG4gKiBgYGBcbiAqXG4gKiBAZXhhbXBsZSBBenVyZSBTZXJ2aWNlIEJ1c1xuICogYGBgdHlwZXNjcmlwdFxuICogaW1wb3J0IHsgUHViU3ViVHJpZ2dlciwgQXp1cmVTZXJ2aWNlQnVzQWRhcHRlciB9IGZyb20gXCJAYmxva2pzL3RyaWdnZXItcHVic3ViXCI7XG4gKlxuICogY2xhc3MgTXlQdWJTdWJUcmlnZ2VyIGV4dGVuZHMgUHViU3ViVHJpZ2dlciB7XG4gKiAgIHByb3RlY3RlZCBhZGFwdGVyID0gbmV3IEF6dXJlU2VydmljZUJ1c0FkYXB0ZXIoe1xuICogICAgIGNvbm5lY3Rpb25TdHJpbmc6IHByb2Nlc3MuZW52LkFaVVJFX1NFUlZJQ0VfQlVTX0NPTk5FQ1RJT05fU1RSSU5HLFxuICogICB9KTtcbiAqICAgLy8gLi4uXG4gKiB9XG4gKiBgYGBcbiAqL1xuXG4vLyBDb3JlIGV4cG9ydHNcbmV4cG9ydCB7XG5cdFB1YlN1YlRyaWdnZXIsXG5cdHR5cGUgUHViU3ViQWRhcHRlcixcblx0dHlwZSBQdWJTdWJNZXNzYWdlLFxufSBmcm9tIFwiLi9QdWJTdWJUcmlnZ2VyXCI7XG5cbi8vIEFkYXB0ZXJzXG5leHBvcnQgeyBHQ1BQdWJTdWJBZGFwdGVyLCB0eXBlIEdDUFB1YlN1YkNvbmZpZyB9IGZyb20gXCIuL2FkYXB0ZXJzL0dDUFB1YlN1YkFkYXB0ZXJcIjtcbmV4cG9ydCB7IEFXU1NOU0FkYXB0ZXIsIHR5cGUgQVdTU05TQ29uZmlnIH0gZnJvbSBcIi4vYWRhcHRlcnMvQVdTU05TQWRhcHRlclwiO1xuZXhwb3J0IHsgQXp1cmVTZXJ2aWNlQnVzQWRhcHRlciwgdHlwZSBBenVyZVNlcnZpY2VCdXNDb25maWcgfSBmcm9tIFwiLi9hZGFwdGVycy9BenVyZVNlcnZpY2VCdXNBZGFwdGVyXCI7XG5cbi8vIFJlLWV4cG9ydCB0eXBlcyBmcm9tIGhlbHBlciBmb3IgY29udmVuaWVuY2VcbmV4cG9ydCB0eXBlIHtcblx0UHViU3ViUHJvdmlkZXIsXG5cdFB1YlN1YlRyaWdnZXJPcHRzLFxufSBmcm9tIFwiQGJsb2tqcy9oZWxwZXJcIjtcbiJdfQ==
43
+ export { GCPPubSubAdapter } from "./adapters/GCPPubSubAdapter";
44
+ export { KafkaPubSubAdapter } from "./adapters/KafkaPubSubAdapter";
45
+ export { NATSPubSubAdapter } from "./adapters/NATSPubSubAdapter";
46
+ export { RedisStreamsPubSubAdapter } from "./adapters/RedisStreamsPubSubAdapter";
47
+ // v0.7 PR 6 — factory + pool used by PubSubTrigger and exposed for
48
+ // helper nodes (`@blokjs/pubsub-publish`).
49
+ export { _resetAdapterPoolForTests, createPubSubAdapter, getOrCreateAdapter, resolveProvider, } from "./adapters/factory";
50
+ //# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiaW5kZXguanMiLCJzb3VyY2VSb290IjoiIiwic291cmNlcyI6WyIuLi9zcmMvaW5kZXgudHMiXSwibmFtZXMiOltdLCJtYXBwaW5ncyI6IkFBQUE7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7OztHQW9DRztBQUVILGVBQWU7QUFDZixPQUFPLEVBQ04sYUFBYSxHQUdiLE1BQU0saUJBQWlCLENBQUM7QUFFekIsV0FBVztBQUNYLE9BQU8sRUFBRSxhQUFhLEVBQXFCLE1BQU0sMEJBQTBCLENBQUM7QUFDNUUsT0FBTyxFQUFFLHNCQUFzQixFQUE4QixNQUFNLG1DQUFtQyxDQUFDO0FBQ3ZHLE9BQU8sRUFBRSxnQkFBZ0IsRUFBd0IsTUFBTSw2QkFBNkIsQ0FBQztBQUNyRixPQUFPLEVBQUUsa0JBQWtCLEVBQTBCLE1BQU0sK0JBQStCLENBQUM7QUFDM0YsT0FBTyxFQUFFLGlCQUFpQixFQUF5QixNQUFNLDhCQUE4QixDQUFDO0FBQ3hGLE9BQU8sRUFBRSx5QkFBeUIsRUFBaUMsTUFBTSxzQ0FBc0MsQ0FBQztBQUVoSCxtRUFBbUU7QUFDbkUsMkNBQTJDO0FBQzNDLE9BQU8sRUFDTix5QkFBeUIsRUFDekIsbUJBQW1CLEVBQ25CLGtCQUFrQixFQUNsQixlQUFlLEdBQ2YsTUFBTSxvQkFBb0IsQ0FBQyIsInNvdXJjZXNDb250ZW50IjpbIi8qKlxuICogQGJsb2tqcy90cmlnZ2VyLXB1YnN1YlxuICpcbiAqIFB1Yi9TdWItYmFzZWQgdHJpZ2dlciBmb3IgQmxvayB3b3JrZmxvd3MuIFN1cHBvcnRzIDYgcHJvdmlkZXJzOlxuICpcbiAqICAgLSAqKk5BVFMqKiAoQ29yZSArIEpldFN0cmVhbSkg4oCUIGNoZWFwZXN0IGluZnJhOyBzdWJqZWN0IHdpbGRjYXJkcy5cbiAqICAgLSAqKlJlZGlzIFN0cmVhbXMqKiAgICAgICAgICAg4oCUIHdoZW4gUmVkaXMgaXMgYWxyZWFkeSBpbiBzdGFjay5cbiAqICAgLSAqKkthZmthKiogICAgICAgICAgICAgICAgICAg4oCUIGhpZ2gtdGhyb3VnaHB1dCBzdHJlYW1pbmcuXG4gKiAgIC0gKipHQ1AgUHViL1N1YioqICAgICAgICAgICAgIOKAlCBHb29nbGUgQ2xvdWQtbG9ja2VkLlxuICogICAtICoqQVdTIFNOUytTUVMqKiAgICAgICAgICAgICDigJQgU05TIGZhbi1vdXQg4oaSIFNRUyBxdWV1ZWluZy5cbiAqICAgLSAqKkF6dXJlIFNlcnZpY2UgQnVzKiogICAgICAg4oCUIEF6dXJlIFNlcnZpY2UgQnVzLlxuICpcbiAqIHYwLjcrIOKAlCBwaWNrIHRoZSBhZGFwdGVyIHBlciB3b3JrZmxvdyB2aWEgYHRyaWdnZXIucHVic3ViLnByb3ZpZGVyYC5cbiAqIGBCTE9LX1BVQlNVQl9BREFQVEVSYCBlbnYgdmFyIHNldHMgdGhlIGRlZmF1bHQgKGZhbGxzIGJhY2sgdG8gTkFUUykuXG4gKiBTdWJjbGFzc2VzIGNhbiBzdGlsbCBzZXQgYHByb3RlY3RlZCBhZGFwdGVyYCBkaXJlY3RseSBmb3IgYmFjay1cbiAqIGNvbXBhdCB3aXRoIHRoZSBwcmUtdjAuNyBzaW5nbGUtYWRhcHRlciBwYXR0ZXJuLlxuICpcbiAqICoqRmFuLW91dCB2cyBjb21wZXRpbmctY29uc3VtZXIqKjogb21pdCBgY29uc3VtZXJHcm91cGAgZm9yIGZhbi1vdXRcbiAqIChldmVyeSBzdWJzY3JpYmVyIHNlZXMgZXZlcnkgbWVzc2FnZSk7IHNldCBpdCBmb3IgY29tcGV0aW5nLWNvbnN1bWVyXG4gKiAoMSBvZiBOIHdpdGhpbiBncm91cCkuIE9uZSBmaWVsZCBkaXNhbWJpZ3VhdGVzIHRoZSB0d28gc2VtYW50aWNzLlxuICpcbiAqIEBleGFtcGxlIHYwLjcg4oCUIE5BVFMgc3ViamVjdCBoaWVyYXJjaHkgd2l0aCBKU09OIHdvcmtmbG93XG4gKiBgYGBqc29uXG4gKiB7XG4gKiAgIFwibmFtZVwiOiBcImF1ZGl0LWFsbC1vcmRlci1ldmVudHNcIixcbiAqICAgXCJ0cmlnZ2VyXCI6IHtcbiAqICAgICBcInB1YnN1YlwiOiB7XG4gKiAgICAgICBcInByb3ZpZGVyXCI6IFwibmF0c1wiLFxuICogICAgICAgXCJ0b3BpY1wiOiBcIm9yZGVycy4+XCIsXG4gKiAgICAgICBcImR1cmFibGVcIjogdHJ1ZSxcbiAqICAgICAgIFwic3RhcnRGcm9tXCI6IFwiZWFybGllc3RcIlxuICogICAgIH1cbiAqICAgfSxcbiAqICAgXCJzdGVwc1wiOiBbLi4uXVxuICogfVxuICogYGBgXG4gKi9cblxuLy8gQ29yZSBleHBvcnRzXG5leHBvcnQge1xuXHRQdWJTdWJUcmlnZ2VyLFxuXHR0eXBlIFB1YlN1YkFkYXB0ZXIsXG5cdHR5cGUgUHViU3ViTWVzc2FnZSxcbn0gZnJvbSBcIi4vUHViU3ViVHJpZ2dlclwiO1xuXG4vLyBBZGFwdGVyc1xuZXhwb3J0IHsgQVdTU05TQWRhcHRlciwgdHlwZSBBV1NTTlNDb25maWcgfSBmcm9tIFwiLi9hZGFwdGVycy9BV1NTTlNBZGFwdGVyXCI7XG5leHBvcnQgeyBBenVyZVNlcnZpY2VCdXNBZGFwdGVyLCB0eXBlIEF6dXJlU2VydmljZUJ1c0NvbmZpZyB9IGZyb20gXCIuL2FkYXB0ZXJzL0F6dXJlU2VydmljZUJ1c0FkYXB0ZXJcIjtcbmV4cG9ydCB7IEdDUFB1YlN1YkFkYXB0ZXIsIHR5cGUgR0NQUHViU3ViQ29uZmlnIH0gZnJvbSBcIi4vYWRhcHRlcnMvR0NQUHViU3ViQWRhcHRlclwiO1xuZXhwb3J0IHsgS2Fma2FQdWJTdWJBZGFwdGVyLCB0eXBlIEthZmthUHViU3ViQ29uZmlnIH0gZnJvbSBcIi4vYWRhcHRlcnMvS2Fma2FQdWJTdWJBZGFwdGVyXCI7XG5leHBvcnQgeyBOQVRTUHViU3ViQWRhcHRlciwgdHlwZSBOQVRTUHViU3ViQ29uZmlnIH0gZnJvbSBcIi4vYWRhcHRlcnMvTkFUU1B1YlN1YkFkYXB0ZXJcIjtcbmV4cG9ydCB7IFJlZGlzU3RyZWFtc1B1YlN1YkFkYXB0ZXIsIHR5cGUgUmVkaXNTdHJlYW1zUHViU3ViQ29uZmlnIH0gZnJvbSBcIi4vYWRhcHRlcnMvUmVkaXNTdHJlYW1zUHViU3ViQWRhcHRlclwiO1xuXG4vLyB2MC43IFBSIDYg4oCUIGZhY3RvcnkgKyBwb29sIHVzZWQgYnkgUHViU3ViVHJpZ2dlciBhbmQgZXhwb3NlZCBmb3Jcbi8vIGhlbHBlciBub2RlcyAoYEBibG9ranMvcHVic3ViLXB1Ymxpc2hgKS5cbmV4cG9ydCB7XG5cdF9yZXNldEFkYXB0ZXJQb29sRm9yVGVzdHMsXG5cdGNyZWF0ZVB1YlN1YkFkYXB0ZXIsXG5cdGdldE9yQ3JlYXRlQWRhcHRlcixcblx0cmVzb2x2ZVByb3ZpZGVyLFxufSBmcm9tIFwiLi9hZGFwdGVycy9mYWN0b3J5XCI7XG5cbi8vIFJlLWV4cG9ydCB0eXBlcyBmcm9tIGhlbHBlciBmb3IgY29udmVuaWVuY2VcbmV4cG9ydCB0eXBlIHtcblx0UHViU3ViUHJvdmlkZXIsXG5cdFB1YlN1YlRyaWdnZXJPcHRzLFxufSBmcm9tIFwiQGJsb2tqcy9oZWxwZXJcIjtcbiJdfQ==
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@blokjs/trigger-pubsub",
3
- "version": "0.2.2",
4
- "description": "Pub/Sub-based trigger for Blok workflows - supports GCP Pub/Sub, AWS SNS/SQS, and Azure Service Bus",
3
+ "version": "0.6.1",
4
+ "description": "Pub/Sub trigger for Blok workflows supports NATS (Core + JetStream), Redis Streams, Kafka, GCP Pub/Sub, AWS SNS+SQS, and Azure Service Bus.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
7
7
  "types": "dist/index.d.ts",
@@ -14,9 +14,9 @@
14
14
  "author": "Deskree Technologies Inc.",
15
15
  "license": "Apache-2.0",
16
16
  "dependencies": {
17
- "@blokjs/helper": "^0.2.0",
18
- "@blokjs/runner": "^0.2.0",
19
- "@blokjs/shared": "^0.2.0",
17
+ "@blokjs/helper": "^0.6.1",
18
+ "@blokjs/runner": "^0.6.1",
19
+ "@blokjs/shared": "^0.6.1",
20
20
  "@opentelemetry/api": "^1.9.0",
21
21
  "uuid": "^11.1.0"
22
22
  },
@@ -27,15 +27,15 @@
27
27
  "vitest": "^4.0.18"
28
28
  },
29
29
  "peerDependencies": {
30
- "@google-cloud/pubsub": "^5.0.0",
31
30
  "@aws-sdk/client-sns": "^3.980.0",
32
31
  "@aws-sdk/client-sqs": "^3.980.0",
33
- "@azure/service-bus": "^7.9.5"
32
+ "@azure/service-bus": "^7.9.5",
33
+ "@google-cloud/pubsub": "^5.0.0",
34
+ "ioredis": "^5.9.2",
35
+ "kafkajs": "^2.2.0",
36
+ "nats": "^2.29.0"
34
37
  },
35
38
  "peerDependenciesMeta": {
36
- "@google-cloud/pubsub": {
37
- "optional": true
38
- },
39
39
  "@aws-sdk/client-sns": {
40
40
  "optional": true
41
41
  },
@@ -44,6 +44,18 @@
44
44
  },
45
45
  "@azure/service-bus": {
46
46
  "optional": true
47
+ },
48
+ "@google-cloud/pubsub": {
49
+ "optional": true
50
+ },
51
+ "ioredis": {
52
+ "optional": true
53
+ },
54
+ "kafkajs": {
55
+ "optional": true
56
+ },
57
+ "nats": {
58
+ "optional": true
47
59
  }
48
60
  },
49
61
  "private": false,
@@ -20,9 +20,9 @@
20
20
 
21
21
  import type { HelperResponse, PubSubProvider, PubSubTriggerOpts } from "@blokjs/helper";
22
22
  import {
23
+ type BlokService,
23
24
  DefaultLogger,
24
25
  type GlobalOptions,
25
- type BlokService,
26
26
  NodeMap,
27
27
  TriggerBase,
28
28
  type TriggerResponse,
@@ -74,6 +74,18 @@ export interface PubSubAdapter {
74
74
  /** Unsubscribe from a topic */
75
75
  unsubscribe(subscription: string): Promise<void>;
76
76
 
77
+ /**
78
+ * v0.7 PR 6 — publish a single message to a topic. Used by the
79
+ * `@blokjs/pubsub-publish` helper and any workflow that fan-outs
80
+ * events to subscribers. Provider-portable: each adapter wraps its
81
+ * native producer client.
82
+ *
83
+ * Optional `partitionKey` / `orderingKey` is honored by providers
84
+ * that support per-key ordering (Kafka, GCP Pub/Sub ordered
85
+ * delivery). Ignored otherwise.
86
+ */
87
+ publish(topic: string, payload: unknown, opts?: { partitionKey?: string; orderingKey?: string }): Promise<void>;
88
+
77
89
  /** Check if connected */
78
90
  isConnected(): boolean;
79
91
 
@@ -107,18 +119,27 @@ export abstract class PubSubTrigger extends TriggerBase {
107
119
  process.env.PROJECT_VERSION || "0.0.1",
108
120
  );
109
121
  protected readonly logger = new DefaultLogger();
110
- protected abstract adapter: PubSubAdapter;
122
+
123
+ /**
124
+ * v0.7 PR 6 — back-compat default adapter. When subclasses set
125
+ * `protected adapter = new GCPPubSubAdapter()` (pre-v0.7 pattern),
126
+ * ALL workflows route through it regardless of their `provider`
127
+ * field. When unset, each workflow's `provider` is resolved via
128
+ * the factory.
129
+ */
130
+ protected adapter?: PubSubAdapter;
131
+
132
+ /**
133
+ * v0.7 PR 6 — adapter pool, keyed by provider. Populated lazily in
134
+ * `listen()` as workflows are matched to providers. Drained in
135
+ * `stop()`. One adapter (one broker connection) per provider.
136
+ */
137
+ protected adapterPool: Map<string, PubSubAdapter> = new Map();
111
138
 
112
139
  // Subclasses provide these
113
140
  protected abstract nodes: Record<string, BlokService<unknown>>;
114
141
  protected abstract workflows: Record<string, HelperResponse>;
115
142
 
116
- constructor() {
117
- super();
118
- this.loadNodes();
119
- this.loadWorkflows();
120
- }
121
-
122
143
  /**
123
144
  * Load nodes into the node map
124
145
  */
@@ -143,11 +164,12 @@ export abstract class PubSubTrigger extends TriggerBase {
143
164
  async listen(): Promise<number> {
144
165
  const startTime = this.startCounter();
145
166
 
146
- try {
147
- // Connect to pub/sub system
148
- await this.adapter.connect();
149
- this.logger.log(`Connected to ${this.adapter.provider} pub/sub system`);
167
+ // Initialize nodes and workflows (called here because subclass properties
168
+ // aren't available in parent constructor)
169
+ this.loadNodes();
170
+ this.loadWorkflows();
150
171
 
172
+ try {
151
173
  // Find all workflows with pub/sub triggers
152
174
  const pubsubWorkflows = this.getPubSubWorkflows();
153
175
 
@@ -156,14 +178,17 @@ export abstract class PubSubTrigger extends TriggerBase {
156
178
  return this.endCounter(startTime);
157
179
  }
158
180
 
159
- // Subscribe to each topic/subscription
181
+ // Subscribe to each topic via the adapter that owns its
182
+ // provider. Per-workflow `provider` field with subclass-
183
+ // adapter back-compat (handled in resolveAdapterForWorkflow).
160
184
  for (const workflow of pubsubWorkflows) {
161
185
  const config = workflow.config.trigger?.pubsub as PubSubTriggerOpts;
186
+ const adapter = await this.resolveAdapterForWorkflow(config);
162
187
  this.logger.log(
163
- `Subscribing to topic: ${config.topic}, subscription: ${config.subscription} for workflow: ${workflow.path}`,
188
+ `Subscribing to topic: ${config.topic} via ${adapter.provider} (subscription: ${config.subscription ?? "<auto>"}, group: ${config.consumerGroup ?? "<fan-out>"})`,
164
189
  );
165
190
 
166
- await this.adapter.subscribe(config, async (message) => {
191
+ await adapter.subscribe(config, async (message) => {
167
192
  await this.handleMessage(message, workflow, config);
168
193
  });
169
194
  }
@@ -183,13 +208,53 @@ export abstract class PubSubTrigger extends TriggerBase {
183
208
  }
184
209
 
185
210
  /**
186
- * Stop the pub/sub subscriber
211
+ * Stop the pub/sub subscriber — drains every adapter in the pool
212
+ * plus the subclass-set adapter (if any).
187
213
  */
188
214
  async stop(): Promise<void> {
189
- await this.adapter.disconnect();
215
+ for (const adapter of this.adapterPool.values()) {
216
+ try {
217
+ await adapter.disconnect();
218
+ } catch (err) {
219
+ this.logger.error(`[blok][pubsub] disconnect failed: ${(err as Error).message}`);
220
+ }
221
+ }
222
+ this.adapterPool.clear();
190
223
  this.logger.log("Pub/Sub trigger stopped");
191
224
  }
192
225
 
226
+ /**
227
+ * v0.7 PR 6 — pick the adapter for a workflow's `provider` field.
228
+ *
229
+ * Resolution order:
230
+ * 1. Subclass-set `this.adapter` (back-compat).
231
+ * 2. Per-workflow `provider` field via the factory.
232
+ * 3. `BLOK_PUBSUB_ADAPTER` env var.
233
+ * 4. `"nats"` fallback.
234
+ *
235
+ * Adapters are connected on first use and pooled per provider.
236
+ */
237
+ protected async resolveAdapterForWorkflow(config: PubSubTriggerOpts): Promise<PubSubAdapter> {
238
+ if (this.adapter) {
239
+ if (!this.adapter.isConnected()) {
240
+ await this.adapter.connect();
241
+ this.logger.log(`Connected to ${this.adapter.provider} pub/sub system (subclass adapter)`);
242
+ }
243
+ this.adapterPool.set(this.adapter.provider, this.adapter);
244
+ return this.adapter;
245
+ }
246
+ const { resolveProvider, createPubSubAdapter } = await import("./adapters/factory");
247
+ const provider = resolveProvider(config.provider);
248
+ let adapter = this.adapterPool.get(provider);
249
+ if (!adapter) {
250
+ adapter = createPubSubAdapter(provider);
251
+ await adapter.connect();
252
+ this.logger.log(`Connected to ${adapter.provider} pub/sub system`);
253
+ this.adapterPool.set(provider, adapter);
254
+ }
255
+ return adapter;
256
+ }
257
+
193
258
  protected override async onHmrWorkflowChange(): Promise<void> {
194
259
  this.logger.log("[HMR] Pub/Sub workflow changed, reloading...");
195
260
  await this.waitForInFlightRequests();
@@ -264,7 +329,7 @@ export abstract class PubSubTrigger extends TriggerBase {
264
329
 
265
330
  // Store message metadata in context
266
331
  if (!ctx.vars) ctx.vars = {};
267
- ctx.vars["_pubsub_message"] = {
332
+ ctx.vars._pubsub_message = {
268
333
  topic: message.topic,
269
334
  subscription: message.subscription || "",
270
335
  publishTime: message.publishTime?.toISOString() ?? "",
@@ -281,8 +346,8 @@ export abstract class PubSubTrigger extends TriggerBase {
281
346
  span.setAttribute("success", true);
282
347
  span.setAttribute("message_id", id);
283
348
  span.setAttribute("topic", config.topic);
284
- span.setAttribute("subscription", config.subscription);
285
- span.setAttribute("provider", config.provider);
349
+ span.setAttribute("subscription", config.subscription ?? "<auto>");
350
+ span.setAttribute("provider", config.provider ?? "<default>");
286
351
  span.setAttribute("elapsed_ms", end - start);
287
352
  span.setStatus({ code: SpanStatusCode.OK });
288
353
 
@@ -290,8 +355,8 @@ export abstract class PubSubTrigger extends TriggerBase {
290
355
  pubsubMessages.add(1, {
291
356
  env: process.env.NODE_ENV,
292
357
  topic: config.topic,
293
- subscription: config.subscription,
294
- provider: config.provider,
358
+ subscription: config.subscription ?? "<auto>",
359
+ provider: config.provider ?? "<default>",
295
360
  workflow_name: this.configuration.name,
296
361
  success: "true",
297
362
  });
@@ -315,8 +380,8 @@ export abstract class PubSubTrigger extends TriggerBase {
315
380
  pubsubErrors.add(1, {
316
381
  env: process.env.NODE_ENV,
317
382
  topic: config.topic,
318
- subscription: config.subscription,
319
- provider: config.provider,
383
+ subscription: config.subscription ?? "<auto>",
384
+ provider: config.provider ?? "<default>",
320
385
  workflow_name: this.configuration?.name || "unknown",
321
386
  });
322
387