@blokjs/trigger-pubsub 0.2.3 → 0.6.2

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 -3
  5. package/dist/PubSubTrigger.js +70 -16
  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 +84 -18
  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.3",
4
- "description": "Pub/Sub-based trigger for Blok workflows - supports GCP Pub/Sub, AWS SNS/SQS, and Azure Service Bus",
3
+ "version": "0.6.2",
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.2",
18
+ "@blokjs/runner": "^0.6.2",
19
+ "@blokjs/shared": "^0.6.2",
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,7 +119,22 @@ 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>>;
@@ -143,10 +170,6 @@ export abstract class PubSubTrigger extends TriggerBase {
143
170
  this.loadWorkflows();
144
171
 
145
172
  try {
146
- // Connect to pub/sub system
147
- await this.adapter.connect();
148
- this.logger.log(`Connected to ${this.adapter.provider} pub/sub system`);
149
-
150
173
  // Find all workflows with pub/sub triggers
151
174
  const pubsubWorkflows = this.getPubSubWorkflows();
152
175
 
@@ -155,14 +178,17 @@ export abstract class PubSubTrigger extends TriggerBase {
155
178
  return this.endCounter(startTime);
156
179
  }
157
180
 
158
- // 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).
159
184
  for (const workflow of pubsubWorkflows) {
160
185
  const config = workflow.config.trigger?.pubsub as PubSubTriggerOpts;
186
+ const adapter = await this.resolveAdapterForWorkflow(config);
161
187
  this.logger.log(
162
- `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>"})`,
163
189
  );
164
190
 
165
- await this.adapter.subscribe(config, async (message) => {
191
+ await adapter.subscribe(config, async (message) => {
166
192
  await this.handleMessage(message, workflow, config);
167
193
  });
168
194
  }
@@ -182,13 +208,53 @@ export abstract class PubSubTrigger extends TriggerBase {
182
208
  }
183
209
 
184
210
  /**
185
- * 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).
186
213
  */
187
214
  async stop(): Promise<void> {
188
- 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();
189
223
  this.logger.log("Pub/Sub trigger stopped");
190
224
  }
191
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
+
192
258
  protected override async onHmrWorkflowChange(): Promise<void> {
193
259
  this.logger.log("[HMR] Pub/Sub workflow changed, reloading...");
194
260
  await this.waitForInFlightRequests();
@@ -263,7 +329,7 @@ export abstract class PubSubTrigger extends TriggerBase {
263
329
 
264
330
  // Store message metadata in context
265
331
  if (!ctx.vars) ctx.vars = {};
266
- ctx.vars["_pubsub_message"] = {
332
+ ctx.vars._pubsub_message = {
267
333
  topic: message.topic,
268
334
  subscription: message.subscription || "",
269
335
  publishTime: message.publishTime?.toISOString() ?? "",
@@ -280,8 +346,8 @@ export abstract class PubSubTrigger extends TriggerBase {
280
346
  span.setAttribute("success", true);
281
347
  span.setAttribute("message_id", id);
282
348
  span.setAttribute("topic", config.topic);
283
- span.setAttribute("subscription", config.subscription);
284
- span.setAttribute("provider", config.provider);
349
+ span.setAttribute("subscription", config.subscription ?? "<auto>");
350
+ span.setAttribute("provider", config.provider ?? "<default>");
285
351
  span.setAttribute("elapsed_ms", end - start);
286
352
  span.setStatus({ code: SpanStatusCode.OK });
287
353
 
@@ -289,8 +355,8 @@ export abstract class PubSubTrigger extends TriggerBase {
289
355
  pubsubMessages.add(1, {
290
356
  env: process.env.NODE_ENV,
291
357
  topic: config.topic,
292
- subscription: config.subscription,
293
- provider: config.provider,
358
+ subscription: config.subscription ?? "<auto>",
359
+ provider: config.provider ?? "<default>",
294
360
  workflow_name: this.configuration.name,
295
361
  success: "true",
296
362
  });
@@ -314,8 +380,8 @@ export abstract class PubSubTrigger extends TriggerBase {
314
380
  pubsubErrors.add(1, {
315
381
  env: process.env.NODE_ENV,
316
382
  topic: config.topic,
317
- subscription: config.subscription,
318
- provider: config.provider,
383
+ subscription: config.subscription ?? "<auto>",
384
+ provider: config.provider ?? "<default>",
319
385
  workflow_name: this.configuration?.name || "unknown",
320
386
  });
321
387