business-as-code 0.2.1 → 2.0.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 (190) hide show
  1. package/.turbo/turbo-build.log +5 -0
  2. package/CHANGELOG.md +17 -0
  3. package/IMPLEMENTATION.md +226 -0
  4. package/README.md +1133 -193
  5. package/dist/business.d.ts +62 -0
  6. package/dist/business.d.ts.map +1 -0
  7. package/dist/business.js +109 -0
  8. package/dist/business.js.map +1 -0
  9. package/dist/dollar.d.ts +60 -0
  10. package/dist/dollar.d.ts.map +1 -0
  11. package/dist/dollar.js +107 -0
  12. package/dist/dollar.js.map +1 -0
  13. package/dist/entities/assets.d.ts +21 -0
  14. package/dist/entities/assets.d.ts.map +1 -0
  15. package/dist/entities/assets.js +323 -0
  16. package/dist/entities/assets.js.map +1 -0
  17. package/dist/entities/business.d.ts +36 -0
  18. package/dist/entities/business.d.ts.map +1 -0
  19. package/dist/entities/business.js +370 -0
  20. package/dist/entities/business.js.map +1 -0
  21. package/dist/entities/communication.d.ts +21 -0
  22. package/dist/entities/communication.d.ts.map +1 -0
  23. package/dist/entities/communication.js +255 -0
  24. package/dist/entities/communication.js.map +1 -0
  25. package/dist/entities/customers.d.ts +58 -0
  26. package/dist/entities/customers.d.ts.map +1 -0
  27. package/dist/entities/customers.js +989 -0
  28. package/dist/entities/customers.js.map +1 -0
  29. package/dist/entities/financials.d.ts +59 -0
  30. package/dist/entities/financials.d.ts.map +1 -0
  31. package/dist/entities/financials.js +932 -0
  32. package/dist/entities/financials.js.map +1 -0
  33. package/dist/entities/goals.d.ts +58 -0
  34. package/dist/entities/goals.d.ts.map +1 -0
  35. package/dist/entities/goals.js +800 -0
  36. package/dist/entities/goals.js.map +1 -0
  37. package/dist/entities/index.d.ts +299 -0
  38. package/dist/entities/index.d.ts.map +1 -0
  39. package/dist/entities/index.js +198 -0
  40. package/dist/entities/index.js.map +1 -0
  41. package/dist/entities/legal.d.ts +21 -0
  42. package/dist/entities/legal.d.ts.map +1 -0
  43. package/dist/entities/legal.js +301 -0
  44. package/dist/entities/legal.js.map +1 -0
  45. package/dist/entities/market.d.ts +21 -0
  46. package/dist/entities/market.d.ts.map +1 -0
  47. package/dist/entities/market.js +301 -0
  48. package/dist/entities/market.js.map +1 -0
  49. package/dist/entities/marketing.d.ts +67 -0
  50. package/dist/entities/marketing.d.ts.map +1 -0
  51. package/dist/entities/marketing.js +1157 -0
  52. package/dist/entities/marketing.js.map +1 -0
  53. package/dist/entities/offerings.d.ts +51 -0
  54. package/dist/entities/offerings.d.ts.map +1 -0
  55. package/dist/entities/offerings.js +727 -0
  56. package/dist/entities/offerings.js.map +1 -0
  57. package/dist/entities/operations.d.ts +58 -0
  58. package/dist/entities/operations.d.ts.map +1 -0
  59. package/dist/entities/operations.js +787 -0
  60. package/dist/entities/operations.js.map +1 -0
  61. package/dist/entities/organization.d.ts +57 -0
  62. package/dist/entities/organization.d.ts.map +1 -0
  63. package/dist/entities/organization.js +807 -0
  64. package/dist/entities/organization.js.map +1 -0
  65. package/dist/entities/partnerships.d.ts +21 -0
  66. package/dist/entities/partnerships.d.ts.map +1 -0
  67. package/dist/entities/partnerships.js +300 -0
  68. package/dist/entities/partnerships.js.map +1 -0
  69. package/dist/entities/planning.d.ts +87 -0
  70. package/dist/entities/planning.d.ts.map +1 -0
  71. package/dist/entities/planning.js +271 -0
  72. package/dist/entities/planning.js.map +1 -0
  73. package/dist/entities/projects.d.ts +25 -0
  74. package/dist/entities/projects.d.ts.map +1 -0
  75. package/dist/entities/projects.js +349 -0
  76. package/dist/entities/projects.js.map +1 -0
  77. package/dist/entities/risk.d.ts +21 -0
  78. package/dist/entities/risk.d.ts.map +1 -0
  79. package/dist/entities/risk.js +293 -0
  80. package/dist/entities/risk.js.map +1 -0
  81. package/dist/entities/sales.d.ts +72 -0
  82. package/dist/entities/sales.d.ts.map +1 -0
  83. package/dist/entities/sales.js +1248 -0
  84. package/dist/entities/sales.js.map +1 -0
  85. package/dist/financials.d.ts +130 -0
  86. package/dist/financials.d.ts.map +1 -0
  87. package/dist/financials.js +297 -0
  88. package/dist/financials.js.map +1 -0
  89. package/dist/goals.d.ts +87 -0
  90. package/dist/goals.d.ts.map +1 -0
  91. package/dist/goals.js +215 -0
  92. package/dist/goals.js.map +1 -0
  93. package/dist/index.d.ts +97 -4
  94. package/dist/index.d.ts.map +1 -0
  95. package/dist/index.js +131 -1079
  96. package/dist/index.js.map +1 -1
  97. package/dist/kpis.d.ts +118 -0
  98. package/dist/kpis.d.ts.map +1 -0
  99. package/dist/kpis.js +232 -0
  100. package/dist/kpis.js.map +1 -0
  101. package/dist/metrics.d.ts +448 -0
  102. package/dist/metrics.d.ts.map +1 -0
  103. package/dist/metrics.js +325 -0
  104. package/dist/metrics.js.map +1 -0
  105. package/dist/okrs.d.ts +123 -0
  106. package/dist/okrs.d.ts.map +1 -0
  107. package/dist/okrs.js +269 -0
  108. package/dist/okrs.js.map +1 -0
  109. package/dist/organization.d.ts +585 -0
  110. package/dist/organization.d.ts.map +1 -0
  111. package/dist/organization.js +173 -0
  112. package/dist/organization.js.map +1 -0
  113. package/dist/process.d.ts +112 -0
  114. package/dist/process.d.ts.map +1 -0
  115. package/dist/process.js +241 -0
  116. package/dist/process.js.map +1 -0
  117. package/dist/product.d.ts +85 -0
  118. package/dist/product.d.ts.map +1 -0
  119. package/dist/product.js +145 -0
  120. package/dist/product.js.map +1 -0
  121. package/dist/queries.d.ts +304 -0
  122. package/dist/queries.d.ts.map +1 -0
  123. package/dist/queries.js +415 -0
  124. package/dist/queries.js.map +1 -0
  125. package/dist/roles.d.ts +340 -0
  126. package/dist/roles.d.ts.map +1 -0
  127. package/dist/roles.js +255 -0
  128. package/dist/roles.js.map +1 -0
  129. package/dist/service.d.ts +61 -0
  130. package/dist/service.d.ts.map +1 -0
  131. package/dist/service.js +140 -0
  132. package/dist/service.js.map +1 -0
  133. package/dist/types.d.ts +459 -0
  134. package/dist/types.d.ts.map +1 -0
  135. package/dist/types.js +5 -0
  136. package/dist/types.js.map +1 -0
  137. package/dist/vision.d.ts +38 -0
  138. package/dist/vision.d.ts.map +1 -0
  139. package/dist/vision.js +68 -0
  140. package/dist/vision.js.map +1 -0
  141. package/dist/workflow.d.ts +115 -0
  142. package/dist/workflow.d.ts.map +1 -0
  143. package/dist/workflow.js +247 -0
  144. package/dist/workflow.js.map +1 -0
  145. package/examples/basic-usage.ts +307 -0
  146. package/package.json +19 -60
  147. package/src/business.ts +121 -0
  148. package/src/dollar.ts +132 -0
  149. package/src/entities/assets.ts +332 -0
  150. package/src/entities/business.ts +406 -0
  151. package/src/entities/communication.ts +264 -0
  152. package/src/entities/customers.ts +1072 -0
  153. package/src/entities/financials.ts +1011 -0
  154. package/src/entities/goals.ts +871 -0
  155. package/src/entities/index.ts +383 -0
  156. package/src/entities/legal.ts +310 -0
  157. package/src/entities/market.ts +310 -0
  158. package/src/entities/marketing.ts +1249 -0
  159. package/src/entities/offerings.ts +789 -0
  160. package/src/entities/operations.ts +861 -0
  161. package/src/entities/organization.ts +876 -0
  162. package/src/entities/partnerships.ts +309 -0
  163. package/src/entities/planning.ts +307 -0
  164. package/src/entities/projects.ts +360 -0
  165. package/src/entities/risk.ts +302 -0
  166. package/src/entities/sales.ts +1352 -0
  167. package/src/financials.ts +352 -0
  168. package/src/goals.ts +250 -0
  169. package/src/index.test.ts +336 -0
  170. package/src/index.ts +530 -0
  171. package/src/kpis.ts +275 -0
  172. package/src/metrics.ts +825 -0
  173. package/src/okrs.ts +325 -0
  174. package/src/organization.ts +909 -0
  175. package/src/process.ts +272 -0
  176. package/src/product.ts +178 -0
  177. package/src/queries.ts +767 -0
  178. package/src/roles.ts +686 -0
  179. package/src/service.ts +164 -0
  180. package/src/types.ts +493 -0
  181. package/src/vision.ts +88 -0
  182. package/src/workflow.ts +280 -0
  183. package/tsconfig.json +9 -0
  184. package/dist/loaders/index.d.ts +0 -174
  185. package/dist/loaders/index.js +0 -366
  186. package/dist/loaders/index.js.map +0 -1
  187. package/dist/schema/index.d.ts +0 -146
  188. package/dist/schema/index.js +0 -716
  189. package/dist/schema/index.js.map +0 -1
  190. package/dist/types-CJ9eGS_C.d.ts +0 -86
