@revos/cli 0.2.1 → 0.2.3

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 (162) hide show
  1. package/README.md +289 -77
  2. package/dist/adapters/oclif/commands/action-runs/get.mjs +1 -1
  3. package/dist/adapters/oclif/commands/action-runs/list.mjs +8 -2
  4. package/dist/adapters/oclif/commands/actions/get-input-schema.mjs +2 -2
  5. package/dist/adapters/oclif/commands/actions/get-params-schema.mjs +2 -2
  6. package/dist/adapters/oclif/commands/actions/get.mjs +1 -1
  7. package/dist/adapters/oclif/commands/actions/list.mjs +8 -4
  8. package/dist/adapters/oclif/commands/ai-instructions/create.mjs +1 -1
  9. package/dist/adapters/oclif/commands/ai-instructions/delete.mjs +1 -1
  10. package/dist/adapters/oclif/commands/ai-instructions/get.mjs +1 -1
  11. package/dist/adapters/oclif/commands/ai-instructions/list.mjs +8 -2
  12. package/dist/adapters/oclif/commands/ai-instructions/update.mjs +1 -1
  13. package/dist/adapters/oclif/commands/api.d.mts +11 -0
  14. package/dist/adapters/oclif/commands/api.mjs +112 -0
  15. package/dist/adapters/oclif/commands/apply.d.mts +29 -0
  16. package/dist/adapters/oclif/commands/apply.mjs +77 -0
  17. package/dist/adapters/oclif/commands/auth/login.d.mts +6 -4
  18. package/dist/adapters/oclif/commands/auth/login.mjs +23 -11
  19. package/dist/adapters/oclif/commands/auth/logout.d.mts +2 -1
  20. package/dist/adapters/oclif/commands/auth/logout.mjs +3 -2
  21. package/dist/adapters/oclif/commands/auth/status.d.mts +4 -2
  22. package/dist/adapters/oclif/commands/auth/status.mjs +23 -3
  23. package/dist/adapters/oclif/commands/connections/create.d.mts +6 -0
  24. package/dist/adapters/oclif/commands/connections/create.mjs +8 -0
  25. package/dist/adapters/oclif/commands/connections/delete.d.mts +6 -0
  26. package/dist/adapters/oclif/commands/connections/delete.mjs +8 -0
  27. package/dist/adapters/oclif/commands/connections/get.d.mts +6 -0
  28. package/dist/adapters/oclif/commands/connections/get.mjs +8 -0
  29. package/dist/adapters/oclif/commands/connections/list.d.mts +6 -0
  30. package/dist/adapters/oclif/commands/connections/list.mjs +14 -0
  31. package/dist/adapters/oclif/commands/connections/update.d.mts +6 -0
  32. package/dist/adapters/oclif/commands/connections/update.mjs +8 -0
  33. package/dist/adapters/oclif/commands/cubes/create.d.mts +6 -0
  34. package/dist/adapters/oclif/commands/cubes/create.mjs +8 -0
  35. package/dist/adapters/oclif/commands/cubes/delete.d.mts +6 -0
  36. package/dist/adapters/oclif/commands/cubes/delete.mjs +8 -0
  37. package/dist/adapters/oclif/commands/cubes/get.d.mts +6 -0
  38. package/dist/adapters/oclif/commands/cubes/get.mjs +8 -0
  39. package/dist/adapters/oclif/commands/cubes/list.d.mts +6 -0
  40. package/dist/adapters/oclif/commands/cubes/list.mjs +13 -0
  41. package/dist/adapters/oclif/commands/cubes/update.d.mts +6 -0
  42. package/dist/adapters/oclif/commands/cubes/update.mjs +8 -0
  43. package/dist/adapters/oclif/commands/diff.d.mts +28 -0
  44. package/dist/adapters/oclif/commands/diff.mjs +66 -0
  45. package/dist/adapters/oclif/commands/gservice-account-keys/get.mjs +1 -1
  46. package/dist/adapters/oclif/commands/gservice-account-keys/reveal.mjs +2 -2
  47. package/dist/adapters/oclif/commands/gservice-accounts/create.mjs +1 -1
  48. package/dist/adapters/oclif/commands/gservice-accounts/delete.mjs +1 -1
  49. package/dist/adapters/oclif/commands/gservice-accounts/get.mjs +1 -1
  50. package/dist/adapters/oclif/commands/gservice-accounts/list.mjs +7 -2
  51. package/dist/adapters/oclif/commands/init.d.mts +3 -1
  52. package/dist/adapters/oclif/commands/init.mjs +27 -23
  53. package/dist/adapters/oclif/commands/org/create.mjs +3 -2
  54. package/dist/adapters/oclif/commands/org/current.d.mts +12 -3
  55. package/dist/adapters/oclif/commands/org/current.mjs +27 -2
  56. package/dist/adapters/oclif/commands/org/get.mjs +3 -2
  57. package/dist/adapters/oclif/commands/org/list.d.mts +3 -11
  58. package/dist/adapters/oclif/commands/org/list.mjs +35 -26
  59. package/dist/adapters/oclif/commands/org/switch.d.mts +4 -2
  60. package/dist/adapters/oclif/commands/org/switch.mjs +16 -3
  61. package/dist/adapters/oclif/commands/pull.d.mts +29 -0
  62. package/dist/adapters/oclif/commands/pull.mjs +88 -0
  63. package/dist/adapters/oclif/commands/score-groups/create.mjs +3 -2
  64. package/dist/adapters/oclif/commands/score-groups/delete.mjs +1 -1
  65. package/dist/adapters/oclif/commands/score-groups/get.mjs +1 -1
  66. package/dist/adapters/oclif/commands/score-groups/list.mjs +3 -2
  67. package/dist/adapters/oclif/commands/score-groups/update.mjs +1 -1
  68. package/dist/adapters/oclif/commands/scores/create.mjs +3 -2
  69. package/dist/adapters/oclif/commands/scores/delete.mjs +1 -1
  70. package/dist/adapters/oclif/commands/scores/list.mjs +3 -2
  71. package/dist/adapters/oclif/commands/scores/update.mjs +1 -1
  72. package/dist/adapters/oclif/commands/segments/create.mjs +1 -1
  73. package/dist/adapters/oclif/commands/segments/delete.mjs +1 -1
  74. package/dist/adapters/oclif/commands/segments/evaluate.mjs +2 -2
  75. package/dist/adapters/oclif/commands/segments/get-evaluation-history.mjs +2 -2
  76. package/dist/adapters/oclif/commands/segments/get-version.mjs +2 -2
  77. package/dist/adapters/oclif/commands/segments/get.mjs +1 -1
  78. package/dist/adapters/oclif/commands/segments/list-versions.mjs +16 -5
  79. package/dist/adapters/oclif/commands/segments/list.mjs +9 -2
  80. package/dist/adapters/oclif/commands/segments/restore-version.mjs +2 -2
  81. package/dist/adapters/oclif/commands/segments/update.mjs +1 -1
  82. package/dist/adapters/oclif/commands/sources/create.d.mts +11 -0
  83. package/dist/adapters/oclif/commands/sources/create.mjs +16 -0
  84. package/dist/adapters/oclif/commands/sources/delete.d.mts +6 -0
  85. package/dist/adapters/oclif/commands/sources/delete.mjs +8 -0
  86. package/dist/adapters/oclif/commands/sources/get.d.mts +6 -0
  87. package/dist/adapters/oclif/commands/sources/get.mjs +8 -0
  88. package/dist/adapters/oclif/commands/sources/list-streams.d.mts +6 -0
  89. package/dist/adapters/oclif/commands/sources/list-streams.mjs +31 -0
  90. package/dist/adapters/oclif/commands/sources/list.d.mts +6 -0
  91. package/dist/adapters/oclif/commands/sources/list.mjs +13 -0
  92. package/dist/adapters/oclif/commands/{integrations/get.d.mts → sources/update.d.mts} +4 -4
  93. package/dist/adapters/oclif/commands/sources/update.mjs +21 -0
  94. package/dist/adapters/oclif/commands/status.d.mts +27 -0
  95. package/dist/adapters/oclif/commands/status.mjs +77 -0
  96. package/dist/adapters/oclif/commands/table-views/create.mjs +3 -2
  97. package/dist/adapters/oclif/commands/table-views/delete.mjs +1 -1
  98. package/dist/adapters/oclif/commands/table-views/list.mjs +3 -2
  99. package/dist/adapters/oclif/commands/table-views/update.mjs +1 -1
  100. package/dist/adapters/oclif/commands/tables/create.mjs +1 -1
  101. package/dist/adapters/oclif/commands/tables/delete.mjs +1 -1
  102. package/dist/adapters/oclif/commands/tables/get.mjs +1 -1
  103. package/dist/adapters/oclif/commands/tables/list.mjs +3 -2
  104. package/dist/adapters/oclif/commands/tables/update.mjs +1 -1
  105. package/dist/{base.command-d7VW6WTp.d.mts → base.command-BmddDbHa.d.mts} +4 -1
  106. package/dist/base.command-D8taHOFF.mjs +83 -0
  107. package/dist/chunk-CfYAbeIz.mjs +13 -0
  108. package/dist/context-D5uelKLe.d.mts +62 -0
  109. package/dist/core-B-IdeRNl.mjs +2448 -0
  110. package/dist/{factory-BrFKT8t-.mjs → factory-CCcimDhl.mjs} +45 -10
  111. package/dist/iac-render-BSZZEP0n.mjs +17 -0
  112. package/dist/index-D0ax2I61.d.mts +581 -0
  113. package/dist/index.d.mts +4 -4
  114. package/dist/index.mjs +2 -2
  115. package/dist/{presets-D9b6IWKy.mjs → presets-Bb9gwgeh.mjs} +40 -8
  116. package/dist/templates/.claude/settings.json +39 -0
  117. package/dist/templates/.devcontainer/Dockerfile +9 -0
  118. package/dist/templates/.devcontainer/devcontainer.json +4 -1
  119. package/dist/templates/.devcontainer/setup.sh +3 -0
  120. package/dist/templates/AGENTS.md +33 -20
  121. package/dist/templates/dbt/dbt_project.yml +2 -2
  122. package/dist/templates/gitignore +3 -1
  123. package/dist/templates/skills/create-connections/SKILL.md +210 -0
  124. package/dist/templates/skills/create-connections/references/mappers.md +152 -0
  125. package/dist/templates/skills/{create-semantic-model → create-cubes}/SKILL.md +20 -18
  126. package/dist/templates/skills/create-cubes/references/bq-pk-fk-conventions.md +183 -0
  127. package/dist/templates/skills/{create-semantic-model → create-cubes}/references/cube-examples.md +2 -2
  128. package/dist/templates/skills/create-cubes/references/hubspot-entities.md +289 -0
  129. package/dist/templates/skills/create-cubes/references/jira-entities.md +201 -0
  130. package/dist/templates/skills/create-cubes/references/netsuite-entities.md +121 -0
  131. package/dist/templates/skills/create-cubes/references/stripe-entities.md +114 -0
  132. package/dist/templates/skills/create-dbt-transformations/SKILL.md +43 -22
  133. package/dist/templates/skills/create-dbt-transformations/references/edge-cases.md +20 -2
  134. package/dist/templates/skills/create-dbt-transformations/references/schema-conventions.md +21 -7
  135. package/dist/templates/skills/create-dbt-transformations/references/sql-templates.md +34 -20
  136. package/dist/templates/skills/explore-lakehouse/SKILL.md +3 -3
  137. package/dist/templates/skills/load-sample-data/SKILL.md +1 -1
  138. package/dist/templates/skills/visualize-semantic-model/SKILL.md +159 -0
  139. package/dist/templates/skills/visualize-semantic-model/scripts/render_graph.py +186 -0
  140. package/dist/{types-Y_ht_ja5.d.mts → types-Bk2Cb5yt.d.mts} +9 -0
  141. package/package.json +44 -7
  142. package/dist/adapters/oclif/commands/integrations/create.d.mts +0 -11
  143. package/dist/adapters/oclif/commands/integrations/create.mjs +0 -16
  144. package/dist/adapters/oclif/commands/integrations/get.mjs +0 -21
  145. package/dist/adapters/oclif/commands/integrations/list.d.mts +0 -11
  146. package/dist/adapters/oclif/commands/integrations/list.mjs +0 -16
  147. package/dist/adapters/oclif/commands/integrations/update.d.mts +0 -15
  148. package/dist/adapters/oclif/commands/integrations/update.mjs +0 -21
  149. package/dist/adapters/oclif/commands/overlays/diff.d.mts +0 -19
  150. package/dist/adapters/oclif/commands/overlays/diff.mjs +0 -80
  151. package/dist/adapters/oclif/commands/overlays/pull.d.mts +0 -15
  152. package/dist/adapters/oclif/commands/overlays/pull.mjs +0 -45
  153. package/dist/adapters/oclif/commands/overlays/push.d.mts +0 -18
  154. package/dist/adapters/oclif/commands/overlays/push.mjs +0 -59
  155. package/dist/adapters/oclif/commands/overlays/status.d.mts +0 -18
  156. package/dist/adapters/oclif/commands/overlays/status.mjs +0 -53
  157. package/dist/base.command-YiwlGlKs.mjs +0 -62
  158. package/dist/core-jpFPylBb.mjs +0 -997
  159. package/dist/index-DD2Vr-pu.d.mts +0 -193
  160. package/dist/types-C_p_6rkj.d.mts +0 -69
  161. /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/key-patterns.md +0 -0
  162. /package/dist/templates/skills/{create-semantic-model → create-cubes}/references/validation-queries.md +0 -0
