business-as-code 2.1.3 → 2.4.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 (235) hide show
  1. package/.turbo/turbo-build.log +4 -5
  2. package/CHANGELOG.md +53 -0
  3. package/README.md +2 -0
  4. package/dist/dollar.d.ts.map +1 -1
  5. package/dist/dollar.js +2 -2
  6. package/dist/dollar.js.map +1 -1
  7. package/dist/entities/organization.d.ts +4 -0
  8. package/dist/entities/organization.d.ts.map +1 -1
  9. package/dist/entities/organization.js +27 -18
  10. package/dist/entities/organization.js.map +1 -1
  11. package/dist/entities/planning.d.ts +87 -0
  12. package/dist/finance/account.d.ts +44 -0
  13. package/dist/finance/account.d.ts.map +1 -0
  14. package/dist/finance/account.js +6 -0
  15. package/dist/finance/account.js.map +1 -0
  16. package/dist/finance/authority.d.ts +78 -0
  17. package/dist/finance/authority.d.ts.map +1 -0
  18. package/dist/finance/authority.js +27 -0
  19. package/dist/finance/authority.js.map +1 -0
  20. package/dist/finance/card.d.ts +36 -0
  21. package/dist/finance/card.d.ts.map +1 -0
  22. package/dist/finance/card.js +6 -0
  23. package/dist/finance/card.js.map +1 -0
  24. package/dist/finance/identity.d.ts +30 -0
  25. package/dist/finance/identity.d.ts.map +1 -0
  26. package/dist/finance/identity.js +8 -0
  27. package/dist/finance/identity.js.map +1 -0
  28. package/dist/finance/index.d.ts +36 -0
  29. package/dist/finance/index.d.ts.map +1 -0
  30. package/dist/finance/index.js +22 -0
  31. package/dist/finance/index.js.map +1 -0
  32. package/dist/finance/ledger.d.ts +24 -0
  33. package/dist/finance/ledger.d.ts.map +1 -0
  34. package/dist/finance/ledger.js +8 -0
  35. package/dist/finance/ledger.js.map +1 -0
  36. package/dist/finance/merchant.d.ts +129 -0
  37. package/dist/finance/merchant.d.ts.map +1 -0
  38. package/dist/finance/merchant.js +21 -0
  39. package/dist/finance/merchant.js.map +1 -0
  40. package/dist/finance/outcome-contract.d.ts +139 -0
  41. package/dist/finance/outcome-contract.d.ts.map +1 -0
  42. package/dist/finance/outcome-contract.js +27 -0
  43. package/dist/finance/outcome-contract.js.map +1 -0
  44. package/dist/finance/port.d.ts +121 -0
  45. package/dist/finance/port.d.ts.map +1 -0
  46. package/dist/finance/port.js +10 -0
  47. package/dist/finance/port.js.map +1 -0
  48. package/dist/finance/pricing.d.ts +154 -0
  49. package/dist/finance/pricing.d.ts.map +1 -0
  50. package/dist/finance/pricing.js +79 -0
  51. package/dist/finance/pricing.js.map +1 -0
  52. package/dist/finance/proof-predicate.d.ts +92 -0
  53. package/dist/finance/proof-predicate.d.ts.map +1 -0
  54. package/dist/finance/proof-predicate.js +80 -0
  55. package/dist/finance/proof-predicate.js.map +1 -0
  56. package/dist/finance/refund.d.ts +44 -0
  57. package/dist/finance/refund.d.ts.map +1 -0
  58. package/dist/finance/refund.js +41 -0
  59. package/dist/finance/refund.js.map +1 -0
  60. package/dist/finance/sla.d.ts +25 -0
  61. package/dist/finance/sla.d.ts.map +1 -0
  62. package/dist/finance/sla.js +7 -0
  63. package/dist/finance/sla.js.map +1 -0
  64. package/dist/finance/types.d.ts +79 -0
  65. package/dist/finance/types.d.ts.map +1 -0
  66. package/dist/finance/types.js +8 -0
  67. package/dist/{canvas → finance}/types.js.map +1 -1
  68. package/dist/goals.d.ts +19 -0
  69. package/dist/goals.d.ts.map +1 -1
  70. package/dist/goals.js +81 -12
  71. package/dist/goals.js.map +1 -1
  72. package/dist/index.d.ts +12 -8
  73. package/dist/index.d.ts.map +1 -1
  74. package/dist/index.js +19 -7
  75. package/dist/index.js.map +1 -1
  76. package/dist/kpis.d.ts +19 -0
  77. package/dist/kpis.d.ts.map +1 -1
  78. package/dist/kpis.js +71 -6
  79. package/dist/kpis.js.map +1 -1
  80. package/dist/metrics.d.ts.map +1 -1
  81. package/dist/metrics.js +29 -24
  82. package/dist/metrics.js.map +1 -1
  83. package/dist/okrs.d.ts +34 -0
  84. package/dist/okrs.d.ts.map +1 -1
  85. package/dist/okrs.js +135 -13
  86. package/dist/okrs.js.map +1 -1
  87. package/dist/organization.d.ts.map +1 -1
  88. package/dist/organization.js +11 -11
  89. package/dist/organization.js.map +1 -1
  90. package/dist/process.d.ts.map +1 -1
  91. package/dist/process.js +13 -12
  92. package/dist/process.js.map +1 -1
  93. package/dist/product.d.ts.map +1 -1
  94. package/dist/product.js +9 -9
  95. package/dist/product.js.map +1 -1
  96. package/dist/queries.d.ts.map +1 -1
  97. package/dist/queries.js +194 -32
  98. package/dist/queries.js.map +1 -1
  99. package/dist/roles.d.ts +25 -31
  100. package/dist/roles.d.ts.map +1 -1
  101. package/dist/roles.js +37 -10
  102. package/dist/roles.js.map +1 -1
  103. package/dist/workflow.d.ts.map +1 -1
  104. package/dist/workflow.js +13 -12
  105. package/dist/workflow.js.map +1 -1
  106. package/package.json +20 -13
  107. package/src/dollar.ts +5 -2
  108. package/src/entities/organization.ts +31 -18
  109. package/src/finance/account.ts +48 -0
  110. package/src/finance/authority.ts +42 -0
  111. package/src/finance/card.ts +38 -0
  112. package/src/finance/identity.ts +31 -0
  113. package/src/finance/index.ts +117 -0
  114. package/src/finance/ledger.ts +26 -0
  115. package/src/finance/merchant.ts +127 -0
  116. package/src/finance/outcome-contract.ts +157 -0
  117. package/src/finance/port.ts +144 -0
  118. package/src/finance/pricing.ts +197 -0
  119. package/src/finance/proof-predicate.ts +106 -0
  120. package/src/finance/refund.ts +52 -0
  121. package/src/finance/sla.ts +33 -0
  122. package/src/finance/types.ts +75 -0
  123. package/src/goals.ts +78 -12
  124. package/src/index.ts +48 -18
  125. package/src/kpis.ts +62 -8
  126. package/src/metrics.ts +92 -79
  127. package/src/okrs.ts +120 -20
  128. package/src/organization.ts +12 -15
  129. package/src/process.ts +11 -12
  130. package/src/product.ts +8 -9
  131. package/src/queries.ts +238 -75
  132. package/src/roles.ts +62 -61
  133. package/src/workflow.ts +22 -15
  134. package/test/business.test.ts +282 -0
  135. package/test/dollar.test.ts +270 -0
  136. package/test/entities.test.ts +628 -0
  137. package/test/financials.test.ts +539 -0
  138. package/test/goals.test.ts +451 -0
  139. package/{src → test}/index.test.ts +1 -1
  140. package/test/kpis.test.ts +440 -0
  141. package/test/metrics.test.ts +744 -0
  142. package/test/okrs.test.ts +741 -0
  143. package/test/organization.test.ts +548 -0
  144. package/test/process.test.ts +503 -0
  145. package/test/product.test.ts +430 -0
  146. package/test/queries.test.ts +556 -0
  147. package/test/roles.test.ts +546 -0
  148. package/test/service.test.ts +450 -0
  149. package/test/types.test.ts +1141 -0
  150. package/test/vision.test.ts +214 -0
  151. package/test/workflow.test.ts +501 -0
  152. package/vitest.config.ts +47 -0
  153. package/LICENSE +0 -21
  154. package/dist/canvas/activities.d.ts +0 -19
  155. package/dist/canvas/activities.d.ts.map +0 -1
  156. package/dist/canvas/activities.js +0 -20
  157. package/dist/canvas/activities.js.map +0 -1
  158. package/dist/canvas/channels.d.ts +0 -20
  159. package/dist/canvas/channels.d.ts.map +0 -1
  160. package/dist/canvas/channels.js +0 -21
  161. package/dist/canvas/channels.js.map +0 -1
  162. package/dist/canvas/relationships.d.ts +0 -20
  163. package/dist/canvas/relationships.d.ts.map +0 -1
  164. package/dist/canvas/relationships.js +0 -21
  165. package/dist/canvas/relationships.js.map +0 -1
  166. package/dist/canvas/resources.d.ts +0 -20
  167. package/dist/canvas/resources.d.ts.map +0 -1
  168. package/dist/canvas/resources.js +0 -30
  169. package/dist/canvas/resources.js.map +0 -1
  170. package/dist/canvas/revenue.d.ts +0 -22
  171. package/dist/canvas/revenue.d.ts.map +0 -1
  172. package/dist/canvas/revenue.js +0 -30
  173. package/dist/canvas/revenue.js.map +0 -1
  174. package/dist/canvas/segments.d.ts +0 -20
  175. package/dist/canvas/segments.d.ts.map +0 -1
  176. package/dist/canvas/segments.js +0 -28
  177. package/dist/canvas/segments.js.map +0 -1
  178. package/dist/canvas/types.d.ts +0 -232
  179. package/dist/canvas/types.d.ts.map +0 -1
  180. package/dist/canvas/types.js +0 -8
  181. package/dist/canvas/value.d.ts +0 -20
  182. package/dist/canvas/value.d.ts.map +0 -1
  183. package/dist/canvas/value.js +0 -21
  184. package/dist/canvas/value.js.map +0 -1
  185. package/src/business.js +0 -108
  186. package/src/canvas/activities.ts +0 -32
  187. package/src/canvas/canvas.ts +0 -482
  188. package/src/canvas/channels.ts +0 -34
  189. package/src/canvas/costs.ts +0 -43
  190. package/src/canvas/economics.ts +0 -99
  191. package/src/canvas/index.ts +0 -206
  192. package/src/canvas/partnerships.ts +0 -34
  193. package/src/canvas/projections.ts +0 -141
  194. package/src/canvas/relationships.ts +0 -34
  195. package/src/canvas/resources.ts +0 -43
  196. package/src/canvas/revenue.ts +0 -56
  197. package/src/canvas/segments.ts +0 -42
  198. package/src/canvas/types.ts +0 -363
  199. package/src/canvas/value.ts +0 -34
  200. package/src/dollar.js +0 -106
  201. package/src/entities/assets.js +0 -322
  202. package/src/entities/business.js +0 -369
  203. package/src/entities/communication.js +0 -254
  204. package/src/entities/customers.js +0 -988
  205. package/src/entities/financials.js +0 -931
  206. package/src/entities/goals.js +0 -799
  207. package/src/entities/index.js +0 -197
  208. package/src/entities/legal.js +0 -300
  209. package/src/entities/market.js +0 -300
  210. package/src/entities/marketing.js +0 -1156
  211. package/src/entities/offerings.js +0 -726
  212. package/src/entities/operations.js +0 -786
  213. package/src/entities/organization.js +0 -806
  214. package/src/entities/partnerships.js +0 -299
  215. package/src/entities/planning.js +0 -270
  216. package/src/entities/projects.js +0 -348
  217. package/src/entities/risk.js +0 -292
  218. package/src/entities/sales.js +0 -1247
  219. package/src/financials.js +0 -296
  220. package/src/goals.js +0 -214
  221. package/src/index.js +0 -131
  222. package/src/index.test.js +0 -274
  223. package/src/kpis.js +0 -231
  224. package/src/metrics.js +0 -324
  225. package/src/okrs.js +0 -268
  226. package/src/organization.js +0 -172
  227. package/src/process.js +0 -240
  228. package/src/product.js +0 -144
  229. package/src/queries.js +0 -414
  230. package/src/roles.js +0 -254
  231. package/src/service.js +0 -139
  232. package/src/types.js +0 -4
  233. package/src/vision.js +0 -67
  234. package/src/workflow.js +0 -246
  235. package/tests/canvas.test.ts +0 -842
