business-as-code 2.1.1 → 2.3.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 (212) hide show
  1. package/CHANGELOG.md +18 -0
  2. package/README.md +2 -0
  3. package/package.json +7 -4
  4. package/src/dollar.ts +5 -2
  5. package/src/entities/organization.ts +31 -18
  6. package/src/goals.ts +78 -12
  7. package/src/index.ts +48 -18
  8. package/src/kpis.ts +62 -8
  9. package/src/metrics.ts +92 -79
  10. package/src/okrs.ts +120 -20
  11. package/src/organization.ts +12 -15
  12. package/src/process.ts +11 -12
  13. package/src/product.ts +8 -9
  14. package/src/queries.ts +238 -75
  15. package/src/roles.ts +62 -61
  16. package/src/workflow.ts +22 -15
  17. package/test/business.test.ts +282 -0
  18. package/test/dollar.test.ts +270 -0
  19. package/test/entities.test.ts +628 -0
  20. package/test/financials.test.ts +539 -0
  21. package/test/goals.test.ts +451 -0
  22. package/{src → test}/index.test.ts +1 -1
  23. package/test/kpis.test.ts +440 -0
  24. package/test/metrics.test.ts +744 -0
  25. package/test/okrs.test.ts +741 -0
  26. package/test/organization.test.ts +548 -0
  27. package/test/process.test.ts +503 -0
  28. package/test/product.test.ts +430 -0
  29. package/test/queries.test.ts +556 -0
  30. package/test/roles.test.ts +546 -0
  31. package/test/service.test.ts +450 -0
  32. package/test/types.test.ts +1141 -0
  33. package/test/vision.test.ts +214 -0
  34. package/test/workflow.test.ts +501 -0
  35. package/vitest.config.ts +47 -0
  36. package/.turbo/turbo-build.log +0 -5
  37. package/dist/business.d.ts +0 -62
  38. package/dist/business.d.ts.map +0 -1
  39. package/dist/business.js +0 -109
  40. package/dist/business.js.map +0 -1
  41. package/dist/dollar.d.ts +0 -60
  42. package/dist/dollar.d.ts.map +0 -1
  43. package/dist/dollar.js +0 -107
  44. package/dist/dollar.js.map +0 -1
  45. package/dist/entities/assets.d.ts +0 -21
  46. package/dist/entities/assets.d.ts.map +0 -1
  47. package/dist/entities/assets.js +0 -323
  48. package/dist/entities/assets.js.map +0 -1
  49. package/dist/entities/business.d.ts +0 -36
  50. package/dist/entities/business.d.ts.map +0 -1
  51. package/dist/entities/business.js +0 -370
  52. package/dist/entities/business.js.map +0 -1
  53. package/dist/entities/communication.d.ts +0 -21
  54. package/dist/entities/communication.d.ts.map +0 -1
  55. package/dist/entities/communication.js +0 -255
  56. package/dist/entities/communication.js.map +0 -1
  57. package/dist/entities/customers.d.ts +0 -58
  58. package/dist/entities/customers.d.ts.map +0 -1
  59. package/dist/entities/customers.js +0 -989
  60. package/dist/entities/customers.js.map +0 -1
  61. package/dist/entities/financials.d.ts +0 -59
  62. package/dist/entities/financials.d.ts.map +0 -1
  63. package/dist/entities/financials.js +0 -932
  64. package/dist/entities/financials.js.map +0 -1
  65. package/dist/entities/goals.d.ts +0 -58
  66. package/dist/entities/goals.d.ts.map +0 -1
  67. package/dist/entities/goals.js +0 -800
  68. package/dist/entities/goals.js.map +0 -1
  69. package/dist/entities/index.d.ts +0 -299
  70. package/dist/entities/index.d.ts.map +0 -1
  71. package/dist/entities/index.js +0 -198
  72. package/dist/entities/index.js.map +0 -1
  73. package/dist/entities/legal.d.ts +0 -21
  74. package/dist/entities/legal.d.ts.map +0 -1
  75. package/dist/entities/legal.js +0 -301
  76. package/dist/entities/legal.js.map +0 -1
  77. package/dist/entities/market.d.ts +0 -21
  78. package/dist/entities/market.d.ts.map +0 -1
  79. package/dist/entities/market.js +0 -301
  80. package/dist/entities/market.js.map +0 -1
  81. package/dist/entities/marketing.d.ts +0 -67
  82. package/dist/entities/marketing.d.ts.map +0 -1
  83. package/dist/entities/marketing.js +0 -1157
  84. package/dist/entities/marketing.js.map +0 -1
  85. package/dist/entities/offerings.d.ts +0 -51
  86. package/dist/entities/offerings.d.ts.map +0 -1
  87. package/dist/entities/offerings.js +0 -727
  88. package/dist/entities/offerings.js.map +0 -1
  89. package/dist/entities/operations.d.ts +0 -58
  90. package/dist/entities/operations.d.ts.map +0 -1
  91. package/dist/entities/operations.js +0 -787
  92. package/dist/entities/operations.js.map +0 -1
  93. package/dist/entities/organization.d.ts +0 -57
  94. package/dist/entities/organization.d.ts.map +0 -1
  95. package/dist/entities/organization.js +0 -807
  96. package/dist/entities/organization.js.map +0 -1
  97. package/dist/entities/partnerships.d.ts +0 -21
  98. package/dist/entities/partnerships.d.ts.map +0 -1
  99. package/dist/entities/partnerships.js +0 -300
  100. package/dist/entities/partnerships.js.map +0 -1
  101. package/dist/entities/planning.d.ts +0 -87
  102. package/dist/entities/planning.d.ts.map +0 -1
  103. package/dist/entities/planning.js +0 -271
  104. package/dist/entities/planning.js.map +0 -1
  105. package/dist/entities/projects.d.ts +0 -25
  106. package/dist/entities/projects.d.ts.map +0 -1
  107. package/dist/entities/projects.js +0 -349
  108. package/dist/entities/projects.js.map +0 -1
  109. package/dist/entities/risk.d.ts +0 -21
  110. package/dist/entities/risk.d.ts.map +0 -1
  111. package/dist/entities/risk.js +0 -293
  112. package/dist/entities/risk.js.map +0 -1
  113. package/dist/entities/sales.d.ts +0 -72
  114. package/dist/entities/sales.d.ts.map +0 -1
  115. package/dist/entities/sales.js +0 -1248
  116. package/dist/entities/sales.js.map +0 -1
  117. package/dist/financials.d.ts +0 -130
  118. package/dist/financials.d.ts.map +0 -1
  119. package/dist/financials.js +0 -297
  120. package/dist/financials.js.map +0 -1
  121. package/dist/goals.d.ts +0 -87
  122. package/dist/goals.d.ts.map +0 -1
  123. package/dist/goals.js +0 -215
  124. package/dist/goals.js.map +0 -1
  125. package/dist/index.d.ts +0 -97
  126. package/dist/index.d.ts.map +0 -1
  127. package/dist/index.js +0 -132
  128. package/dist/index.js.map +0 -1
  129. package/dist/kpis.d.ts +0 -118
  130. package/dist/kpis.d.ts.map +0 -1
  131. package/dist/kpis.js +0 -232
  132. package/dist/kpis.js.map +0 -1
  133. package/dist/metrics.d.ts +0 -448
  134. package/dist/metrics.d.ts.map +0 -1
  135. package/dist/metrics.js +0 -325
  136. package/dist/metrics.js.map +0 -1
  137. package/dist/okrs.d.ts +0 -123
  138. package/dist/okrs.d.ts.map +0 -1
  139. package/dist/okrs.js +0 -269
  140. package/dist/okrs.js.map +0 -1
  141. package/dist/organization.d.ts +0 -585
  142. package/dist/organization.d.ts.map +0 -1
  143. package/dist/organization.js +0 -173
  144. package/dist/organization.js.map +0 -1
  145. package/dist/process.d.ts +0 -112
  146. package/dist/process.d.ts.map +0 -1
  147. package/dist/process.js +0 -241
  148. package/dist/process.js.map +0 -1
  149. package/dist/product.d.ts +0 -85
  150. package/dist/product.d.ts.map +0 -1
  151. package/dist/product.js +0 -145
  152. package/dist/product.js.map +0 -1
  153. package/dist/queries.d.ts +0 -304
  154. package/dist/queries.d.ts.map +0 -1
  155. package/dist/queries.js +0 -415
  156. package/dist/queries.js.map +0 -1
  157. package/dist/roles.d.ts +0 -340
  158. package/dist/roles.d.ts.map +0 -1
  159. package/dist/roles.js +0 -255
  160. package/dist/roles.js.map +0 -1
  161. package/dist/service.d.ts +0 -61
  162. package/dist/service.d.ts.map +0 -1
  163. package/dist/service.js +0 -140
  164. package/dist/service.js.map +0 -1
  165. package/dist/types.d.ts +0 -459
  166. package/dist/types.d.ts.map +0 -1
  167. package/dist/types.js +0 -5
  168. package/dist/types.js.map +0 -1
  169. package/dist/vision.d.ts +0 -38
  170. package/dist/vision.d.ts.map +0 -1
  171. package/dist/vision.js +0 -68
  172. package/dist/vision.js.map +0 -1
  173. package/dist/workflow.d.ts +0 -115
  174. package/dist/workflow.d.ts.map +0 -1
  175. package/dist/workflow.js +0 -247
  176. package/dist/workflow.js.map +0 -1
  177. package/src/business.js +0 -108
  178. package/src/dollar.js +0 -106
  179. package/src/entities/assets.js +0 -322
  180. package/src/entities/business.js +0 -369
  181. package/src/entities/communication.js +0 -254
  182. package/src/entities/customers.js +0 -988
  183. package/src/entities/financials.js +0 -931
  184. package/src/entities/goals.js +0 -799
  185. package/src/entities/index.js +0 -197
  186. package/src/entities/legal.js +0 -300
  187. package/src/entities/market.js +0 -300
  188. package/src/entities/marketing.js +0 -1156
  189. package/src/entities/offerings.js +0 -726
  190. package/src/entities/operations.js +0 -786
  191. package/src/entities/organization.js +0 -806
  192. package/src/entities/partnerships.js +0 -299
  193. package/src/entities/planning.js +0 -270
  194. package/src/entities/projects.js +0 -348
  195. package/src/entities/risk.js +0 -292
  196. package/src/entities/sales.js +0 -1247
  197. package/src/financials.js +0 -296
  198. package/src/goals.js +0 -214
  199. package/src/index.js +0 -131
  200. package/src/index.test.js +0 -274
  201. package/src/kpis.js +0 -231
  202. package/src/metrics.js +0 -324
  203. package/src/okrs.js +0 -268
  204. package/src/organization.js +0 -172
  205. package/src/process.js +0 -240
  206. package/src/product.js +0 -144
  207. package/src/queries.js +0 -414
  208. package/src/roles.js +0 -254
  209. package/src/service.js +0 -139
  210. package/src/types.js +0 -4
  211. package/src/vision.js +0 -67
  212. package/src/workflow.js +0 -246
