blokctl 0.2.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.
Files changed (169) hide show
  1. package/dist/commands/build/index.d.ts +2 -0
  2. package/dist/commands/build/index.js +210 -0
  3. package/dist/commands/config/index.d.ts +1 -0
  4. package/dist/commands/config/index.js +46 -0
  5. package/dist/commands/cost/index.d.ts +1 -0
  6. package/dist/commands/cost/index.js +74 -0
  7. package/dist/commands/create/node.d.ts +2 -0
  8. package/dist/commands/create/node.js +541 -0
  9. package/dist/commands/create/project.d.ts +2 -0
  10. package/dist/commands/create/project.js +941 -0
  11. package/dist/commands/create/utils/Examples.d.ts +39 -0
  12. package/dist/commands/create/utils/Examples.js +983 -0
  13. package/dist/commands/create/workflow.d.ts +2 -0
  14. package/dist/commands/create/workflow.js +109 -0
  15. package/dist/commands/deploy/index.d.ts +2 -0
  16. package/dist/commands/deploy/index.js +176 -0
  17. package/dist/commands/dev/index.d.ts +2 -0
  18. package/dist/commands/dev/index.js +190 -0
  19. package/dist/commands/generate/GenerationAnalytics.d.ts +61 -0
  20. package/dist/commands/generate/GenerationAnalytics.js +162 -0
  21. package/dist/commands/generate/GenerationAnalytics.test.d.ts +1 -0
  22. package/dist/commands/generate/GenerationAnalytics.test.js +407 -0
  23. package/dist/commands/generate/NodeFileWriter.d.ts +5 -0
  24. package/dist/commands/generate/NodeFileWriter.js +240 -0
  25. package/dist/commands/generate/NodeGenerator.d.ts +20 -0
  26. package/dist/commands/generate/NodeGenerator.js +181 -0
  27. package/dist/commands/generate/NodeGenerator.test.d.ts +1 -0
  28. package/dist/commands/generate/NodeGenerator.test.js +101 -0
  29. package/dist/commands/generate/PromptVersioning.d.ts +25 -0
  30. package/dist/commands/generate/PromptVersioning.js +71 -0
  31. package/dist/commands/generate/PromptVersioning.test.d.ts +1 -0
  32. package/dist/commands/generate/PromptVersioning.test.js +120 -0
  33. package/dist/commands/generate/RegisterNode.d.ts +3 -0
  34. package/dist/commands/generate/RegisterNode.js +37 -0
  35. package/dist/commands/generate/RuntimeGenerator.d.ts +40 -0
  36. package/dist/commands/generate/RuntimeGenerator.js +369 -0
  37. package/dist/commands/generate/RuntimeGenerator.test.d.ts +1 -0
  38. package/dist/commands/generate/RuntimeGenerator.test.js +553 -0
  39. package/dist/commands/generate/TriggerGenerator.d.ts +22 -0
  40. package/dist/commands/generate/TriggerGenerator.js +220 -0
  41. package/dist/commands/generate/TriggerGenerator.test.d.ts +1 -0
  42. package/dist/commands/generate/TriggerGenerator.test.js +209 -0
  43. package/dist/commands/generate/WorkflowGenerator.d.ts +20 -0
  44. package/dist/commands/generate/WorkflowGenerator.js +131 -0
  45. package/dist/commands/generate/WorkflowGenerator.test.d.ts +1 -0
  46. package/dist/commands/generate/WorkflowGenerator.test.js +77 -0
  47. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.d.ts +1 -0
  48. package/dist/commands/generate/e2e/NodeGenerator.e2e.test.js +216 -0
  49. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.d.ts +1 -0
  50. package/dist/commands/generate/e2e/RuntimeGenerator.e2e.test.js +759 -0
  51. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.d.ts +1 -0
  52. package/dist/commands/generate/e2e/TriggerGenerator.e2e.test.js +295 -0
  53. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.d.ts +1 -0
  54. package/dist/commands/generate/e2e/WorkflowGenerator.e2e.test.js +353 -0
  55. package/dist/commands/generate/index.d.ts +1 -0
  56. package/dist/commands/generate/index.js +418 -0
  57. package/dist/commands/generate/prompts/create-fn-node.system.d.ts +5 -0
  58. package/dist/commands/generate/prompts/create-fn-node.system.js +256 -0
  59. package/dist/commands/generate/prompts/create-node-manifest.system.d.ts +4 -0
  60. package/dist/commands/generate/prompts/create-node-manifest.system.js +41 -0
  61. package/dist/commands/generate/prompts/create-node.system.d.ts +5 -0
  62. package/dist/commands/generate/prompts/create-node.system.js +114 -0
  63. package/dist/commands/generate/prompts/create-readme.system.d.ts +4 -0
  64. package/dist/commands/generate/prompts/create-readme.system.js +83 -0
  65. package/dist/commands/generate/prompts/create-runtime.system.d.ts +5 -0
  66. package/dist/commands/generate/prompts/create-runtime.system.js +284 -0
  67. package/dist/commands/generate/prompts/create-trigger.system.d.ts +5 -0
  68. package/dist/commands/generate/prompts/create-trigger.system.js +293 -0
  69. package/dist/commands/generate/prompts/create-workflow.system.d.ts +5 -0
  70. package/dist/commands/generate/prompts/create-workflow.system.js +476 -0
  71. package/dist/commands/generate/prompts/register-node.system.d.ts +4 -0
  72. package/dist/commands/generate/prompts/register-node.system.js +26 -0
  73. package/dist/commands/generate/validators/CompilationValidator.d.ts +9 -0
  74. package/dist/commands/generate/validators/CompilationValidator.js +86 -0
  75. package/dist/commands/generate/validators/CompilationValidator.test.d.ts +1 -0
  76. package/dist/commands/generate/validators/CompilationValidator.test.js +161 -0
  77. package/dist/commands/generate/validators/NodeValidator.d.ts +18 -0
  78. package/dist/commands/generate/validators/NodeValidator.js +217 -0
  79. package/dist/commands/generate/validators/NodeValidator.test.d.ts +1 -0
  80. package/dist/commands/generate/validators/NodeValidator.test.js +281 -0
  81. package/dist/commands/generate/validators/WorkflowValidator.d.ts +6 -0
  82. package/dist/commands/generate/validators/WorkflowValidator.js +301 -0
  83. package/dist/commands/generate/validators/WorkflowValidator.test.d.ts +1 -0
  84. package/dist/commands/generate/validators/WorkflowValidator.test.js +647 -0
  85. package/dist/commands/generate/validators/index.d.ts +4 -0
  86. package/dist/commands/generate/validators/index.js +2 -0
  87. package/dist/commands/graph/index.d.ts +1 -0
  88. package/dist/commands/graph/index.js +69 -0
  89. package/dist/commands/install/index.d.ts +1 -0
  90. package/dist/commands/install/index.js +4 -0
  91. package/dist/commands/install/node.d.ts +4 -0
  92. package/dist/commands/install/node.js +136 -0
  93. package/dist/commands/install/workflow.d.ts +4 -0
  94. package/dist/commands/install/workflow.js +62 -0
  95. package/dist/commands/login/index.d.ts +2 -0
  96. package/dist/commands/login/index.js +77 -0
  97. package/dist/commands/logout/index.d.ts +2 -0
  98. package/dist/commands/logout/index.js +20 -0
  99. package/dist/commands/marketplace/runtime.d.ts +54 -0
  100. package/dist/commands/marketplace/runtime.js +350 -0
  101. package/dist/commands/migrate/index.d.ts +1 -0
  102. package/dist/commands/migrate/index.js +14 -0
  103. package/dist/commands/migrate/node.d.ts +2 -0
  104. package/dist/commands/migrate/node.js +110 -0
  105. package/dist/commands/monitor/index.d.ts +1 -0
  106. package/dist/commands/monitor/index.js +28 -0
  107. package/dist/commands/monitor/monitor-component.d.ts +1 -0
  108. package/dist/commands/monitor/monitor-component.js +271 -0
  109. package/dist/commands/monitor/static/index.html +2124 -0
  110. package/dist/commands/monitor/static-web-server.d.ts +1 -0
  111. package/dist/commands/monitor/static-web-server.js +89 -0
  112. package/dist/commands/profile/index.d.ts +1 -0
  113. package/dist/commands/profile/index.js +112 -0
  114. package/dist/commands/publish/index.d.ts +1 -0
  115. package/dist/commands/publish/index.js +4 -0
  116. package/dist/commands/publish/node.d.ts +4 -0
  117. package/dist/commands/publish/node.js +231 -0
  118. package/dist/commands/publish/workflow.d.ts +4 -0
  119. package/dist/commands/publish/workflow.js +165 -0
  120. package/dist/commands/search/docs.d.ts +17 -0
  121. package/dist/commands/search/docs.js +179 -0
  122. package/dist/commands/search/index.d.ts +1 -0
  123. package/dist/commands/search/index.js +5 -0
  124. package/dist/commands/search/indexer.d.ts +10 -0
  125. package/dist/commands/search/indexer.js +265 -0
  126. package/dist/commands/search/nodes.d.ts +4 -0
  127. package/dist/commands/search/nodes.js +101 -0
  128. package/dist/commands/search/workflow.d.ts +4 -0
  129. package/dist/commands/search/workflow.js +100 -0
  130. package/dist/commands/trace/index.d.ts +1 -0
  131. package/dist/commands/trace/index.js +26 -0
  132. package/dist/commands/trace/startStudio.d.ts +8 -0
  133. package/dist/commands/trace/startStudio.js +116 -0
  134. package/dist/index.d.ts +17 -0
  135. package/dist/index.js +186 -0
  136. package/dist/services/commander.d.ts +9 -0
  137. package/dist/services/commander.js +20 -0
  138. package/dist/services/constants.d.ts +1 -0
  139. package/dist/services/constants.js +3 -0
  140. package/dist/services/local-token-manager.d.ts +14 -0
  141. package/dist/services/local-token-manager.js +99 -0
  142. package/dist/services/non-interactive.d.ts +5 -0
  143. package/dist/services/non-interactive.js +30 -0
  144. package/dist/services/package-manager.d.ts +35 -0
  145. package/dist/services/package-manager.js +111 -0
  146. package/dist/services/posthog.d.ts +31 -0
  147. package/dist/services/posthog.js +159 -0
  148. package/dist/services/registry-manager.d.ts +9 -0
  149. package/dist/services/registry-manager.js +26 -0
  150. package/dist/services/runtime-detector.d.ts +23 -0
  151. package/dist/services/runtime-detector.js +181 -0
  152. package/dist/services/runtime-setup.d.ts +36 -0
  153. package/dist/services/runtime-setup.js +250 -0
  154. package/dist/services/utils.d.ts +2 -0
  155. package/dist/services/utils.js +29 -0
  156. package/dist/services/workflow-loader.d.ts +30 -0
  157. package/dist/services/workflow-loader.js +46 -0
  158. package/dist/studio-dist/assets/charts-Dso0hPUR.js +68 -0
  159. package/dist/studio-dist/assets/graph-CsV2nWGn.js +23 -0
  160. package/dist/studio-dist/assets/icons-zP8LLgPh.js +311 -0
  161. package/dist/studio-dist/assets/index-CLyEkXMx.css +1 -0
  162. package/dist/studio-dist/assets/index-CNXFX_ar.js +27 -0
  163. package/dist/studio-dist/assets/react-vendor--Eh9ivFN.js +17 -0
  164. package/dist/studio-dist/assets/tanstack-query-CiM1U6F5.js +1 -0
  165. package/dist/studio-dist/assets/tanstack-router-Btjy0MKq.js +25 -0
  166. package/dist/studio-dist/assets/tanstack-table-DhwRvuH2.js +22 -0
  167. package/dist/studio-dist/favicon.svg +5 -0
  168. package/dist/studio-dist/index.html +21 -0
  169. package/package.json +75 -0