package/src/queries.ts ADDED
@@ -0,0 +1,767 @@
1
+ /**
2
+ * Live Queries & Views
3
+ *
4
+ * Query definitions for real-time analytics against ai-database (ClickHouse-backed).
5
+ * These are NOT batch reports - they're live, composable queries that execute
6
+ * in real-time against a performant OLAP database.
7
+ *
8
+ * @packageDocumentation
9
+ */
10
+
11
+ import type { TimePeriod } from './types.js'
12
+
13
+ // =============================================================================
14
+ // Core Query Types
15
+ // =============================================================================
16
+
17
+ /**
18
+ * Time granularity for aggregations
19
+ */
20
+ export type Granularity =
21
+ | 'minute'
22
+ | 'hour'
23
+ | 'day'
24
+ | 'week'
25
+ | 'month'
26
+ | 'quarter'
27
+ | 'year'
28
+
29
+ /**
30
+ * Aggregation function
31
+ */
32
+ export type AggregateFunction =
33
+ | 'sum'
34
+ | 'avg'
35
+ | 'min'
36
+ | 'max'
37
+ | 'count'
38
+ | 'countDistinct'
39
+ | 'first'
40
+ | 'last'
41
+ | 'median'
42
+ | 'p50'
43
+ | 'p90'
44
+ | 'p95'
45
+ | 'p99'
46
+ | 'stddev'
47
+ | 'variance'
48
+
49
+ /**
50
+ * Comparison operator
51
+ */
52
+ 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
62
+ | 'notLike' // NOT LIKE
63
+ | 'between' // BETWEEN
64
+ | 'isNull' // IS NULL
65
+ | 'isNotNull' // IS NOT NULL
66
+
67
+ /**
68
+ * Sort direction
69
+ */
70
+ export type SortDirection = 'asc' | 'desc'
71
+
72
+ // =============================================================================
73
+ // Dimension & Measure (OLAP Primitives)
74
+ // =============================================================================
75
+
76
+ /**
77
+ * Dimension - categorical attribute for grouping/filtering
78
+ */
79
+ export interface Dimension {
80
+ name: string
81
+ field: string // Source field in database
82
+ type: 'string' | 'number' | 'date' | 'boolean'
83
+ description?: string
84
+ granularity?: Granularity // For time dimensions
85
+ format?: string // Display format
86
+ }
87
+
88
+ /**
89
+ * Measure - numeric value to aggregate
90
+ */
91
+ export interface Measure {
92
+ name: string
93
+ field: string // Source field or expression
94
+ aggregate: AggregateFunction
95
+ type?: 'number' | 'currency' | 'percent'
96
+ description?: string
97
+ format?: string
98
+ currency?: string
99
+ }
100
+
101
+ /**
102
+ * Calculated measure - derived from other measures
103
+ */
104
+ export interface CalculatedMeasure {
105
+ name: string
106
+ expression: string // e.g., "revenue - cogs" or "revenue / customers"
107
+ measures: string[] // Dependencies
108
+ type?: 'number' | 'currency' | 'percent'
109
+ description?: string
110
+ format?: string
111
+ }
112
+
113
+ // =============================================================================
114
+ // Filter & Sort
115
+ // =============================================================================
116
+
117
+ /**
118
+ * Filter condition
119
+ */
120
+ export interface Filter {
121
+ field: string
122
+ operator: Operator
123
+ value: unknown
124
+ and?: Filter[]
125
+ or?: Filter[]
126
+ }
127
+
128
+ /**
129
+ * Sort specification
130
+ */
131
+ export interface Sort {
132
+ field: string
133
+ direction: SortDirection
134
+ }
135
+
136
+ /**
137
+ * Time range filter
138
+ */
139
+ export interface TimeRange {
140
+ field: string // The timestamp field
141
+ start?: Date | string // Absolute or relative (e.g., '-30d')
142
+ end?: Date | string
143
+ granularity?: Granularity
144
+ }
145
+
146
+ // =============================================================================
147
+ // Query Definition
148
+ // =============================================================================
149
+
150
+ /**
151
+ * Query definition - a composable, reusable query
152
+ */
153
+ export interface Query {
154
+ name: string
155
+ description?: string
156
+
157
+ // Data source
158
+ source: string // Table or view name
159
+
160
+ // What to select
161
+ dimensions?: string[] // Dimension names to group by
162
+ measures?: string[] // Measure names to aggregate
163
+
164
+ // Filtering
165
+ filters?: Filter[]
166
+ timeRange?: TimeRange
167
+
168
+ // Sorting & pagination
169
+ sort?: Sort[]
170
+ limit?: number
171
+ offset?: number
172
+
173
+ // Metadata
174
+ tags?: string[]
175
+ owner?: string
176
+ }
177
+
178
+ /**
179
+ * Query with resolved schema
180
+ */
181
+ export interface ResolvedQuery extends Query {
182
+ resolvedDimensions: Dimension[]
183
+ resolvedMeasures: (Measure | CalculatedMeasure)[]
184
+ }
185
+
186
+ // =============================================================================
187
+ // View Definition (Like Materialized Views)
188
+ // =============================================================================
189
+
190
+ /**
191
+ * View - a saved query with optional materialization
192
+ */
193
+ export interface View {
194
+ name: string
195
+ description?: string
196
+ query: Query
197
+
198
+ // Materialization options
199
+ materialized?: boolean
200
+ refreshInterval?: string // e.g., '5m', '1h', '1d'
201
+ retention?: string // How long to keep data
202
+
203
+ // Access
204
+ public?: boolean
205
+ owner?: string
206
+ tags?: string[]
207
+ }
208
+
209
+ /**
210
+ * Dashboard - collection of related views
211
+ */
212
+ export interface Dashboard {
213
+ name: string
214
+ description?: string
215
+ views: View[]
216
+ layout?: DashboardLayout
217
+ refreshInterval?: string
218
+ owner?: string
219
+ tags?: string[]
220
+ }
221
+
222
+ /**
223
+ * Dashboard layout
224
+ */
225
+ export interface DashboardLayout {
226
+ columns: number
227
+ rows: number
228
+ items: DashboardItem[]
229
+ }
230
+
231
+ /**
232
+ * Dashboard item position
233
+ */
234
+ export interface DashboardItem {
235
+ viewName: string
236
+ x: number
237
+ y: number
238
+ width: number
239
+ height: number
240
+ visualization?: Visualization
241
+ }
242
+
243
+ /**
244
+ * Visualization type
245
+ */
246
+ 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
257
+
258
+ // =============================================================================
259
+ // Metric Definitions (Standard SaaS Metrics as Queries)
260
+ // =============================================================================
261
+
262
+ /**
263
+ * Standard SaaS metric dimensions
264
+ */
265
+ export const StandardDimensions: Record<string, Dimension> = {
266
+ // Time
267
+ date: { name: 'date', field: 'date', type: 'date', description: 'Event date' },
268
+ month: { name: 'month', field: 'date', type: 'date', granularity: 'month', description: 'Month' },
269
+ quarter: { name: 'quarter', field: 'date', type: 'date', granularity: 'quarter', description: 'Quarter' },
270
+ year: { name: 'year', field: 'date', type: 'date', granularity: 'year', description: 'Year' },
271
+
272
+ // 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' },
275
+ plan: { name: 'plan', field: 'plan', type: 'string', description: 'Subscription plan' },
276
+ cohort: { name: 'cohort', field: 'cohort', type: 'string', description: 'Customer cohort' },
277
+
278
+ // Product
279
+ productId: { name: 'productId', field: 'product_id', type: 'string', description: 'Product ID' },
280
+ productName: { name: 'productName', field: 'product_name', type: 'string', description: 'Product name' },
281
+ feature: { name: 'feature', field: 'feature', type: 'string', description: 'Feature name' },
282
+
283
+ // Geography
284
+ country: { name: 'country', field: 'country', type: 'string', description: 'Country' },
285
+ region: { name: 'region', field: 'region', type: 'string', description: 'Region' },
286
+
287
+ // Channel
288
+ channel: { name: 'channel', field: 'channel', type: 'string', description: 'Acquisition channel' },
289
+ source: { name: 'source', field: 'source', type: 'string', description: 'Traffic source' },
290
+ campaign: { name: 'campaign', field: 'campaign', type: 'string', description: 'Marketing campaign' },
291
+ }
292
+
293
+ /**
294
+ * Standard SaaS metric measures
295
+ */
296
+ export const StandardMeasures: Record<string, Measure> = {
297
+ // 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' },
304
+
305
+ // 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' },
309
+
310
+ // 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' },
314
+
315
+ // 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' },
319
+ }
320
+
321
+ /**
322
+ * Calculated SaaS metrics
323
+ */
324
+ export const CalculatedMetrics: Record<string, CalculatedMeasure> = {
325
+ // Revenue metrics
326
+ arr: {
327
+ name: 'arr',
328
+ expression: 'mrr * 12',
329
+ measures: ['mrr'],
330
+ type: 'currency',
331
+ description: 'Annual recurring revenue',
332
+ },
333
+ netNewMrr: {
334
+ name: 'netNewMrr',
335
+ expression: 'newMrr + expansionMrr - contractionMrr - churnedMrr',
336
+ measures: ['newMrr', 'expansionMrr', 'contractionMrr', 'churnedMrr'],
337
+ type: 'currency',
338
+ description: 'Net new MRR',
339
+ },
340
+ arpu: {
341
+ name: 'arpu',
342
+ expression: 'mrr / customers',
343
+ measures: ['mrr', 'customers'],
344
+ type: 'currency',
345
+ description: 'Average revenue per user',
346
+ },
347
+
348
+ // Margin metrics
349
+ grossProfit: {
350
+ name: 'grossProfit',
351
+ expression: 'revenue - cogs',
352
+ measures: ['revenue', 'cogs'],
353
+ type: 'currency',
354
+ description: 'Gross profit',
355
+ },
356
+ grossMargin: {
357
+ name: 'grossMargin',
358
+ expression: '(revenue - cogs) / revenue * 100',
359
+ measures: ['revenue', 'cogs'],
360
+ type: 'percent',
361
+ description: 'Gross margin percentage',
362
+ },
363
+
364
+ // Efficiency metrics
365
+ cac: {
366
+ name: 'cac',
367
+ expression: '(salesSpend + marketingSpend) / newCustomers',
368
+ measures: ['salesSpend', 'marketingSpend', 'newCustomers'],
369
+ type: 'currency',
370
+ description: 'Customer acquisition cost',
371
+ },
372
+ ltv: {
373
+ name: 'ltv',
374
+ expression: 'arpu * grossMargin / 100 / churnRate',
375
+ measures: ['arpu', 'grossMargin'],
376
+ type: 'currency',
377
+ description: 'Customer lifetime value',
378
+ },
379
+ ltvCacRatio: {
380
+ name: 'ltvCacRatio',
381
+ expression: 'ltv / cac',
382
+ measures: ['ltv', 'cac'],
383
+ type: 'number',
384
+ description: 'LTV:CAC ratio',
385
+ },
386
+
387
+ // Churn metrics
388
+ customerChurnRate: {
389
+ name: 'customerChurnRate',
390
+ expression: 'churnedCustomers / customers * 100',
391
+ measures: ['churnedCustomers', 'customers'],
392
+ type: 'percent',
393
+ description: 'Customer churn rate',
394
+ },
395
+ revenueChurnRate: {
396
+ name: 'revenueChurnRate',
397
+ expression: 'churnedMrr / mrr * 100',
398
+ measures: ['churnedMrr', 'mrr'],
399
+ type: 'percent',
400
+ description: 'Revenue churn rate',
401
+ },
402
+ nrr: {
403
+ name: 'nrr',
404
+ expression: '(mrr + expansionMrr - contractionMrr - churnedMrr) / mrr * 100',
405
+ measures: ['mrr', 'expansionMrr', 'contractionMrr', 'churnedMrr'],
406
+ type: 'percent',
407
+ description: 'Net revenue retention',
408
+ },
409
+
410
+ // Growth metrics
411
+ quickRatio: {
412
+ name: 'quickRatio',
413
+ expression: '(newMrr + expansionMrr) / (contractionMrr + churnedMrr)',
414
+ measures: ['newMrr', 'expansionMrr', 'contractionMrr', 'churnedMrr'],
415
+ type: 'number',
416
+ description: 'SaaS Quick Ratio',
417
+ },
418
+ magicNumber: {
419
+ name: 'magicNumber',
420
+ expression: 'netNewMrr * 12 / (salesSpend + marketingSpend)',
421
+ measures: ['netNewMrr', 'salesSpend', 'marketingSpend'],
422
+ type: 'number',
423
+ description: 'Magic Number',
424
+ },
425
+ }
426
+
427
+ // =============================================================================
428
+ // Query Builder Functions
429
+ // =============================================================================
430
+
431
+ /**
432
+ * Create a query
433
+ */
434
+ export function query(name: string, source: string): QueryBuilder {
435
+ return new QueryBuilder(name, source)
436
+ }
437
+
438
+ /**
439
+ * Fluent query builder
440
+ */
441
+ export class QueryBuilder {
442
+ private _query: Query
443
+
444
+ constructor(name: string, source: string) {
445
+ this._query = { name, source }
446
+ }
447
+
448
+ describe(description: string): this {
449
+ this._query.description = description
450
+ return this
451
+ }
452
+
453
+ dimensions(...dims: string[]): this {
454
+ this._query.dimensions = dims
455
+ return this
456
+ }
457
+
458
+ measures(...measures: string[]): this {
459
+ this._query.measures = measures
460
+ return this
461
+ }
462
+
463
+ filter(field: string, operator: Operator, value: unknown): this {
464
+ if (!this._query.filters) this._query.filters = []
465
+ this._query.filters.push({ field, operator, value })
466
+ return this
467
+ }
468
+
469
+ where(filters: Filter[]): this {
470
+ this._query.filters = filters
471
+ return this
472
+ }
473
+
474
+ timeRange(field: string, start?: Date | string, end?: Date | string, granularity?: Granularity): this {
475
+ this._query.timeRange = { field, start, end, granularity }
476
+ return this
477
+ }
478
+
479
+ last(duration: string, field: string = 'date'): this {
480
+ this._query.timeRange = { field, start: `-${duration}` }
481
+ return this
482
+ }
483
+
484
+ sort(field: string, direction: SortDirection = 'desc'): this {
485
+ if (!this._query.sort) this._query.sort = []
486
+ this._query.sort.push({ field, direction })
487
+ return this
488
+ }
489
+
490
+ limit(n: number): this {
491
+ this._query.limit = n
492
+ return this
493
+ }
494
+
495
+ offset(n: number): this {
496
+ this._query.offset = n
497
+ return this
498
+ }
499
+
500
+ tags(...tags: string[]): this {
501
+ this._query.tags = tags
502
+ return this
503
+ }
504
+
505
+ owner(owner: string): this {
506
+ this._query.owner = owner
507
+ return this
508
+ }
509
+
510
+ build(): Query {
511
+ return { ...this._query }
512
+ }
513
+ }
514
+
515
+ // =============================================================================
516
+ // Pre-built SaaS Metric Queries
517
+ // =============================================================================
518
+
519
+ /**
520
+ * MRR Overview query
521
+ */
522
+ export const MrrOverview = query('mrr_overview', 'revenue_events')
523
+ .describe('Monthly recurring revenue breakdown')
524
+ .dimensions('month')
525
+ .measures('mrr', 'newMrr', 'expansionMrr', 'contractionMrr', 'churnedMrr', 'netNewMrr')
526
+ .last('12m')
527
+ .sort('month', 'asc')
528
+ .build()
529
+
530
+ /**
531
+ * ARR by segment query
532
+ */
533
+ export const ArrBySegment = query('arr_by_segment', 'revenue_events')
534
+ .describe('Annual recurring revenue by customer segment')
535
+ .dimensions('customerSegment')
536
+ .measures('arr', 'customers', 'arpu')
537
+ .last('1m')
538
+ .sort('arr', 'desc')
539
+ .build()
540
+
541
+ /**
542
+ * Customer cohort retention query
543
+ */
544
+ export const CohortRetention = query('cohort_retention', 'customer_events')
545
+ .describe('Customer retention by signup cohort')
546
+ .dimensions('cohort', 'month')
547
+ .measures('customers', 'mrr')
548
+ .last('12m')
549
+ .sort('cohort', 'asc')
550
+ .build()
551
+
552
+ /**
553
+ * Unit economics query
554
+ */
555
+ export const UnitEconomics = query('unit_economics', 'financial_events')
556
+ .describe('Key unit economics metrics')
557
+ .dimensions('month')
558
+ .measures('cac', 'ltv', 'ltvCacRatio', 'arpu', 'customerChurnRate')
559
+ .last('12m')
560
+ .sort('month', 'asc')
561
+ .build()
562
+
563
+ /**
564
+ * Revenue by channel query
565
+ */
566
+ export const RevenueByChannel = query('revenue_by_channel', 'revenue_events')
567
+ .describe('Revenue breakdown by acquisition channel')
568
+ .dimensions('channel')
569
+ .measures('mrr', 'newCustomers', 'cac')
570
+ .last('3m')
571
+ .sort('mrr', 'desc')
572
+ .build()
573
+
574
+ /**
575
+ * Growth metrics query
576
+ */
577
+ export const GrowthMetrics = query('growth_metrics', 'financial_events')
578
+ .describe('Key growth and efficiency metrics')
579
+ .dimensions('month')
580
+ .measures('mrr', 'netNewMrr', 'quickRatio', 'nrr', 'magicNumber')
581
+ .last('12m')
582
+ .sort('month', 'asc')
583
+ .build()
584
+
585
+ // =============================================================================
586
+ // View Builder
587
+ // =============================================================================
588
+
589
+ /**
590
+ * Create a view from a query
591
+ */
592
+ export function view(name: string, queryDef: Query): ViewBuilder {
593
+ return new ViewBuilder(name, queryDef)
594
+ }
595
+
596
+ /**
597
+ * Fluent view builder
598
+ */
599
+ export class ViewBuilder {
600
+ private _view: View
601
+
602
+ constructor(name: string, queryDef: Query) {
603
+ this._view = { name, query: queryDef }
604
+ }
605
+
606
+ describe(description: string): this {
607
+ this._view.description = description
608
+ return this
609
+ }
610
+
611
+ materialize(refreshInterval?: string, retention?: string): this {
612
+ this._view.materialized = true
613
+ this._view.refreshInterval = refreshInterval
614
+ this._view.retention = retention
615
+ return this
616
+ }
617
+
618
+ public(): this {
619
+ this._view.public = true
620
+ return this
621
+ }
622
+
623
+ owner(owner: string): this {
624
+ this._view.owner = owner
625
+ return this
626
+ }
627
+
628
+ tags(...tags: string[]): this {
629
+ this._view.tags = tags
630
+ return this
631
+ }
632
+
633
+ build(): View {
634
+ return { ...this._view }
635
+ }
636
+ }
637
+
638
+ // =============================================================================
639
+ // Dashboard Builder
640
+ // =============================================================================
641
+
642
+ /**
643
+ * Create a dashboard
644
+ */
645
+ export function dashboard(name: string): DashboardBuilder {
646
+ return new DashboardBuilder(name)
647
+ }
648
+
649
+ /**
650
+ * Fluent dashboard builder
651
+ */
652
+ export class DashboardBuilder {
653
+ private _dashboard: Dashboard
654
+
655
+ constructor(name: string) {
656
+ this._dashboard = { name, views: [] }
657
+ }
658
+
659
+ describe(description: string): this {
660
+ this._dashboard.description = description
661
+ return this
662
+ }
663
+
664
+ add(viewDef: View, options?: { x?: number; y?: number; width?: number; height?: number; visualization?: Visualization }): this {
665
+ this._dashboard.views.push(viewDef)
666
+ if (options && this._dashboard.layout) {
667
+ this._dashboard.layout.items.push({
668
+ viewName: viewDef.name,
669
+ x: options.x || 0,
670
+ y: options.y || 0,
671
+ width: options.width || 1,
672
+ height: options.height || 1,
673
+ visualization: options.visualization,
674
+ })
675
+ }
676
+ return this
677
+ }
678
+
679
+ layout(columns: number, rows: number): this {
680
+ this._dashboard.layout = { columns, rows, items: [] }
681
+ return this
682
+ }
683
+
684
+ refresh(interval: string): this {
685
+ this._dashboard.refreshInterval = interval
686
+ return this
687
+ }
688
+
689
+ owner(owner: string): this {
690
+ this._dashboard.owner = owner
691
+ return this
692
+ }
693
+
694
+ tags(...tags: string[]): this {
695
+ this._dashboard.tags = tags
696
+ return this
697
+ }
698
+
699
+ build(): Dashboard {
700
+ return { ...this._dashboard }
701
+ }
702
+ }
703
+
704
+ // =============================================================================
705
+ // Pre-built Dashboards
706
+ // =============================================================================
707
+
708
+ /**
709
+ * Executive SaaS Dashboard
710
+ */
711
+ export const ExecutiveDashboard = dashboard('executive')
712
+ .describe('Executive overview of key SaaS metrics')
713
+ .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' })
719
+ .refresh('5m')
720
+ .tags('executive', 'saas', 'metrics')
721
+ .build()
722
+
723
+ // =============================================================================
724
+ // Query Execution Types (Interface with ai-database)
725
+ // =============================================================================
726
+
727
+ /**
728
+ * Query result row
729
+ */
730
+ export type QueryRow = Record<string, unknown>
731
+
732
+ /**
733
+ * Query result
734
+ */
735
+ export interface QueryResult {
736
+ query: Query
737
+ rows: QueryRow[]
738
+ rowCount: number
739
+ executionTimeMs: number
740
+ cached?: boolean
741
+ metadata?: Record<string, unknown>
742
+ }
743
+
744
+ /**
745
+ * Query executor interface (implemented by ai-database)
746
+ */
747
+ export interface QueryExecutor {
748
+ execute(query: Query): Promise<QueryResult>
749
+ explain(query: Query): Promise<string>
750
+ validate(query: Query): Promise<{ valid: boolean; errors?: string[] }>
751
+ }
752
+
753
+ /**
754
+ * Streaming query result
755
+ */
756
+ export interface StreamingQueryResult {
757
+ query: Query
758
+ stream: AsyncIterable<QueryRow>
759
+ cancel: () => void
760
+ }
761
+
762
+ /**
763
+ * Streaming query executor interface
764
+ */
765
+ export interface StreamingQueryExecutor extends QueryExecutor {
766
+ stream(query: Query): StreamingQueryResult
767
+ }