package/src/process.ts CHANGED
@@ -89,7 +89,7 @@ export function getStepsByAutomationLevel(
89
89
  process: ProcessDefinition,
90
90
  level: ProcessStep['automationLevel']
91
91
  ): ProcessStep[] {
92
- return process.steps?.filter(step => step.automationLevel === level) || []
92
+ return process.steps?.filter((step) => step.automationLevel === level) || []
93
93
  }
94
94
 
95
95
  /**
@@ -160,7 +160,7 @@ export function calculateAutomationPercentage(process: ProcessDefinition): numbe
160
160
  if (!process.steps || process.steps.length === 0) return 0
161
161
 
162
162
  const automatedSteps = process.steps.filter(
163
- step => step.automationLevel === 'automated' || step.automationLevel === 'semi-automated'
163
+ (step) => step.automationLevel === 'automated' || step.automationLevel === 'semi-automated'
164
164
  ).length
165
165
 
166
166
  return (automatedSteps / process.steps.length) * 100
@@ -170,7 +170,7 @@ export function calculateAutomationPercentage(process: ProcessDefinition): numbe
170
170
  * Get metric by name
171
171
  */
172
172
  export function getMetric(process: ProcessDefinition, name: string): ProcessMetric | undefined {
173
- return process.metrics?.find(m => m.name === name)
173
+ return process.metrics?.find((m) => m.name === name)
174
174
  }