@@ -0,0 +1,556 @@
1
+ /**
2
+ * Tests for queries.ts - Live Queries & Views for ai-database
3
+ */
4
+
5
+ import { describe, it, expect } from 'vitest'
6
+ import {
7
+ // Types
8
+ StandardDimensions,
9
+ StandardMeasures,
10
+ CalculatedMetrics,
11
+
12
+ // Builders
13
+ query,
14
+ QueryBuilder,
15
+ view,
16
+ ViewBuilder,
17
+ dashboard,
18
+ DashboardBuilder,
19
+
20
+ // Pre-built queries
21
+ MrrOverview,
22
+ ArrBySegment,
23
+ CohortRetention,
24
+ UnitEconomics,
25
+ RevenueByChannel,
26
+ GrowthMetrics,
27
+
28
+ // Pre-built dashboards
29
+ ExecutiveDashboard,
30
+ } from '../src/queries.js'
31
+
32
+ describe('StandardDimensions', () => {
33
+ it('should have time dimensions', () => {
34
+ expect(StandardDimensions.date).toBeDefined()
35
+ expect(StandardDimensions.date.name).toBe('date')
36
+ expect(StandardDimensions.date.type).toBe('date')
37
+
38
+ expect(StandardDimensions.month).toBeDefined()
39
+ expect(StandardDimensions.month.granularity).toBe('month')
40
+
41
+ expect(StandardDimensions.quarter).toBeDefined()
42
+ expect(StandardDimensions.quarter.granularity).toBe('quarter')
43
+
44
+ expect(StandardDimensions.year).toBeDefined()
45
+ expect(StandardDimensions.year.granularity).toBe('year')
46
+ })
47
+
48
+ it('should have customer dimensions', () => {
49
+ expect(StandardDimensions.customerId).toBeDefined()
50
+ expect(StandardDimensions.customerSegment).toBeDefined()
51
+ expect(StandardDimensions.plan).toBeDefined()
52
+ expect(StandardDimensions.cohort).toBeDefined()
53
+ })
54
+
55
+ it('should have product dimensions', () => {
56
+ expect(StandardDimensions.productId).toBeDefined()
57
+ expect(StandardDimensions.productName).toBeDefined()
58
+ expect(StandardDimensions.feature).toBeDefined()
59
+ })
60
+
61
+ it('should have geography dimensions', () => {
62
+ expect(StandardDimensions.country).toBeDefined()
63
+ expect(StandardDimensions.region).toBeDefined()
64
+ })
65
+
66
+ it('should have channel dimensions', () => {
67
+ expect(StandardDimensions.channel).toBeDefined()
68
+ expect(StandardDimensions.source).toBeDefined()
69
+ expect(StandardDimensions.campaign).toBeDefined()
70
+ })
71
+ })
72
+
73
+ describe('StandardMeasures', () => {
74
+ it('should have revenue measures', () => {
75
+ expect(StandardMeasures.revenue).toBeDefined()
76
+ expect(StandardMeasures.revenue.aggregate).toBe('sum')
77
+ expect(StandardMeasures.revenue.type).toBe('currency')
78
+
79
+ expect(StandardMeasures.mrr).toBeDefined()
80
+ expect(StandardMeasures.newMrr).toBeDefined()
81
+ expect(StandardMeasures.expansionMrr).toBeDefined()
82
+ expect(StandardMeasures.contractionMrr).toBeDefined()
83
+ expect(StandardMeasures.churnedMrr).toBeDefined()
84
+ })
85
+
86
+ it('should have customer measures', () => {
87
+ expect(StandardMeasures.customers).toBeDefined()
88
+ expect(StandardMeasures.customers.aggregate).toBe('countDistinct')
89
+
90
+ expect(StandardMeasures.newCustomers).toBeDefined()
91
+ expect(StandardMeasures.churnedCustomers).toBeDefined()
92
+ })
93
+
94
+ it('should have usage measures', () => {
95
+ expect(StandardMeasures.events).toBeDefined()
96
+ expect(StandardMeasures.events.aggregate).toBe('count')
97
+
98
+ expect(StandardMeasures.sessions).toBeDefined()
99
+ expect(StandardMeasures.activeUsers).toBeDefined()
100
+ })
101
+
102
+ it('should have cost measures', () => {
103
+ expect(StandardMeasures.cogs).toBeDefined()
104
+ expect(StandardMeasures.salesSpend).toBeDefined()
105
+ expect(StandardMeasures.marketingSpend).toBeDefined()
106
+ })
107
+ })
108
+
109
+ describe('CalculatedMetrics', () => {
110
+ it('should have revenue calculated metrics', () => {
111
+ expect(CalculatedMetrics.arr).toBeDefined()
112
+ expect(CalculatedMetrics.arr.expression).toBe('mrr * 12')
113
+ expect(CalculatedMetrics.arr.measures).toContain('mrr')
114
+
115
+ expect(CalculatedMetrics.netNewMrr).toBeDefined()
116
+ expect(CalculatedMetrics.arpu).toBeDefined()
117
+ })
118
+
119
+ it('should have margin calculated metrics', () => {
120
+ expect(CalculatedMetrics.grossProfit).toBeDefined()
121
+ expect(CalculatedMetrics.grossMargin).toBeDefined()
122
+ expect(CalculatedMetrics.grossMargin.type).toBe('percent')
123
+ })
124
+
125
+ it('should have efficiency calculated metrics', () => {
126
+ expect(CalculatedMetrics.cac).toBeDefined()
127
+ expect(CalculatedMetrics.ltv).toBeDefined()
128
+ expect(CalculatedMetrics.ltvCacRatio).toBeDefined()
129
+ })
130
+
131
+ it('should have churn calculated metrics', () => {
132
+ expect(CalculatedMetrics.customerChurnRate).toBeDefined()
133
+ expect(CalculatedMetrics.revenueChurnRate).toBeDefined()
134
+ expect(CalculatedMetrics.nrr).toBeDefined()
135
+ })
136
+
137
+ it('should have growth calculated metrics', () => {
138
+ expect(CalculatedMetrics.quickRatio).toBeDefined()
139
+ expect(CalculatedMetrics.magicNumber).toBeDefined()
140
+ })
141
+ })
142
+
143
+ describe('QueryBuilder', () => {
144
+ describe('query()', () => {
145
+ it('should create a basic query', () => {
146
+ const q = query('test_query', 'test_table').build()
147
+
148
+ expect(q.name).toBe('test_query')
149
+ expect(q.source).toBe('test_table')
150
+ })
151
+
152
+ it('should set description', () => {
153
+ const q = query('test', 'table').describe('A test query').build()
154
+
155
+ expect(q.description).toBe('A test query')
156
+ })
157
+
158
+ it('should set dimensions', () => {
159
+ const q = query('test', 'table').dimensions('month', 'segment').build()
160
+
161
+ expect(q.dimensions).toEqual(['month', 'segment'])
162
+ })
163
+
164
+ it('should set measures', () => {
165
+ const q = query('test', 'table').measures('mrr', 'customers').build()
166
+
167
+ expect(q.measures).toEqual(['mrr', 'customers'])
168
+ })
169
+
170
+ it('should add filters', () => {
171
+ const q = query('test', 'table')
172
+ .filter('status', 'eq', 'active')
173
+ .filter('revenue', 'gt', 1000)
174
+ .build()
175
+
176
+ expect(q.filters).toHaveLength(2)
177
+ expect(q.filters![0]).toEqual({ field: 'status', operator: 'eq', value: 'active' })
178
+ expect(q.filters![1]).toEqual({ field: 'revenue', operator: 'gt', value: 1000 })
179
+ })
180
+
181
+ it('should set where filters', () => {
182
+ const filters = [
183
+ { field: 'status', operator: 'eq' as const, value: 'active' },
184
+ { field: 'revenue', operator: 'gt' as const, value: 1000 },
185
+ ]
186
+ const q = query('test', 'table').where(filters).build()
187
+
188
+ expect(q.filters).toEqual(filters)
189
+ })
190
+
191
+ it('should set time range', () => {
192
+ const start = new Date('2024-01-01')
193
+ const end = new Date('2024-12-31')
194
+ const q = query('test', 'table').timeRange('created_at', start, end, 'month').build()
195
+
196
+ expect(q.timeRange).toEqual({
197
+ field: 'created_at',
198
+ start,
199
+ end,
200
+ granularity: 'month',
201
+ })
202
+ })
203
+
204
+ it('should set last duration', () => {
205
+ const q = query('test', 'table').last('30d').build()
206
+
207
+ expect(q.timeRange).toEqual({
208
+ field: 'date',
209
+ start: '-30d',
210
+ })
211
+ })
212
+
213
+ it('should set last duration with custom field', () => {
214
+ const q = query('test', 'table').last('7d', 'created_at').build()
215
+
216
+ expect(q.timeRange?.field).toBe('created_at')
217
+ expect(q.timeRange?.start).toBe('-7d')
218
+ })
219
+
220
+ it('should add sort', () => {
221
+ const q = query('test', 'table').sort('revenue', 'desc').sort('date', 'asc').build()
222
+
223
+ expect(q.sort).toHaveLength(2)
224
+ expect(q.sort![0]).toEqual({ field: 'revenue', direction: 'desc' })
225
+ expect(q.sort![1]).toEqual({ field: 'date', direction: 'asc' })
226
+ })
227
+
228
+ it('should use desc as default sort direction', () => {
229
+ const q = query('test', 'table').sort('revenue').build()
230
+
231
+ expect(q.sort![0]?.direction).toBe('desc')
232
+ })
233
+
234
+ it('should set limit', () => {
235
+ const q = query('test', 'table').limit(100).build()
236
+
237
+ expect(q.limit).toBe(100)
238
+ })
239
+
240
+ it('should set offset', () => {
241
+ const q = query('test', 'table').offset(50).build()
242
+
243
+ expect(q.offset).toBe(50)
244
+ })
245
+
246
+ it('should set tags', () => {
247
+ const q = query('test', 'table').tags('saas', 'revenue', 'metrics').build()
248
+
249
+ expect(q.tags).toEqual(['saas', 'revenue', 'metrics'])
250
+ })
251
+
252
+ it('should set owner', () => {
253
+ const q = query('test', 'table').owner('finance-team').build()
254
+
255
+ expect(q.owner).toBe('finance-team')
256
+ })
257
+
258
+ it('should chain all builder methods', () => {
259
+ const q = query('full_query', 'revenue_events')
260
+ .describe('Full query example')
261
+ .dimensions('month', 'segment')
262
+ .measures('mrr', 'customers')
263
+ .filter('status', 'eq', 'active')
264
+ .last('12m')
265
+ .sort('mrr', 'desc')
266
+ .limit(100)
267
+ .offset(0)
268
+ .tags('test')
269
+ .owner('team')
270
+ .build()
271
+
272
+ expect(q.name).toBe('full_query')
273
+ expect(q.source).toBe('revenue_events')
274
+ expect(q.description).toBe('Full query example')
275
+ expect(q.dimensions).toEqual(['month', 'segment'])
276
+ expect(q.measures).toEqual(['mrr', 'customers'])
277
+ expect(q.filters).toHaveLength(1)
278
+ expect(q.timeRange?.start).toBe('-12m')
279
+ expect(q.sort).toHaveLength(1)
280
+ expect(q.limit).toBe(100)
281
+ expect(q.offset).toBe(0)
282
+ expect(q.tags).toEqual(['test'])
283
+ expect(q.owner).toBe('team')
284
+ })
285
+ })
286
+ })
287
+
288
+ describe('ViewBuilder', () => {
289
+ const testQuery = query('test', 'table').build()
290
+
291
+ describe('view()', () => {
292
+ it('should create a basic view', () => {
293
+ const v = view('test_view', testQuery).build()
294
+
295
+ expect(v.name).toBe('test_view')
296
+ expect(v.query).toEqual(testQuery)
297
+ })
298
+
299
+ it('should set description', () => {
300
+ const v = view('test', testQuery).describe('A test view').build()
301
+
302
+ expect(v.description).toBe('A test view')
303
+ })
304
+
305
+ it('should set materialization', () => {
306
+ const v = view('test', testQuery).materialize('5m', '30d').build()
307
+
308
+ expect(v.materialized).toBe(true)
309
+ expect(v.refreshInterval).toBe('5m')
310
+ expect(v.retention).toBe('30d')
311
+ })
312
+
313
+ it('should set public flag', () => {
314
+ const v = view('test', testQuery).public().build()
315
+
316
+ expect(v.public).toBe(true)
317
+ })
318
+
319
+ it('should set owner', () => {
320
+ const v = view('test', testQuery).owner('data-team').build()
321
+
322
+ expect(v.owner).toBe('data-team')
323
+ })
324
+
325
+ it('should set tags', () => {
326
+ const v = view('test', testQuery).tags('financial', 'metrics').build()
327
+
328
+ expect(v.tags).toEqual(['financial', 'metrics'])
329
+ })
330
+
331
+ it('should chain all builder methods', () => {
332
+ const v = view('full_view', testQuery)
333
+ .describe('Full view example')
334
+ .materialize('1h', '7d')
335
+ .public()
336
+ .owner('analytics')
337
+ .tags('test', 'example')
338
+ .build()
339
+
340
+ expect(v.name).toBe('full_view')
341
+ expect(v.description).toBe('Full view example')
342
+ expect(v.materialized).toBe(true)
343
+ expect(v.refreshInterval).toBe('1h')
344
+ expect(v.retention).toBe('7d')
345
+ expect(v.public).toBe(true)
346
+ expect(v.owner).toBe('analytics')
347
+ expect(v.tags).toEqual(['test', 'example'])
348
+ })
349
+ })
350
+ })
351
+
352
+ describe('DashboardBuilder', () => {
353
+ const testQuery = query('test', 'table').build()
354
+ const testView = view('test_view', testQuery).build()
355
+
356
+ describe('dashboard()', () => {
357
+ it('should create a basic dashboard', () => {
358
+ const d = dashboard('test_dashboard').build()
359
+
360
+ expect(d.name).toBe('test_dashboard')
361
+ expect(d.views).toEqual([])
362
+ })
363
+
364
+ it('should set description', () => {
365
+ const d = dashboard('test').describe('A test dashboard').build()
366
+
367
+ expect(d.description).toBe('A test dashboard')
368
+ })
369
+
370
+ it('should add views', () => {
371
+ const d = dashboard('test').add(testView).build()
372
+
373
+ expect(d.views).toHaveLength(1)
374
+ expect(d.views[0]).toEqual(testView)
375
+ })
376
+
377
+ it('should add multiple views', () => {
378
+ const view2 = view('test_view_2', testQuery).build()
379
+ const d = dashboard('test').add(testView).add(view2).build()
380
+
381
+ expect(d.views).toHaveLength(2)
382
+ })
383
+
384
+ it('should set layout', () => {
385
+ const d = dashboard('test').layout(4, 3).build()
386
+
387
+ expect(d.layout).toBeDefined()
388
+ expect(d.layout?.columns).toBe(4)
389
+ expect(d.layout?.rows).toBe(3)
390
+ expect(d.layout?.items).toEqual([])
391
+ })
392
+
393
+ it('should add view with layout options when layout is set', () => {
394
+ const d = dashboard('test')
395
+ .layout(4, 3)
396
+ .add(testView, { x: 0, y: 0, width: 2, height: 1, visualization: 'line' })
397
+ .build()
398
+
399
+ expect(d.views).toHaveLength(1)
400
+ expect(d.layout?.items).toHaveLength(1)
401
+ expect(d.layout?.items[0]).toEqual({
402
+ viewName: 'test_view',
403
+ x: 0,
404
+ y: 0,
405
+ width: 2,
406
+ height: 1,
407
+ visualization: 'line',
408
+ })
409
+ })
410
+
411
+ it('should set refresh interval', () => {
412
+ const d = dashboard('test').refresh('5m').build()
413
+
414
+ expect(d.refreshInterval).toBe('5m')
415
+ })
416
+
417
+ it('should set owner', () => {
418
+ const d = dashboard('test').owner('exec-team').build()
419
+
420
+ expect(d.owner).toBe('exec-team')
421
+ })
422
+
423
+ it('should set tags', () => {
424
+ const d = dashboard('test').tags('executive', 'saas').build()
425
+
426
+ expect(d.tags).toEqual(['executive', 'saas'])
427
+ })
428
+
429
+ it('should chain all builder methods', () => {
430
+ const d = dashboard('full_dashboard')
431
+ .describe('Full dashboard example')
432
+ .layout(4, 3)
433
+ .add(testView, { x: 0, y: 0, width: 2, height: 1 })
434
+ .refresh('10m')
435
+ .owner('leadership')
436
+ .tags('kpi', 'metrics')
437
+ .build()
438
+
439
+ expect(d.name).toBe('full_dashboard')
440
+ expect(d.description).toBe('Full dashboard example')
441
+ expect(d.layout?.columns).toBe(4)
442
+ expect(d.layout?.rows).toBe(3)
443
+ expect(d.views).toHaveLength(1)
444
+ expect(d.refreshInterval).toBe('10m')
445
+ expect(d.owner).toBe('leadership')
446
+ expect(d.tags).toEqual(['kpi', 'metrics'])
447
+ })
448
+ })
449
+ })
450
+
451
+ describe('Pre-built Queries', () => {
452
+ describe('MrrOverview', () => {
453
+ it('should have correct structure', () => {
454
+ expect(MrrOverview.name).toBe('mrr_overview')
455
+ expect(MrrOverview.source).toBe('revenue_events')
456
+ expect(MrrOverview.dimensions).toContain('month')
457
+ expect(MrrOverview.measures).toContain('mrr')
458
+ expect(MrrOverview.measures).toContain('newMrr')
459
+ expect(MrrOverview.measures).toContain('expansionMrr')
460
+ expect(MrrOverview.measures).toContain('contractionMrr')
461
+ expect(MrrOverview.measures).toContain('churnedMrr')
462
+ expect(MrrOverview.measures).toContain('netNewMrr')
463
+ expect(MrrOverview.timeRange?.start).toBe('-12m')
464
+ })
465
+ })
466
+
467
+ describe('ArrBySegment', () => {
468
+ it('should have correct structure', () => {
469
+ expect(ArrBySegment.name).toBe('arr_by_segment')
470
+ expect(ArrBySegment.source).toBe('revenue_events')
471
+ expect(ArrBySegment.dimensions).toContain('customerSegment')
472
+ expect(ArrBySegment.measures).toContain('arr')
473
+ expect(ArrBySegment.measures).toContain('customers')
474
+ expect(ArrBySegment.measures).toContain('arpu')
475
+ })
476
+ })
477
+
478
+ describe('CohortRetention', () => {
479
+ it('should have correct structure', () => {
480
+ expect(CohortRetention.name).toBe('cohort_retention')
481
+ expect(CohortRetention.source).toBe('customer_events')
482
+ expect(CohortRetention.dimensions).toContain('cohort')
483
+ expect(CohortRetention.dimensions).toContain('month')
484
+ expect(CohortRetention.measures).toContain('customers')
485
+ expect(CohortRetention.measures).toContain('mrr')
486
+ })
487
+ })
488
+
489
+ describe('UnitEconomics', () => {
490
+ it('should have correct structure', () => {
491
+ expect(UnitEconomics.name).toBe('unit_economics')
492
+ expect(UnitEconomics.source).toBe('financial_events')
493
+ expect(UnitEconomics.measures).toContain('cac')
494
+ expect(UnitEconomics.measures).toContain('ltv')
495
+ expect(UnitEconomics.measures).toContain('ltvCacRatio')
496
+ expect(UnitEconomics.measures).toContain('arpu')
497
+ expect(UnitEconomics.measures).toContain('customerChurnRate')
498
+ })
499
+ })
500
+
501
+ describe('RevenueByChannel', () => {
502
+ it('should have correct structure', () => {
503
+ expect(RevenueByChannel.name).toBe('revenue_by_channel')
504
+ expect(RevenueByChannel.source).toBe('revenue_events')
505
+ expect(RevenueByChannel.dimensions).toContain('channel')
506
+ expect(RevenueByChannel.measures).toContain('mrr')
507
+ expect(RevenueByChannel.measures).toContain('newCustomers')
508
+ expect(RevenueByChannel.measures).toContain('cac')
509
+ })
510
+ })
511
+
512
+ describe('GrowthMetrics', () => {
513
+ it('should have correct structure', () => {
514
+ expect(GrowthMetrics.name).toBe('growth_metrics')
515
+ expect(GrowthMetrics.source).toBe('financial_events')
516
+ expect(GrowthMetrics.dimensions).toContain('month')
517
+ expect(GrowthMetrics.measures).toContain('mrr')
518
+ expect(GrowthMetrics.measures).toContain('netNewMrr')
519
+ expect(GrowthMetrics.measures).toContain('quickRatio')
520
+ expect(GrowthMetrics.measures).toContain('nrr')
521
+ expect(GrowthMetrics.measures).toContain('magicNumber')
522
+ })
523
+ })
524
+ })
525
+
526
+ describe('Pre-built Dashboards', () => {
527
+ describe('ExecutiveDashboard', () => {
528
+ it('should have correct structure', () => {
529
+ expect(ExecutiveDashboard.name).toBe('executive')
530
+ expect(ExecutiveDashboard.description).toBe('Executive overview of key SaaS metrics')
531
+ expect(ExecutiveDashboard.views).toHaveLength(5)
532
+ expect(ExecutiveDashboard.refreshInterval).toBe('5m')
533
+ expect(ExecutiveDashboard.tags).toContain('executive')
534
+ expect(ExecutiveDashboard.tags).toContain('saas')
535
+ expect(ExecutiveDashboard.tags).toContain('metrics')
536
+ })
537
+
538
+ it('should have correct layout', () => {
539
+ expect(ExecutiveDashboard.layout).toBeDefined()
540
+ expect(ExecutiveDashboard.layout?.columns).toBe(4)
541
+ expect(ExecutiveDashboard.layout?.rows).toBe(3)
542
+ expect(ExecutiveDashboard.layout?.items).toHaveLength(5)
543
+ })
544
+
545
+ it('should have views with visualizations', () => {
546
+ const items = ExecutiveDashboard.layout?.items || []
547
+ const visualizations = items.map((item) => item.visualization)
548
+
549
+ expect(visualizations).toContain('trend')
550
+ expect(visualizations).toContain('bar')
551
+ expect(visualizations).toContain('table')
552
+ expect(visualizations).toContain('line')
553
+ expect(visualizations).toContain('cohort')
554
+ })
555
+ })
556
+ })