@@ -0,0 +1,162 @@
1
+ export class GenerationAnalytics {
2
+ events = [];
3
+ static instance = null;
4
+ static getInstance() {
5
+ if (!GenerationAnalytics.instance) {
6
+ GenerationAnalytics.instance = new GenerationAnalytics();
7
+ }
8
+ return GenerationAnalytics.instance;
9
+ }
10
+ recordEvent(event) {
11
+ const fullEvent = {
12
+ ...event,
13
+ id: this.generateId(),
14
+ timestamp: new Date().toISOString(),
15
+ };
16
+ this.events.push(fullEvent);
17
+ return fullEvent;
18
+ }
19
+ startTimer() {
20
+ const start = performance.now();
21
+ return () => Math.round(performance.now() - start);
22
+ }
23
+ getStats() {
24
+ const total = this.events.length;
25
+ if (total === 0) {
26
+ return {
27
+ totalGenerations: 0,
28
+ successCount: 0,
29
+ failureCount: 0,
30
+ successRate: 0,
31
+ averageAttempts: 0,
32
+ averageDurationMs: 0,
33
+ topErrors: [],
34
+ byType: {
35
+ node: { total: 0, success: 0, failure: 0, successRate: 0, averageAttempts: 0 },
36
+ workflow: { total: 0, success: 0, failure: 0, successRate: 0, averageAttempts: 0 },
37
+ trigger: { total: 0, success: 0, failure: 0, successRate: 0, averageAttempts: 0 },
38
+ },
39
+ };
40
+ }
41
+ const successCount = this.events.filter((e) => e.success).length;
42
+ const failureCount = total - successCount;
43
+ const totalAttempts = this.events.reduce((sum, e) => sum + e.attempts, 0);
44
+ const totalDuration = this.events.reduce((sum, e) => sum + e.durationMs, 0);
45
+ return {
46
+ totalGenerations: total,
47
+ successCount,
48
+ failureCount,
49
+ successRate: Math.round((successCount / total) * 100),
50
+ averageAttempts: Math.round((totalAttempts / total) * 10) / 10,
51
+ averageDurationMs: Math.round(totalDuration / total),
52
+ topErrors: this.getTopErrors(),
53
+ byType: {
54
+ node: this.getTypeStats("node"),
55
+ workflow: this.getTypeStats("workflow"),
56
+ trigger: this.getTypeStats("trigger"),
57
+ },
58
+ };
59
+ }
60
+ getTypeStats(type) {
61
+ const typeEvents = this.events.filter((e) => e.type === type);
62
+ const total = typeEvents.length;
63
+ if (total === 0) {
64
+ return { total: 0, success: 0, failure: 0, successRate: 0, averageAttempts: 0 };
65
+ }
66
+ const success = typeEvents.filter((e) => e.success).length;
67
+ const failure = total - success;
68
+ const totalAttempts = typeEvents.reduce((sum, e) => sum + e.attempts, 0);
69
+ return {
70
+ total,
71
+ success,
72
+ failure,
73
+ successRate: Math.round((success / total) * 100),
74
+ averageAttempts: Math.round((totalAttempts / total) * 10) / 10,
75
+ };
76
+ }
77
+ getTopErrors() {
78
+ const errorCounts = new Map();
79
+ for (const event of this.events) {
80
+ for (const error of event.errors) {
81
+ const pattern = this.normalizeErrorPattern(error);
82
+ errorCounts.set(pattern, (errorCounts.get(pattern) || 0) + 1);
83
+ }
84
+ }
85
+ return Array.from(errorCounts.entries())
86
+ .map(([pattern, count]) => ({ pattern, count }))
87
+ .sort((a, b) => b.count - a.count)
88
+ .slice(0, 10);
89
+ }
90
+ normalizeErrorPattern(error) {
91
+ return error
92
+ .replace(/TS\d+/, "TS****")
93
+ .replace(/"[^"]*"/, '"..."')
94
+ .replace(/'\S+'/, "'...'")
95
+ .replace(/line \d+/, "line N")
96
+ .replace(/column \d+/, "column N")
97
+ .replace(/at index \d+/, "at index N")
98
+ .trim();
99
+ }
100
+ getEvents(filter) {
101
+ let filtered = [...this.events];
102
+ if (filter?.type) {
103
+ filtered = filtered.filter((e) => e.type === filter.type);
104
+ }
105
+ if (filter?.success !== undefined) {
106
+ filtered = filtered.filter((e) => e.success === filter.success);
107
+ }
108
+ if (filter?.since) {
109
+ filtered = filtered.filter((e) => e.timestamp >= filter.since);
110
+ }
111
+ return filtered;
112
+ }
113
+ getFirstAttemptSuccessRate() {
114
+ if (this.events.length === 0)
115
+ return 0;
116
+ const firstAttemptSuccess = this.events.filter((e) => e.success && e.attempts === 1).length;
117
+ return Math.round((firstAttemptSuccess / this.events.length) * 100);
118
+ }
119
+ getSuccessRateByPromptVersion() {
120
+ const byVersion = {};
121
+ for (const event of this.events) {
122
+ if (!byVersion[event.promptVersion]) {
123
+ byVersion[event.promptVersion] = { total: 0, success: 0 };
124
+ }
125
+ byVersion[event.promptVersion].total++;
126
+ if (event.success) {
127
+ byVersion[event.promptVersion].success++;
128
+ }
129
+ }
130
+ const result = {};
131
+ for (const [version, stats] of Object.entries(byVersion)) {
132
+ result[version] = {
133
+ ...stats,
134
+ rate: Math.round((stats.success / stats.total) * 100),
135
+ };
136
+ }
137
+ return result;
138
+ }
139
+ toJSON() {
140
+ return JSON.stringify({
141
+ events: this.events,
142
+ stats: this.getStats(),
143
+ exportedAt: new Date().toISOString(),
144
+ }, null, 2);
145
+ }
146
+ fromJSON(json) {
147
+ const data = JSON.parse(json);
148
+ if (data.events && Array.isArray(data.events)) {
149
+ this.events.push(...data.events);
150
+ }
151
+ }
152
+ clear() {
153
+ this.events = [];
154
+ }
155
+ static resetInstance() {
156
+ GenerationAnalytics.instance = null;
157
+ }
158
+ generateId() {
159
+ return `gen_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
160
+ }
161
+ }
162
+ export default GenerationAnalytics;
@@ -0,0 +1,407 @@
1
+ import { beforeEach, describe, expect, it } from "vitest";
2
+ import { GenerationAnalytics } from "./GenerationAnalytics.js";
3
+ describe("GenerationAnalytics", () => {
4
+ let analytics;
5
+ beforeEach(() => {
6
+ GenerationAnalytics.resetInstance();
7
+ analytics = GenerationAnalytics.getInstance();
8
+ analytics.clear();
9
+ });
10
+ describe("singleton pattern", () => {
11
+ it("should return the same instance", () => {
12
+ const a = GenerationAnalytics.getInstance();
13
+ const b = GenerationAnalytics.getInstance();
14
+ expect(a).toBe(b);
15
+ });
16
+ it("should create new instance after reset", () => {
17
+ const a = GenerationAnalytics.getInstance();
18
+ GenerationAnalytics.resetInstance();
19
+ const b = GenerationAnalytics.getInstance();
20
+ expect(a).not.toBe(b);
21
+ });
22
+ });
23
+ describe("recordEvent", () => {
24
+ it("should record a generation event and return it with id and timestamp", () => {
25
+ const event = analytics.recordEvent({
26
+ type: "node",
27
+ subtype: "function",
28
+ name: "fetch-user",
29
+ success: true,
30
+ attempts: 1,
31
+ durationMs: 2500,
32
+ errors: [],
33
+ promptVersion: "create-fn-node@2.0.0",
34
+ });
35
+ expect(event.id).toBeTruthy();
36
+ expect(event.id).toMatch(/^gen_/);
37
+ expect(event.timestamp).toMatch(/^\d{4}-\d{2}-\d{2}T/);
38
+ expect(event.type).toBe("node");
39
+ expect(event.success).toBe(true);
40
+ });
41
+ it("should track multiple events", () => {
42
+ analytics.recordEvent({
43
+ type: "node",
44
+ subtype: "function",
45
+ name: "node1",
46
+ success: true,
47
+ attempts: 1,
48
+ durationMs: 1000,
49
+ errors: [],
50
+ promptVersion: "v1",
51
+ });
52
+ analytics.recordEvent({
53
+ type: "workflow",
54
+ subtype: "http",
55
+ name: "wf1",
56
+ success: false,
57
+ attempts: 3,
58
+ durationMs: 5000,
59
+ errors: ["Error 1"],
60
+ promptVersion: "v1",
61
+ });
62
+ const events = analytics.getEvents();
63
+ expect(events.length).toBe(2);
64
+ });
65
+ });
66
+ describe("getStats - empty", () => {
67
+ it("should return zero stats when no events recorded", () => {
68
+ const stats = analytics.getStats();
69
+ expect(stats.totalGenerations).toBe(0);
70
+ expect(stats.successCount).toBe(0);
71
+ expect(stats.failureCount).toBe(0);
72
+ expect(stats.successRate).toBe(0);
73
+ expect(stats.averageAttempts).toBe(0);
74
+ expect(stats.averageDurationMs).toBe(0);
75
+ expect(stats.topErrors).toEqual([]);
76
+ });
77
+ });
78
+ describe("getStats - with data", () => {
79
+ beforeEach(() => {
80
+ analytics.recordEvent({
81
+ type: "node",
82
+ subtype: "function",
83
+ name: "n1",
84
+ success: true,
85
+ attempts: 1,
86
+ durationMs: 1000,
87
+ errors: [],
88
+ promptVersion: "v1",
89
+ });
90
+ analytics.recordEvent({
91
+ type: "node",
92
+ subtype: "function",
93
+ name: "n2",
94
+ success: true,
95
+ attempts: 2,
96
+ durationMs: 3000,
97
+ errors: ["Missing import"],
98
+ promptVersion: "v1",
99
+ });
100
+ analytics.recordEvent({
101
+ type: "workflow",
102
+ subtype: "http",
103
+ name: "w1",
104
+ success: true,
105
+ attempts: 1,
106
+ durationMs: 2000,
107
+ errors: [],
108
+ promptVersion: "v2",
109
+ });
110
+ analytics.recordEvent({
111
+ type: "trigger",
112
+ subtype: "queue",
113
+ name: "t1",
114
+ success: false,
115
+ attempts: 3,
116
+ durationMs: 8000,
117
+ errors: ["Missing TriggerBase", "Missing createContext"],
118
+ promptVersion: "v1",
119
+ });
120
+ });
121
+ it("should calculate correct totals", () => {
122
+ const stats = analytics.getStats();
123
+ expect(stats.totalGenerations).toBe(4);
124
+ expect(stats.successCount).toBe(3);
125
+ expect(stats.failureCount).toBe(1);
126
+ });
127
+ it("should calculate correct success rate", () => {
128
+ const stats = analytics.getStats();
129
+ expect(stats.successRate).toBe(75);
130
+ });
131
+ it("should calculate correct average attempts", () => {
132
+ const stats = analytics.getStats();
133
+ expect(stats.averageAttempts).toBe(1.8);
134
+ });
135
+ it("should calculate correct average duration", () => {
136
+ const stats = analytics.getStats();
137
+ expect(stats.averageDurationMs).toBe(3500);
138
+ });
139
+ it("should provide per-type breakdown", () => {
140
+ const stats = analytics.getStats();
141
+ expect(stats.byType.node.total).toBe(2);
142
+ expect(stats.byType.node.success).toBe(2);
143
+ expect(stats.byType.node.successRate).toBe(100);
144
+ expect(stats.byType.workflow.total).toBe(1);
145
+ expect(stats.byType.workflow.success).toBe(1);
146
+ expect(stats.byType.workflow.successRate).toBe(100);
147
+ expect(stats.byType.trigger.total).toBe(1);
148
+ expect(stats.byType.trigger.success).toBe(0);
149
+ expect(stats.byType.trigger.successRate).toBe(0);
150
+ });
151
+ it("should track top error patterns", () => {
152
+ const stats = analytics.getStats();
153
+ expect(stats.topErrors.length).toBeGreaterThan(0);
154
+ for (let i = 1; i < stats.topErrors.length; i++) {
155
+ expect(stats.topErrors[i].count).toBeLessThanOrEqual(stats.topErrors[i - 1].count);
156
+ }
157
+ });
158
+ });
159
+ describe("getEvents - filtering", () => {
160
+ beforeEach(() => {
161
+ analytics.recordEvent({
162
+ type: "node",
163
+ subtype: "function",
164
+ name: "n1",
165
+ success: true,
166
+ attempts: 1,
167
+ durationMs: 1000,
168
+ errors: [],
169
+ promptVersion: "v1",
170
+ });
171
+ analytics.recordEvent({
172
+ type: "workflow",
173
+ subtype: "http",
174
+ name: "w1",
175
+ success: false,
176
+ attempts: 3,
177
+ durationMs: 5000,
178
+ errors: ["Error"],
179
+ promptVersion: "v1",
180
+ });
181
+ analytics.recordEvent({
182
+ type: "trigger",
183
+ subtype: "queue",
184
+ name: "t1",
185
+ success: true,
186
+ attempts: 2,
187
+ durationMs: 3000,
188
+ errors: ["Retried"],
189
+ promptVersion: "v2",
190
+ });
191
+ });
192
+ it("should filter by type", () => {
193
+ const nodes = analytics.getEvents({ type: "node" });
194
+ expect(nodes.length).toBe(1);
195
+ expect(nodes[0].name).toBe("n1");
196
+ });
197
+ it("should filter by success", () => {
198
+ const successes = analytics.getEvents({ success: true });
199
+ expect(successes.length).toBe(2);
200
+ const failures = analytics.getEvents({ success: false });
201
+ expect(failures.length).toBe(1);
202
+ });
203
+ it("should filter by type and success combined", () => {
204
+ const failedWorkflows = analytics.getEvents({ type: "workflow", success: false });
205
+ expect(failedWorkflows.length).toBe(1);
206
+ expect(failedWorkflows[0].name).toBe("w1");
207
+ });
208
+ it("should return all events when no filter", () => {
209
+ const all = analytics.getEvents();
210
+ expect(all.length).toBe(3);
211
+ });
212
+ });
213
+ describe("getFirstAttemptSuccessRate", () => {
214
+ it("should return 0 when no events", () => {
215
+ expect(analytics.getFirstAttemptSuccessRate()).toBe(0);
216
+ });
217
+ it("should calculate first-attempt success rate correctly", () => {
218
+ analytics.recordEvent({
219
+ type: "node",
220
+ subtype: "function",
221
+ name: "n1",
222
+ success: true,
223
+ attempts: 1,
224
+ durationMs: 1000,
225
+ errors: [],
226
+ promptVersion: "v1",
227
+ });
228
+ analytics.recordEvent({
229
+ type: "node",
230
+ subtype: "function",
231
+ name: "n2",
232
+ success: true,
233
+ attempts: 2,
234
+ durationMs: 3000,
235
+ errors: [],
236
+ promptVersion: "v1",
237
+ });
238
+ analytics.recordEvent({
239
+ type: "node",
240
+ subtype: "function",
241
+ name: "n3",
242
+ success: true,
243
+ attempts: 1,
244
+ durationMs: 1000,
245
+ errors: [],
246
+ promptVersion: "v1",
247
+ });
248
+ analytics.recordEvent({
249
+ type: "node",
250
+ subtype: "function",
251
+ name: "n4",
252
+ success: false,
253
+ attempts: 3,
254
+ durationMs: 8000,
255
+ errors: ["Error"],
256
+ promptVersion: "v1",
257
+ });
258
+ expect(analytics.getFirstAttemptSuccessRate()).toBe(50);
259
+ });
260
+ });
261
+ describe("getSuccessRateByPromptVersion", () => {
262
+ it("should break down success rate by prompt version", () => {
263
+ analytics.recordEvent({
264
+ type: "node",
265
+ subtype: "function",
266
+ name: "n1",
267
+ success: true,
268
+ attempts: 1,
269
+ durationMs: 1000,
270
+ errors: [],
271
+ promptVersion: "create-fn-node@1.0.0",
272
+ });
273
+ analytics.recordEvent({
274
+ type: "node",
275
+ subtype: "function",
276
+ name: "n2",
277
+ success: false,
278
+ attempts: 3,
279
+ durationMs: 5000,
280
+ errors: ["Error"],
281
+ promptVersion: "create-fn-node@1.0.0",
282
+ });
283
+ analytics.recordEvent({
284
+ type: "node",
285
+ subtype: "function",
286
+ name: "n3",
287
+ success: true,
288
+ attempts: 1,
289
+ durationMs: 1000,
290
+ errors: [],
291
+ promptVersion: "create-fn-node@2.0.0",
292
+ });
293
+ analytics.recordEvent({
294
+ type: "node",
295
+ subtype: "function",
296
+ name: "n4",
297
+ success: true,
298
+ attempts: 2,
299
+ durationMs: 3000,
300
+ errors: [],
301
+ promptVersion: "create-fn-node@2.0.0",
302
+ });
303
+ const rates = analytics.getSuccessRateByPromptVersion();
304
+ expect(rates["create-fn-node@1.0.0"]).toBeDefined();
305
+ expect(rates["create-fn-node@1.0.0"].total).toBe(2);
306
+ expect(rates["create-fn-node@1.0.0"].rate).toBe(50);
307
+ expect(rates["create-fn-node@2.0.0"]).toBeDefined();
308
+ expect(rates["create-fn-node@2.0.0"].total).toBe(2);
309
+ expect(rates["create-fn-node@2.0.0"].rate).toBe(100);
310
+ });
311
+ });
312
+ describe("serialization", () => {
313
+ it("should serialize to JSON", () => {
314
+ analytics.recordEvent({
315
+ type: "node",
316
+ subtype: "function",
317
+ name: "n1",
318
+ success: true,
319
+ attempts: 1,
320
+ durationMs: 1000,
321
+ errors: [],
322
+ promptVersion: "v1",
323
+ });
324
+ const json = analytics.toJSON();
325
+ const parsed = JSON.parse(json);
326
+ expect(parsed.events).toBeDefined();
327
+ expect(parsed.events.length).toBe(1);
328
+ expect(parsed.stats).toBeDefined();
329
+ expect(parsed.exportedAt).toBeTruthy();
330
+ });
331
+ it("should import from JSON", () => {
332
+ const exportData = {
333
+ events: [
334
+ {
335
+ id: "gen_imported_1",
336
+ timestamp: "2026-01-28T00:00:00Z",
337
+ type: "node",
338
+ subtype: "function",
339
+ name: "imported-node",
340
+ success: true,
341
+ attempts: 1,
342
+ durationMs: 1000,
343
+ errors: [],
344
+ promptVersion: "v1",
345
+ },
346
+ ],
347
+ };
348
+ analytics.fromJSON(JSON.stringify(exportData));
349
+ const events = analytics.getEvents();
350
+ expect(events.length).toBe(1);
351
+ expect(events[0].name).toBe("imported-node");
352
+ });
353
+ });
354
+ describe("startTimer", () => {
355
+ it("should measure duration", async () => {
356
+ const getElapsed = analytics.startTimer();
357
+ await new Promise((resolve) => setTimeout(resolve, 50));
358
+ const elapsed = getElapsed();
359
+ expect(elapsed).toBeGreaterThanOrEqual(40);
360
+ expect(elapsed).toBeLessThan(200);
361
+ });
362
+ });
363
+ describe("error pattern normalization", () => {
364
+ it("should normalize TypeScript error codes in top errors", () => {
365
+ analytics.recordEvent({
366
+ type: "node",
367
+ subtype: "function",
368
+ name: "n1",
369
+ success: false,
370
+ attempts: 3,
371
+ durationMs: 5000,
372
+ errors: ['TS2304: Cannot find name "defineNode"', "TS2307: Cannot find module '@blok/runner'"],
373
+ promptVersion: "v1",
374
+ });
375
+ analytics.recordEvent({
376
+ type: "node",
377
+ subtype: "function",
378
+ name: "n2",
379
+ success: false,
380
+ attempts: 3,
381
+ durationMs: 5000,
382
+ errors: ['TS2304: Cannot find name "z"'],
383
+ promptVersion: "v1",
384
+ });
385
+ const stats = analytics.getStats();
386
+ const ts2304 = stats.topErrors.filter((e) => e.pattern.includes("TS****"));
387
+ expect(ts2304.length).toBeGreaterThan(0);
388
+ });
389
+ });
390
+ describe("clear", () => {
391
+ it("should clear all events", () => {
392
+ analytics.recordEvent({
393
+ type: "node",
394
+ subtype: "function",
395
+ name: "n1",
396
+ success: true,
397
+ attempts: 1,
398
+ durationMs: 1000,
399
+ errors: [],
400
+ promptVersion: "v1",
401
+ });
402
+ analytics.clear();
403
+ const events = analytics.getEvents();
404
+ expect(events.length).toBe(0);
405
+ });
406
+ });
407
+ });
@@ -0,0 +1,5 @@
1
+ export default class NodeFileWriter {
2
+ nodeDependencies: string[];
3
+ typesDependencies: string[];
4
+ generateFile(nodeName: string, nodeType: string, fileContent: string, apiKey: string, nodeStyle?: string): Promise<string>;
5
+ }