175
175
 
176
176
  /**
@@ -198,14 +198,13 @@ export function updateMetric(
198
198
  metricName: string,
199
199
  currentValue: number
200
200
  ): ProcessDefinition {
201
- const metrics = process.metrics?.map(m =>
201
+ const metrics = process.metrics?.map((m) =>
202
202
  m.name === metricName ? { ...m, current: currentValue } : m
203
203
  )
204
204
 
205
- return {
206
- ...process,
207
- metrics,
208
- }
205
+ const result: ProcessDefinition = { ...process }
206
+ if (metrics !== undefined) result.metrics = metrics
207
+ return result
209
208
  }
210
209
 
211
210
  /**
@@ -222,10 +221,10 @@ export function addStep(process: ProcessDefinition, step: ProcessStep): ProcessD
222
221
  * Remove step from process
223
222
  */
224
223
  export function removeStep(process: ProcessDefinition, stepOrder: number): ProcessDefinition {
225
- return {
226
- ...process,
227
- steps: process.steps?.filter(s => s.order !== stepOrder),
228
- }
224
+ const steps = process.steps?.filter((s) => s.order !== stepOrder)
225
+ const result: ProcessDefinition = { ...process }
226
+ if (steps !== undefined) result.steps = steps
227
+ return result
229
228
  }