@@ -0,0 +1,289 @@
1
+ # HubSpot Entities Reference
2
+
3
+ ## Table naming
4
+
5
+ Airbyte syncs HubSpot tables with a configurable prefix (default: `hubspot_`).
6
+ Inspect the BigQuery dataset to identify the actual prefix:
7
+
8
+ ```sql
9
+ SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
10
+ WHERE table_name LIKE '%companies%' OR table_name LIKE '%deals%'
11
+ ORDER BY table_name LIMIT 20;
12
+ ```
13
+
14
+ Throughout this document `<prefix>` is a placeholder for that prefix (e.g. `hubspot_`).
15
+
16
+ ---
17
+
18
+ ## Primary entities
19
+
20
+ | Cube name | BigQuery table | PK | Notes |
21
+ | ------------------------ | ------------------------ | ------------ | -------------------------------------------------- | --- | --- | --- | --------- |
22
+ | `<prefix>companies` | `<prefix>companies` | `id` | `properties_name` is the display name |
23
+ | `<prefix>contacts` | `<prefix>contacts` | `id` | `properties_hs_full_name_or_email` is display name |
24
+ | `<prefix>deals` | `<prefix>deals` | `id` | `properties_dealname` is display name |
25
+ | `<prefix>tickets` | `<prefix>tickets` | `id` | — |
26
+ | `<prefix>owners` | `<prefix>owners` | `id` | Display name: `firstName | | ' ' | | lastName` |
27
+ | `<prefix>engagements` | `<prefix>engagements` | `id` | See engagement sub-types below |
28
+ | `<prefix>deal_pipelines` | `<prefix>deal_pipelines` | `pipelineId` | Stages stored as JSON array |
29
+ | `<prefix>line_items` | `<prefix>line_items` | `id` | `properties_name` |
30
+ | `<prefix>products` | `<prefix>products` | `id` | `properties_name` |
31
+
32
+ **Owner join pattern (shared by companies, contacts, deals, tickets):**
33
+
34
+ ```yaml
35
+ joins:
36
+ <prefix>owners:
37
+ relationship: many_to_one
38
+ sql: "${CUBE}.properties_hubspot_owner_id = ${<prefix>owners.id}"
39
+ ```
40
+
41
+ ---
42
+
43
+ ## Bridge / junction cubes (public: false)
44
+
45
+ HubSpot stores many-to-many associations as JSON arrays on the primary object.
46
+ Bridge cubes are required to join across these associations. They must be
47
+ `public: false` and use a composite PK.
48
+
49
+ ### Association columns
50
+
51
+ | Source table | Column | Contains |
52
+ | --------------------- | ------------------------- | --------------------------------------- |
53
+ | `<prefix>deals` | `companies` | JSON array of company IDs |
54
+ | `<prefix>deals` | `contacts` | JSON array of contact IDs |
55
+ | `<prefix>deals` | `line_items` | JSON array of line item IDs |
56
+ | `<prefix>deals` | `deals` | JSON array (for tickets→deals) |
57
+ | `<prefix>tickets` | `companies` | JSON array of company IDs |
58
+ | `<prefix>tickets` | `contacts` | JSON array of contact IDs |
59
+ | `<prefix>tickets` | `deals` | JSON array of deal IDs (CAST to STRING) |
60
+ | `<prefix>companies` | `contacts` | JSON array of contact IDs |
61
+ | `<prefix>engagements` | `associations.contactIds` | JSON array of contact IDs |
62
+ | `<prefix>engagements` | `associations.companyIds` | JSON array of company IDs |
63
+ | `<prefix>engagements` | `associations.dealIds` | JSON array of deal IDs |
64
+
65
+ ### Bridge cube: companies_to_deals
66
+
67
+ ```yaml
68
+ name: <prefix>companies_to_deals
69
+ sql: >
70
+ SELECT DISTINCT d.id as deal_id, company_id
71
+ FROM `<dataset>.<prefix>deals` d,
72
+ UNNEST(JSON_VALUE_ARRAY(d.companies)) company_id
73
+ public: false
74
+ dimensions:
75
+ id:
76
+ sql: "${CUBE.company_id} || ${CUBE.deal_id}"
77
+ type: string
78
+ primary_key: true
79
+ company_id:
80
+ sql: "${CUBE}.company_id"
81
+ type: string
82
+ deal_id:
83
+ sql: "${CUBE}.deal_id"
84
+ type: string
85
+ joins:
86
+ <prefix>companies:
87
+ relationship: many_to_one
88
+ sql: "${CUBE}.company_id = ${<prefix>companies.id}"
89
+ <prefix>deals:
90
+ relationship: many_to_one
91
+ sql: "${CUBE}.deal_id = ${<prefix>deals.id}"
92
+ refresh_key:
93
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<prefix>deals`"
94
+ ```
95
+
96
+ ### Bridge cube: companies_to_tickets
97
+
98
+ Same pattern — UNNEST `tickets.companies`:
99
+
100
+ ```yaml
101
+ name: <prefix>companies_to_tickets
102
+ sql: >
103
+ SELECT DISTINCT t.id as ticket_id, company_id
104
+ FROM `<dataset>.<prefix>tickets` t,
105
+ UNNEST(JSON_VALUE_ARRAY(t.companies)) company_id
106
+ ```
107
+
108
+ ### Bridge cube: deals_to_tickets
109
+
110
+ Note: ticket `deals` column values are numbers — cast to STRING:
111
+
112
+ ```yaml
113
+ name: <prefix>deals_to_tickets
114
+ sql: >
115
+ SELECT DISTINCT t.id AS ticket_id, CAST(deal_id AS STRING) AS deal_id
116
+ FROM `<dataset>.<prefix>tickets` t,
117
+ UNNEST(JSON_VALUE_ARRAY(t.deals)) AS deal_id
118
+ ```
119
+
120
+ ### Bridge cube: deals_to_line_items
121
+
122
+ ```yaml
123
+ name: <prefix>deals_to_line_items
124
+ sql: >
125
+ SELECT DISTINCT d.id AS deal_id, line_item_id
126
+ FROM `<dataset>.<prefix>deals` d,
127
+ UNNEST(JSON_VALUE_ARRAY(d.line_items)) AS line_item_id
128
+ ```
129
+
130
+ ### Bridge cube: contacts_to_deals
131
+
132
+ ```yaml
133
+ name: <prefix>contacts_to_deals
134
+ sql: >
135
+ SELECT DISTINCT d.id AS deal_id, contact_id
136
+ FROM `<dataset>.<prefix>deals` d,
137
+ UNNEST(JSON_VALUE_ARRAY(d.contacts)) contact_id
138
+ ```
139
+
140
+ ### Bridge cube: contacts_to_tickets
141
+
142
+ ```yaml
143
+ name: <prefix>contacts_to_tickets
144
+ sql: >
145
+ SELECT DISTINCT t.id AS ticket_id, contact_id
146
+ FROM `<dataset>.<prefix>tickets` t,
147
+ UNNEST(JSON_VALUE_ARRAY(t.contacts)) contact_id
148
+ ```
149
+
150
+ ### Bridge cube: contacts_to_companies
151
+
152
+ Note: this uses `SAFE_CAST` on both sides — IDs can have type mismatches:
153
+
154
+ ```yaml
155
+ name: <prefix>contacts_to_companies
156
+ sql: >
157
+ SELECT DISTINCT c.id AS company_id, contact_id
158
+ FROM `<dataset>.<prefix>companies` c,
159
+ UNNEST(JSON_VALUE_ARRAY(c.contacts)) AS contact_id
160
+ joins:
161
+ <prefix>contacts:
162
+ relationship: many_to_one
163
+ sql: "SAFE_CAST(${CUBE}.contact_id AS STRING) = SAFE_CAST(${<prefix>contacts.id} AS STRING)"
164
+ <prefix>companies:
165
+ relationship: many_to_one
166
+ sql: "SAFE_CAST(${CUBE}.company_id AS STRING) = SAFE_CAST(${<prefix>companies.id} AS STRING)"
167
+ ```
168
+
169
+ ### Bridge cubes: engagements_to_contacts / companies / deals
170
+
171
+ Engagement IDs are integers — always CAST to STRING:
172
+
173
+ ```yaml
174
+ name: <prefix>engagements_to_contacts
175
+ sql: >
176
+ SELECT DISTINCT
177
+ CAST(e.id AS STRING) AS engagement_id,
178
+ CAST(contact_id AS STRING) AS contact_id
179
+ FROM `<dataset>.<prefix>engagements` e,
180
+ UNNEST(JSON_VALUE_ARRAY(e.associations.contactIds)) AS contact_id
181
+ ```
182
+
183
+ Same pattern for `companyIds` → `engagements_to_companies` and `dealIds` → `engagements_to_deals`.
184
+
185
+ Engagement join:
186
+
187
+ ```yaml
188
+ joins:
189
+ <prefix>engagements:
190
+ relationship: many_to_one
191
+ sql: "CAST(${CUBE}.engagement_id AS STRING) = CAST(${<prefix>engagements.id} AS STRING)"
192
+ ```
193
+
194
+ ---
195
+
196
+ ## Special cubes
197
+
198
+ ### deal_pipeline_stages
199
+
200
+ Derived from `deal_pipelines.stages` JSON array. Not a raw table — uses `sql:` not `sql_table:`.
201
+
202
+ ```yaml
203
+ name: <prefix>deal_pipeline_stages
204
+ sql: >
205
+ SELECT
206
+ JSON_VALUE(elem, '$.stageId') AS stage_id,
207
+ JSON_VALUE(elem, '$.label') AS label
208
+ FROM `<dataset>.<prefix>deal_pipelines`,
209
+ UNNEST(JSON_QUERY_ARRAY(stages)) AS elem
210
+ dimensions:
211
+ stage_id:
212
+ sql: "${CUBE}.stage_id"
213
+ type: string
214
+ primary_key: true
215
+ label:
216
+ sql: "${CUBE}.label"
217
+ type: string
218
+ joins:
219
+ <prefix>deals:
220
+ relationship: one_to_many
221
+ sql: "${CUBE}.stage_id = ${<prefix>deals.properties_dealstage}"
222
+ refresh_key:
223
+ sql: "SELECT MAX(_airbyte_extracted_at) FROM `<dataset>.<prefix>deal_pipelines`"
224
+ ```
225
+
226
+ Deals join to stages and pipelines:
227
+
228
+ ```yaml
229
+ joins:
230
+ <prefix>deal_pipeline_stages:
231
+ relationship: many_to_one
232
+ sql: "${CUBE}.properties_dealstage = ${<prefix>deal_pipeline_stages.stage_id}"
233
+ <prefix>deal_pipelines:
234
+ relationship: many_to_one
235
+ sql: "${CUBE}.properties_pipeline = ${<prefix>deal_pipelines.pipelineId}"
236
+ ```
237
+
238
+ ### engagements sub-types
239
+
240
+ `engagements` table has sub-type tables: `engagements_calls`, `engagements_emails`,
241
+ `engagements_meetings`, `engagements_tasks`, `engagements_notes`.
242
+
243
+ Join pattern (one-to-one by ID with CAST):
244
+
245
+ ```yaml
246
+ # On the engagements cube:
247
+ joins:
248
+ <prefix>engagements_calls:
249
+ relationship: one_to_one
250
+ sql: "CAST(${CUBE}.id AS STRING) = ${<prefix>engagements_calls.id}"
251
+
252
+ # On each sub-type cube:
253
+ joins:
254
+ <prefix>engagements:
255
+ relationship: many_to_one
256
+ sql: "${CUBE}.id = CAST(${<prefix>engagements.id} AS STRING)"
257
+ ```
258
+
259
+ ---
260
+
261
+ ## Deals measures
262
+
263
+ ```yaml
264
+ measures:
265
+ count_closed:
266
+ type: count
267
+ filters:
268
+ - sql: "${CUBE}.properties_hs_is_closed = TRUE"
269
+ count_closed_won:
270
+ type: count
271
+ filters:
272
+ - sql: "${CUBE}.properties_hs_is_closed_won = TRUE"
273
+ count_closed_lost:
274
+ type: count
275
+ filters:
276
+ - sql: >
277
+ ${CUBE}.properties_hs_is_closed = TRUE
278
+ AND ${CUBE}.properties_hs_is_closed_won = FALSE
279
+ ```
280
+
281
+ ---
282
+
283
+ ## Common pitfalls
284
+
285
+ 1. **ID type mismatches** — HubSpot IDs are sometimes integers, sometimes strings. Use `SAFE_CAST` when unsure (especially contacts_to_companies). Engagement IDs are always integers → always CAST to STRING.
286
+ 2. **JSON_VALUE_ARRAY vs JSON_QUERY_ARRAY** — use `JSON_VALUE_ARRAY` when the array contains scalar strings/ints (association IDs); use `JSON_QUERY_ARRAY` when the array contains JSON objects (deal_pipelines stages).
287
+ 3. **deal_pipeline_stages is derived** — uses `sql:` not `sql_table:`. Cannot be used in `revos cubes preview` diff against Airbyte-generated cubes.
288
+ 4. **engagements bridge refresh_key** — use the parent engagement table timestamp, not the contact/company/deal table.
289
+ 5. **Prefix varies** — always confirm the actual prefix from BigQuery before writing cube files.
@@ -0,0 +1,201 @@
1
+ # Jira Entities Reference
2
+
3
+ ## Table naming
4
+
5
+ Airbyte syncs Jira tables with a configurable prefix (default: `jira_`).
6
+ Inspect the BigQuery dataset to find the actual prefix:
7
+
8
+ ```sql
9
+ SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
10
+ WHERE table_name LIKE '%issues%' OR table_name LIKE '%projects%'
11
+ ORDER BY table_name LIMIT 20;
12
+ ```
13
+
14
+ Throughout this document `<prefix>` is a placeholder for that prefix.
15
+
16
+ ---
17
+
18
+ ## Primary entities
19
+
20
+ | Cube name | BigQuery table | PK | Notes |
21
+ | ---------------------------- | ---------------------------- | ----------- | ------------------------------------------------ |
22
+ | `<prefix>issues` | `<prefix>issues` | `id` | `key` is display name; metadata in `fields` JSON |
23
+ | `<prefix>projects` | `<prefix>projects` | `id` | `name` |
24
+ | `<prefix>issue_types` | `<prefix>issue_types` | `id` | `name` |
25
+ | `<prefix>issue_priorities` | `<prefix>issue_priorities` | `id` | `name` |
26
+ | `<prefix>users` | `<prefix>users` | `accountId` | `displayName`; aliased 4× (see below) |
27
+ | `<prefix>sprints` | `<prefix>sprints` | `id` | `name`; FK `boardId` |
28
+ | `<prefix>boards` | `<prefix>boards` | `id` | `name`; FK `projectId` |
29
+ | `<prefix>issue_comments` | `<prefix>issue_comments` | — | FK `issueId`; `author` is JSON object |
30
+ | `<prefix>issue_worklogs` | `<prefix>issue_worklogs` | — | FK `issueId`; `author` is JSON object |
31
+ | `<prefix>project_components` | `<prefix>project_components` | — | FK `projectId` as INT64 |
32
+ | `<prefix>project_versions` | `<prefix>project_versions` | — | FK `projectId` as INT64 |
33
+
34
+ ---
35
+
36
+ ## Users table aliasing
37
+
38
+ The single `<prefix>users` table must be exposed as **separate cubes** for each
39
+ role because Cube.js does not support joining the same table twice with
40
+ different conditions. Each alias has its own cube name and `sql_table` pointing
41
+ to the same physical table.
42
+
43
+ | Cube name | Role | Join condition on `<prefix>issues` |
44
+ | ------------------------------ | -------------- | -------------------------------------------------- |
45
+ | `<prefix>users_assignee` | Assignee | `JSON_VALUE(fields, '$.assignee.accountId')` |
46
+ | `<prefix>users_reporter` | Reporter | `JSON_VALUE(fields, '$.reporter.accountId')` |
47
+ | `<prefix>users_creator` | Creator | `JSON_VALUE(fields, '$.creator.accountId')` |
48
+ | `<prefix>users_comment_author` | Comment author | `JSON_VALUE(issue_comments.author, '$.accountId')` |
49
+ | `<prefix>users_worklog_author` | Worklog author | `JSON_VALUE(issue_worklogs.author, '$.accountId')` |
50
+ | `<prefix>users` | Direct queries | (no joins defined) |
51
+
52
+ Template for each alias:
53
+
54
+ ```yaml
55
+ name: <prefix>users_assignee
56
+ sql_table: "`<dataset>.<prefix>users`"
57
+ joins:
58
+ <prefix>issues:
59
+ relationship: one_to_many
60
+ sql: >
61
+ ${CUBE}.accountId =
62
+ JSON_VALUE(${<prefix>issues.fields}, '$.assignee.accountId')
63
+ ```
64
+
65
+ Corresponding join on `<prefix>issues`:
66
+
67
+ ```yaml
68
+ joins:
69
+ <prefix>users_assignee:
70
+ relationship: many_to_one
71
+ sql: >
72
+ JSON_VALUE(${CUBE}.fields, '$.assignee.accountId') =
73
+ ${<prefix>users_assignee.accountId}
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Issues: fields JSON column
79
+
80
+ Issue metadata lives in a single `fields` JSON column. Extract with `JSON_VALUE`:
81
+
82
+ | Field | JSON path |
83
+ | ------------------- | ----------------------------------------- |
84
+ | Issue type ID | `$.issuetype.id` |
85
+ | Priority ID | `$.priority.id` |
86
+ | Assignee account ID | `$.assignee.accountId` |
87
+ | Reporter account ID | `$.reporter.accountId` |
88
+ | Creator account ID | `$.creator.accountId` |
89
+ | Status | `$.status.name` |
90
+ | Summary | `$.summary` |
91
+ | Story points | `$.story_points` or `$.customfield_10016` |
92
+
93
+ Example dimension:
94
+
95
+ ```yaml
96
+ dimensions:
97
+ status:
98
+ sql: "JSON_VALUE(${CUBE}.fields, '$.status.name')"
99
+ type: string
100
+ issue_type_name:
101
+ sql: "JSON_VALUE(${CUBE}.fields, '$.issuetype.name')"
102
+ type: string
103
+ ```
104
+
105
+ ---
106
+
107
+ ## Bridge / junction cubes (public: false)
108
+
109
+ ### sprint_issues
110
+
111
+ Sprints and issues are many-to-many. The `sprint_issues` table has columns
112
+ `issueId` (STRING) and `sprintId` (INT64). Composite PK required.
113
+
114
+ ```yaml
115
+ name: <prefix>sprint_issues
116
+ sql_table: "`<dataset>.<prefix>sprint_issues`"
117
+ public: false
118
+ dimensions:
119
+ id:
120
+ sql: "${CUBE}.issueId || '_' || CAST(${CUBE}.sprintId AS STRING)"
121
+ type: string
122
+ primary_key: true
123
+ joins:
124
+ <prefix>issues:
125
+ relationship: many_to_one
126
+ sql: "${CUBE}.issueId = ${<prefix>issues.id}"
127
+ <prefix>sprints:
128
+ relationship: many_to_one
129
+ sql: "${CUBE}.sprintId = ${<prefix>sprints.id}"
130
+ ```
131
+
132
+ Issues and sprints join through this bridge:
133
+
134
+ ```yaml
135
+ # On <prefix>issues:
136
+ joins:
137
+ <prefix>sprint_issues:
138
+ relationship: one_to_many
139
+ sql: "${CUBE}.id = ${<prefix>sprint_issues.issueId}"
140
+
141
+ # On <prefix>sprints:
142
+ joins:
143
+ <prefix>sprint_issues:
144
+ relationship: one_to_many
145
+ sql: "${CUBE}.id = ${<prefix>sprint_issues.sprintId}"
146
+ ```
147
+
148
+ ### board_issues
149
+
150
+ Board issues (`board_issues` table) link boards to issues. Composite PK uses `id` + `boardId`.
151
+
152
+ ```yaml
153
+ name: <prefix>board_issues
154
+ sql_table: "`<dataset>.<prefix>board_issues`"
155
+ public: false
156
+ dimensions:
157
+ composite_id:
158
+ sql: "${CUBE}.id || '_' || CAST(${CUBE}.boardId AS STRING)"
159
+ type: string
160
+ primary_key: true
161
+ joins:
162
+ <prefix>issues:
163
+ relationship: many_to_one
164
+ sql: "${CUBE}.id = ${<prefix>issues.id}"
165
+ <prefix>boards:
166
+ relationship: many_to_one
167
+ sql: "${CUBE}.boardId = ${<prefix>boards.id}"
168
+ ```
169
+
170
+ ---
171
+
172
+ ## Type casting pitfalls
173
+
174
+ ### project_components / project_versions → projects
175
+
176
+ `project_components.projectId` and `project_versions.projectId` are INT64 but
177
+ `projects.id` is STRING. Always cast:
178
+
179
+ ```yaml
180
+ # On <prefix>project_components:
181
+ joins:
182
+ <prefix>projects:
183
+ relationship: many_to_one
184
+ sql: "CAST(${CUBE}.projectId AS STRING) = ${<prefix>projects.id}"
185
+
186
+ # On <prefix>projects:
187
+ joins:
188
+ <prefix>project_components:
189
+ relationship: one_to_many
190
+ sql: "SAFE_CAST(${CUBE}.id AS INT64) = ${<prefix>project_components.projectId}"
191
+ ```
192
+
193
+ ---
194
+
195
+ ## Common pitfalls
196
+
197
+ 1. **`fields` JSON column** — most issue attributes live here, not as top-level columns. Always check `INFORMATION_SCHEMA` before assuming a column exists at the top level.
198
+ 2. **User aliases must be separate cubes** — do not try to join `users` twice from `issues`; Cube.js requires distinct cube names per join target.
199
+ 3. **`sprintId` is INT64** — cast to STRING in the composite PK to avoid type errors.
200
+ 4. **`issue_comments.author` is a JSON object** — extract `accountId` with `JSON_VALUE`, not a direct column reference.
201
+ 5. **`boards.projectId` vs `projects.id`** — both are strings here; no cast needed. But `project_components.projectId` is INT64 — always cast.
@@ -0,0 +1,121 @@
1
+ # NetSuite Entities Reference
2
+
3
+ ## Table naming
4
+
5
+ Airbyte syncs NetSuite tables with a configurable prefix (default: `netsuite_`).
6
+ Inspect the BigQuery dataset to confirm:
7
+
8
+ ```sql
9
+ SELECT table_name FROM `<dataset>.INFORMATION_SCHEMA.TABLES`
10
+ WHERE table_name LIKE '%customer%' OR table_name LIKE '%salesorder%'
11
+ ORDER BY table_name LIMIT 20;
12
+ ```
13
+
14
+ Throughout this document `<prefix>` is a placeholder for that prefix.
15
+
16
+ ---
17
+
18
+ ## Primary entities
19
+
20
+ | Cube name | BigQuery table | PK | Notes |
21
+ | --------------------- | --------------------- | ---- | ------------------------------ |
22
+ | `<prefix>customer` | `<prefix>customer` | `id` | FK `subsidiary` is JSON object |
23
+ | `<prefix>contact` | `<prefix>contact` | `id` | FK `company` is JSON object |
24
+ | `<prefix>opportunity` | `<prefix>opportunity` | `id` | FK `entity` is JSON object |
25
+ | `<prefix>salesorder` | `<prefix>salesorder` | `id` | FK `entity` is JSON object |
26
+ | `<prefix>employee` | `<prefix>employee` | `id` | — |
27
+
28
+ ---
29
+
30
+ ## FK extraction pattern
31
+
32
+ NetSuite stores foreign keys as **JSON objects** with an `id` field rather than
33
+ as flat FK columns. Use `JSON_VALUE` to extract the ID and expose it as a
34
+ computed dimension.
35
+
36
+ ### contact → customer
37
+
38
+ `contact.company` is a JSON object: `{"id": "123", "refName": "Acme Corp"}`.
39
+
40
+ ```yaml
41
+ name: <prefix>contact
42
+ sql_table: "`<dataset>.<prefix>contact`"
43
+ dimensions:
44
+ id:
45
+ sql: "id"
46
+ type: string
47
+ primary_key: true
48
+ customer_id:
49
+ sql: "JSON_VALUE(${CUBE}.company, '$.id')"
50
+ type: string
51
+ joins:
52
+ <prefix>customer:
53
+ relationship: many_to_one
54
+ sql: "${CUBE.customer_id} = ${<prefix>customer.id}"
55
+ ```
56
+
57
+ ### customer → subsidiary
58
+
59
+ `customer.subsidiary` is a JSON object: `{"id": "1", "refName": "Main Subsidiary"}`.
60
+
61
+ ```yaml
62
+ name: <prefix>customer
63
+ sql_table: "`<dataset>.<prefix>customer`"
64
+ dimensions:
65
+ subsidiary_id:
66
+ sql: "JSON_VALUE(${CUBE}.subsidiary, '$.id')"
67
+ type: string
68
+ joins:
69
+ <prefix>contact:
70
+ relationship: one_to_many
71
+ sql: "${CUBE}.id = ${<prefix>contact.customer_id}"
72
+ <prefix>salesorder:
73
+ relationship: one_to_many
74
+ sql: "${CUBE}.id = ${<prefix>salesorder.customer_id}"
75
+ <prefix>opportunity:
76
+ relationship: one_to_many
77
+ sql: "${CUBE}.id = ${<prefix>opportunity.customer_id}"
78
+ ```
79
+
80
+ ### opportunity / salesorder → customer
81
+
82
+ Both `opportunity` and `salesorder` use `entity` (not `company`) as the FK column:
83
+
84
+ ```yaml
85
+ name: <prefix>opportunity
86
+ sql_table: "`<dataset>.<prefix>opportunity`"
87
+ dimensions:
88
+ id:
89
+ sql: "id"
90
+ type: string
91
+ primary_key: true
92
+ customer_id:
93
+ sql: "JSON_VALUE(${CUBE}.entity, '$.id')"
94
+ type: string
95
+ joins:
96
+ <prefix>customer:
97
+ relationship: many_to_one
98
+ sql: "${CUBE.customer_id} = ${<prefix>customer.id}"
99
+ ```
100
+
101
+ Same pattern for `<prefix>salesorder`.
102
+
103
+ ---
104
+
105
+ ## Relationship graph
106
+
107
+ ```
108
+ customer ──< contact
109
+ customer ──< opportunity
110
+ customer ──< salesorder
111
+ ```
112
+
113
+ ---
114
+
115
+ ## Common pitfalls
116
+
117
+ 1. **FK columns are JSON objects** — `contact.company`, `opportunity.entity`, `salesorder.entity`, `customer.subsidiary` are all JSON objects. Never join directly; always extract with `JSON_VALUE(..., '$.id')` and expose as a computed dimension.
118
+ 2. **`entity` vs `company`** — contacts use `company`, but opportunities and sales orders use `entity` as the customer FK column.
119
+ 3. **PK column name is `id` (lowercase)** — not `internalId` or similar. Verify in INFORMATION_SCHEMA.
120
+ 4. **Subsidiary** — `customer.subsidiary` holds the subsidiary FK. If the schema has multiple subsidiaries, you may need a `subsidiary` cube joined via `subsidiary_id`.
121
+ 5. **No `_airbyte_extracted_at` guarantee** — some NetSuite streams use `lastModifiedDate` instead. Check actual column names before writing `refresh_key`.