230
229
 
231
230
  /**
package/src/product.ts CHANGED
@@ -82,7 +82,7 @@ export function getRoadmapByStatus(
82
82
  product: ProductDefinition,
83
83
  status: RoadmapItem['status']
84
84
  ): RoadmapItem[] {
85
- return product.roadmap?.filter(item => item.status === status) || []
85
+ return product.roadmap?.filter((item) => item.status === status) || []
86
86
  }
87
87
 
88
88
  /**
@@ -92,7 +92,7 @@ export function getRoadmapByPriority(
92
92
  product: ProductDefinition,
93
93
  priority: RoadmapItem['priority']
94
94
  ): RoadmapItem[] {
95
- return product.roadmap?.filter(item => item.priority === priority) || []
95
+ return product.roadmap?.filter((item) => item.priority === priority) || []
96
96
  }
97
97
 
98
98
  /**
@@ -102,7 +102,7 @@ export function getOverdueRoadmapItems(product: ProductDefinition): RoadmapItem[
102
102
  const now = new Date()
103
103
  return (
104
104
  product.roadmap?.filter(
105
- item =>
105
+ (item) =>
106
106
  item.targetDate &&
107
107
  item.targetDate < now &&
108
108
  item.status !== 'completed' &&
@@ -119,14 +119,13 @@ export function updateRoadmapItem(
119
119
  itemName: string,
120
120
  updates: Partial<RoadmapItem>
121
121
  ): ProductDefinition {
122
- const roadmap = product.roadmap?.map(item =>
122
+ const roadmap = product.roadmap?.map((item) =>
123
123
  item.name === itemName ? { ...item, ...updates } : item
124
124
  )
125
125
 
126
- return {
127
- ...product,
128
- roadmap,
129
- }
126
+ const result: ProductDefinition = { ...product }
127
+ if (roadmap !== undefined) result.roadmap = roadmap
128
+ return result
130
129
  }
131
130
 
132
131
  /**
@@ -145,7 +144,7 @@ export function addFeature(product: ProductDefinition, feature: string): Product
145
144
  export function removeFeature(product: ProductDefinition, feature: string): ProductDefinition {
146
145
  return {
147
146
  ...product,
148
- features: product.features?.filter(f => f !== feature) || [],
147
+ features: product.features?.filter((f) => f !== feature) || [],
149
148
  }
150
149
  }
151
150
 
package/src/queries.ts CHANGED
@@ -17,14 +17,7 @@ import type { TimePeriod } from './types.js'
17
17
  /**
18
18
  * Time granularity for aggregations
19
19
  */
20
- export type Granularity =
21
- | 'minute'
22
- | 'hour'
23
- | 'day'
24
- | 'week'
25
- | 'month'
26
- | 'quarter'
27
- | 'year'
20
+ export type Granularity = 'minute' | 'hour' | 'day' | 'week' | 'month' | 'quarter' | 'year'
28
21
 
29
22
  /**
30
23
  * Aggregation function
@@ -50,18 +43,18 @@ export type AggregateFunction =
50
43
  * Comparison operator
51
44
  */
52
45
  export type Operator =
53
- | 'eq' // =
54
- | 'ne' // !=
55
- | 'gt' // >
56
- | 'gte' // >=
57
- | 'lt' // <
58
- | 'lte' // <=
59
- | 'in' // IN
60
- | 'notIn' // NOT IN
61
- | 'like' // LIKE
46
+ | 'eq' // =
47
+ | 'ne' // !=
48
+ | 'gt' // >
49
+ | 'gte' // >=
50
+ | 'lt' // <
51
+ | 'lte' // <=
52
+ | 'in' // IN
53
+ | 'notIn' // NOT IN
54
+ | 'like' // LIKE
62
55
  | 'notLike' // NOT LIKE
63
56
  | 'between' // BETWEEN
64
- | 'isNull' // IS NULL
57
+ | 'isNull' // IS NULL
65
58
  | 'isNotNull' // IS NOT NULL
66
59
 
67
60
  /**
@@ -78,11 +71,11 @@ export type SortDirection = 'asc' | 'desc'
78
71
  */
79
72
  export interface Dimension {
80
73
  name: string
81
- field: string // Source field in database
74
+ field: string // Source field in database
82
75
  type: 'string' | 'number' | 'date' | 'boolean'
83
76
  description?: string
84
- granularity?: Granularity // For time dimensions
85
- format?: string // Display format
77
+ granularity?: Granularity // For time dimensions
78
+ format?: string // Display format
86
79
  }
87
80
 
88
81
  /**
@@ -90,7 +83,7 @@ export interface Dimension {
90
83
  */
91
84
  export interface Measure {
92
85
  name: string
93
- field: string // Source field or expression
86
+ field: string // Source field or expression
94
87
  aggregate: AggregateFunction
95
88
  type?: 'number' | 'currency' | 'percent'
96
89
  description?: string
@@ -103,8 +96,8 @@ export interface Measure {
103
96
  */
104
97
  export interface CalculatedMeasure {
105
98
  name: string
106
- expression: string // e.g., "revenue - cogs" or "revenue / customers"
107
- measures: string[] // Dependencies
99
+ expression: string // e.g., "revenue - cogs" or "revenue / customers"
100
+ measures: string[] // Dependencies
108
101
  type?: 'number' | 'currency' | 'percent'
109
102
  description?: string
110
103
  format?: string
@@ -137,8 +130,8 @@ export interface Sort {
137
130
  * Time range filter
138
131
  */
139
132
  export interface TimeRange {
140
- field: string // The timestamp field
141
- start?: Date | string // Absolute or relative (e.g., '-30d')
133
+ field: string // The timestamp field
134
+ start?: Date | string // Absolute or relative (e.g., '-30d')
142
135
  end?: Date | string
143
136
  granularity?: Granularity
144
137
  }
@@ -155,11 +148,11 @@ export interface Query {
155
148
  description?: string
156
149
 
157
150
  // Data source
158
- source: string // Table or view name
151
+ source: string // Table or view name
159
152
 
160
153
  // What to select
161
- dimensions?: string[] // Dimension names to group by
162
- measures?: string[] // Measure names to aggregate
154
+ dimensions?: string[] // Dimension names to group by
155
+ measures?: string[] // Measure names to aggregate
163
156
 
164
157
  // Filtering
165
158
  filters?: Filter[]
@@ -197,8 +190,8 @@ export interface View {
197
190
 
198
191
  // Materialization options
199
192
  materialized?: boolean
200
- refreshInterval?: string // e.g., '5m', '1h', '1d'
201
- retention?: string // How long to keep data
193
+ refreshInterval?: string // e.g., '5m', '1h', '1d'
194
+ retention?: string // How long to keep data
202
195
 
203
196
  // Access
204
197
  public?: boolean
@@ -244,16 +237,16 @@ export interface DashboardItem {
244
237
  * Visualization type
245
238
  */
246
239
  export type Visualization =
247
- | 'number' // Single big number
248
- | 'trend' // Number with sparkline
249
- | 'table' // Data table
250
- | 'bar' // Bar chart
251
- | 'line' // Line chart
252
- | 'area' // Area chart
253
- | 'pie' // Pie chart
254
- | 'funnel' // Funnel chart
255
- | 'cohort' // Cohort matrix
256
- | 'heatmap' // Heatmap
240
+ | 'number' // Single big number
241
+ | 'trend' // Number with sparkline
242
+ | 'table' // Data table
243
+ | 'bar' // Bar chart
244
+ | 'line' // Line chart
245
+ | 'area' // Area chart
246
+ | 'pie' // Pie chart
247
+ | 'funnel' // Funnel chart
248
+ | 'cohort' // Cohort matrix
249
+ | 'heatmap' // Heatmap
257
250
 
258
251
  // =============================================================================
259
252
  // Metric Definitions (Standard SaaS Metrics as Queries)
@@ -266,18 +259,39 @@ export const StandardDimensions: Record<string, Dimension> = {
266
259
  // Time
267
260
  date: { name: 'date', field: 'date', type: 'date', description: 'Event date' },
268
261
  month: { name: 'month', field: 'date', type: 'date', granularity: 'month', description: 'Month' },
269
- quarter: { name: 'quarter', field: 'date', type: 'date', granularity: 'quarter', description: 'Quarter' },
262
+ quarter: {
263
+ name: 'quarter',
264
+ field: 'date',
265
+ type: 'date',
266
+ granularity: 'quarter',
267
+ description: 'Quarter',
268
+ },
270
269
  year: { name: 'year', field: 'date', type: 'date', granularity: 'year', description: 'Year' },
271
270
 
272
271
  // Customer
273
- customerId: { name: 'customerId', field: 'customer_id', type: 'string', description: 'Customer ID' },
274
- customerSegment: { name: 'customerSegment', field: 'customer_segment', type: 'string', description: 'Customer segment' },
272
+ customerId: {
273
+ name: 'customerId',
274
+ field: 'customer_id',
275
+ type: 'string',
276
+ description: 'Customer ID',
277
+ },
278
+ customerSegment: {
279
+ name: 'customerSegment',
280
+ field: 'customer_segment',
281
+ type: 'string',
282
+ description: 'Customer segment',
283
+ },
275
284
  plan: { name: 'plan', field: 'plan', type: 'string', description: 'Subscription plan' },
276
285
  cohort: { name: 'cohort', field: 'cohort', type: 'string', description: 'Customer cohort' },
277
286
 
278
287
  // Product
279
288
  productId: { name: 'productId', field: 'product_id', type: 'string', description: 'Product ID' },
280
- productName: { name: 'productName', field: 'product_name', type: 'string', description: 'Product name' },
289
+ productName: {
290
+ name: 'productName',
291
+ field: 'product_name',
292
+ type: 'string',
293
+ description: 'Product name',
294
+ },
281
295
  feature: { name: 'feature', field: 'feature', type: 'string', description: 'Feature name' },
282
296
 
283
297
  // Geography
@@ -285,9 +299,19 @@ export const StandardDimensions: Record<string, Dimension> = {
285
299
  region: { name: 'region', field: 'region', type: 'string', description: 'Region' },
286
300
 
287
301
  // Channel
288
- channel: { name: 'channel', field: 'channel', type: 'string', description: 'Acquisition channel' },
302
+ channel: {
303
+ name: 'channel',
304
+ field: 'channel',
305
+ type: 'string',
306
+ description: 'Acquisition channel',
307
+ },
289
308
  source: { name: 'source', field: 'source', type: 'string', description: 'Traffic source' },
290
- campaign: { name: 'campaign', field: 'campaign', type: 'string', description: 'Marketing campaign' },
309
+ campaign: {
310
+ name: 'campaign',
311
+ field: 'campaign',
312
+ type: 'string',
313
+ description: 'Marketing campaign',
314
+ },
291
315
  }
292
316
 
293
317
  /**
@@ -295,27 +319,117 @@ export const StandardDimensions: Record<string, Dimension> = {
295
319
  */
296
320
  export const StandardMeasures: Record<string, Measure> = {
297
321
  // Revenue
298
- revenue: { name: 'revenue', field: 'revenue', aggregate: 'sum', type: 'currency', description: 'Total revenue' },
299
- mrr: { name: 'mrr', field: 'mrr', aggregate: 'sum', type: 'currency', description: 'Monthly recurring revenue' },
300
- newMrr: { name: 'newMrr', field: 'new_mrr', aggregate: 'sum', type: 'currency', description: 'New MRR' },
301
- expansionMrr: { name: 'expansionMrr', field: 'expansion_mrr', aggregate: 'sum', type: 'currency', description: 'Expansion MRR' },
302
- contractionMrr: { name: 'contractionMrr', field: 'contraction_mrr', aggregate: 'sum', type: 'currency', description: 'Contraction MRR' },
303
- churnedMrr: { name: 'churnedMrr', field: 'churned_mrr', aggregate: 'sum', type: 'currency', description: 'Churned MRR' },
322
+ revenue: {
323
+ name: 'revenue',
324
+ field: 'revenue',
325
+ aggregate: 'sum',
326
+ type: 'currency',
327
+ description: 'Total revenue',
328
+ },
329
+ mrr: {
330
+ name: 'mrr',
331
+ field: 'mrr',
332
+ aggregate: 'sum',
333
+ type: 'currency',
334
+ description: 'Monthly recurring revenue',
335
+ },
336
+ newMrr: {
337
+ name: 'newMrr',
338
+ field: 'new_mrr',
339
+ aggregate: 'sum',
340
+ type: 'currency',
341
+ description: 'New MRR',
342
+ },
343
+ expansionMrr: {
344
+ name: 'expansionMrr',
345
+ field: 'expansion_mrr',
346
+ aggregate: 'sum',
347
+ type: 'currency',
348
+ description: 'Expansion MRR',
349
+ },
350
+ contractionMrr: {
351
+ name: 'contractionMrr',
352
+ field: 'contraction_mrr',
353
+ aggregate: 'sum',
354
+ type: 'currency',
355
+ description: 'Contraction MRR',
356
+ },
357
+ churnedMrr: {
358
+ name: 'churnedMrr',
359
+ field: 'churned_mrr',
360
+ aggregate: 'sum',
361
+ type: 'currency',
362
+ description: 'Churned MRR',
363
+ },
304
364
 
305
365
  // Customers
306
- customers: { name: 'customers', field: 'customer_id', aggregate: 'countDistinct', type: 'number', description: 'Unique customers' },
307
- newCustomers: { name: 'newCustomers', field: 'new_customer_id', aggregate: 'countDistinct', type: 'number', description: 'New customers' },
308
- churnedCustomers: { name: 'churnedCustomers', field: 'churned_customer_id', aggregate: 'countDistinct', type: 'number', description: 'Churned customers' },
366
+ customers: {
367
+ name: 'customers',
368
+ field: 'customer_id',
369
+ aggregate: 'countDistinct',
370
+ type: 'number',
371
+ description: 'Unique customers',
372
+ },
373
+ newCustomers: {
374
+ name: 'newCustomers',
375
+ field: 'new_customer_id',
376
+ aggregate: 'countDistinct',
377
+ type: 'number',
378
+ description: 'New customers',
379
+ },
380
+ churnedCustomers: {
381
+ name: 'churnedCustomers',
382
+ field: 'churned_customer_id',
383
+ aggregate: 'countDistinct',
384
+ type: 'number',
385
+ description: 'Churned customers',
386
+ },
309
387
 
310
388
  // Usage
311
- events: { name: 'events', field: 'event_id', aggregate: 'count', type: 'number', description: 'Event count' },
312
- sessions: { name: 'sessions', field: 'session_id', aggregate: 'countDistinct', type: 'number', description: 'Unique sessions' },
313
- activeUsers: { name: 'activeUsers', field: 'user_id', aggregate: 'countDistinct', type: 'number', description: 'Active users' },
389
+ events: {
390
+ name: 'events',
391
+ field: 'event_id',
392
+ aggregate: 'count',
393
+ type: 'number',
394
+ description: 'Event count',
395
+ },
396
+ sessions: {
397
+ name: 'sessions',
398
+ field: 'session_id',
399
+ aggregate: 'countDistinct',
400
+ type: 'number',
401
+ description: 'Unique sessions',
402
+ },
403
+ activeUsers: {
404
+ name: 'activeUsers',
405
+ field: 'user_id',
406
+ aggregate: 'countDistinct',
407
+ type: 'number',
408
+ description: 'Active users',
409
+ },
314
410
 
315
411
  // Costs
316
- cogs: { name: 'cogs', field: 'cogs', aggregate: 'sum', type: 'currency', description: 'Cost of goods sold' },
317
- salesSpend: { name: 'salesSpend', field: 'sales_spend', aggregate: 'sum', type: 'currency', description: 'Sales spend' },
318
- marketingSpend: { name: 'marketingSpend', field: 'marketing_spend', aggregate: 'sum', type: 'currency', description: 'Marketing spend' },
412
+ cogs: {
413
+ name: 'cogs',
414
+ field: 'cogs',
415
+ aggregate: 'sum',
416
+ type: 'currency',
417
+ description: 'Cost of goods sold',
418
+ },
419
+ salesSpend: {
420
+ name: 'salesSpend',
421
+ field: 'sales_spend',
422
+ aggregate: 'sum',
423
+ type: 'currency',
424
+ description: 'Sales spend',
425
+ },
426
+ marketingSpend: {
427
+ name: 'marketingSpend',
428
+ field: 'marketing_spend',
429
+ aggregate: 'sum',
430
+ type: 'currency',
431
+ description: 'Marketing spend',
432
+ },
319
433
  }
320
434
 
321
435
  /**
@@ -471,8 +585,17 @@ export class QueryBuilder {
471
585
  return this
472
586
  }
473
587
 
474
- timeRange(field: string, start?: Date | string, end?: Date | string, granularity?: Granularity): this {
475
- this._query.timeRange = { field, start, end, granularity }
588
+ timeRange(
589
+ field: string,
590
+ start?: Date | string,
591
+ end?: Date | string,
592
+ granularity?: Granularity
593
+ ): this {
594
+ const timeRange: TimeRange = { field }
595
+ if (start !== undefined) timeRange.start = start
596
+ if (end !== undefined) timeRange.end = end
597
+ if (granularity !== undefined) timeRange.granularity = granularity
598
+ this._query.timeRange = timeRange
476
599
  return this
477
600
  }
478
601
 
@@ -610,8 +733,8 @@ export class ViewBuilder {
610
733
 
611
734
  materialize(refreshInterval?: string, retention?: string): this {
612
735
  this._view.materialized = true
613
- this._view.refreshInterval = refreshInterval
614
- this._view.retention = retention
736
+ if (refreshInterval !== undefined) this._view.refreshInterval = refreshInterval
737
+ if (retention !== undefined) this._view.retention = retention
615
738
  return this
616
739
  }
617
740
 
@@ -661,17 +784,27 @@ export class DashboardBuilder {
661
784
  return this
662
785
  }
663
786
 
664
- add(viewDef: View, options?: { x?: number; y?: number; width?: number; height?: number; visualization?: Visualization }): this {
787
+ add(
788
+ viewDef: View,
789
+ options?: {
790
+ x?: number
791
+ y?: number
792
+ width?: number
793
+ height?: number
794
+ visualization?: Visualization
795
+ }
796
+ ): this {
665
797
  this._dashboard.views.push(viewDef)
666
798
  if (options && this._dashboard.layout) {
667
- this._dashboard.layout.items.push({
799
+ const item: DashboardItem = {
668
800
  viewName: viewDef.name,
669
801
  x: options.x || 0,
670
802
  y: options.y || 0,
671
803
  width: options.width || 1,
672
804
  height: options.height || 1,
673
- visualization: options.visualization,
674
- })
805
+ }
806
+ if (options.visualization !== undefined) item.visualization = options.visualization
807
+ this._dashboard.layout.items.push(item)
675
808
  }
676
809
  return this
677
810
  }
@@ -711,11 +844,41 @@ export class DashboardBuilder {
711
844
  export const ExecutiveDashboard = dashboard('executive')
712
845
  .describe('Executive overview of key SaaS metrics')
713
846
  .layout(4, 3)
714
- .add(view('mrr', MrrOverview).build(), { x: 0, y: 0, width: 2, height: 1, visualization: 'trend' })
715
- .add(view('arr_segments', ArrBySegment).build(), { x: 2, y: 0, width: 2, height: 1, visualization: 'bar' })
716
- .add(view('unit_econ', UnitEconomics).build(), { x: 0, y: 1, width: 2, height: 1, visualization: 'table' })
717
- .add(view('growth', GrowthMetrics).build(), { x: 2, y: 1, width: 2, height: 1, visualization: 'line' })
718
- .add(view('cohorts', CohortRetention).build(), { x: 0, y: 2, width: 4, height: 1, visualization: 'cohort' })
847
+ .add(view('mrr', MrrOverview).build(), {
848
+ x: 0,
849
+ y: 0,
850
+ width: 2,
851
+ height: 1,
852
+ visualization: 'trend',
853
+ })
854
+ .add(view('arr_segments', ArrBySegment).build(), {
855
+ x: 2,
856
+ y: 0,
857
+ width: 2,
858
+ height: 1,
859
+ visualization: 'bar',
860
+ })
861
+ .add(view('unit_econ', UnitEconomics).build(), {
862
+ x: 0,
863
+ y: 1,
864
+ width: 2,
865
+ height: 1,
866
+ visualization: 'table',
867
+ })
868
+ .add(view('growth', GrowthMetrics).build(), {
869
+ x: 2,
870
+ y: 1,
871
+ width: 2,
872
+ height: 1,
873
+ visualization: 'line',
874
+ })
875
+ .add(view('cohorts', CohortRetention).build(), {
876
+ x: 0,
877
+ y: 2,
878
+ width: 4,
879
+ height: 1,
880
+ visualization: 'cohort',
881
+ })
719
882
  .refresh('5m')
720
883
  .tags('executive', 'saas', 'metrics')
721
884
  .build()