schematichq 1.4.3 → 1.4.5

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 (73) hide show
  1. checksums.yaml +4 -4
  2. data/.fern/metadata.json +2 -2
  3. data/.fern/replay.lock +501 -2
  4. data/WASM_VERSION +1 -1
  5. data/lib/schematic/accounts/client.rb +44 -0
  6. data/lib/schematic/accounts/types/count_account_members_params.rb +15 -0
  7. data/lib/schematic/accounts/types/count_account_members_request.rb +14 -0
  8. data/lib/schematic/accounts/types/count_account_members_response.rb +12 -0
  9. data/lib/schematic/client.rb +1 -1
  10. data/lib/schematic/credits/client.rb +0 -104
  11. data/lib/schematic/integrationsapi/client.rb +99 -3
  12. data/lib/schematic/integrationsapi/types/assume_stripe_installed_response.rb +12 -0
  13. data/lib/schematic/integrationsapi/types/install_integration_response.rb +12 -0
  14. data/lib/schematic/integrationsapi/types/install_stripe_response.rb +12 -0
  15. data/lib/schematic/integrationsapi/types/{load_sample_data_set_v_2_response.rb → load_sample_data_set_response.rb} +1 -1
  16. data/lib/schematic/plangroups/types/create_plan_group_request_body.rb +4 -0
  17. data/lib/schematic/plangroups/types/update_plan_group_request_body.rb +4 -0
  18. data/lib/schematic/plans/types/publish_plan_version_request_body.rb +0 -1
  19. data/lib/schematic/plans/types/retry_custom_plan_billing_request_body.rb +0 -1
  20. data/lib/schematic/types/account_member_response_data.rb +2 -0
  21. data/lib/schematic/types/billing_credit_grant_reason.rb +1 -0
  22. data/lib/schematic/types/billing_credit_grant_response_data.rb +3 -0
  23. data/lib/schematic/types/billing_credit_grant_zeroed_out_reason.rb +1 -0
  24. data/lib/schematic/types/billing_credit_ledger_authority.rb +13 -0
  25. data/lib/schematic/types/billing_credit_view.rb +1 -0
  26. data/lib/schematic/types/billing_plan_credit_grant_response_data.rb +1 -0
  27. data/lib/schematic/types/billing_provider_type.rb +1 -0
  28. data/lib/schematic/types/change_subscription_internal_request_body.rb +2 -0
  29. data/lib/schematic/types/change_subscription_request_body.rb +2 -0
  30. data/lib/schematic/types/checkout_data_response_data.rb +1 -0
  31. data/lib/schematic/types/checkout_field_input.rb +14 -0
  32. data/lib/schematic/types/checkout_field_response_data.rb +18 -0
  33. data/lib/schematic/types/checkout_field_value.rb +10 -0
  34. data/lib/schematic/types/checkout_field_with_value.rb +15 -0
  35. data/lib/schematic/types/checkout_settings_response_data.rb +3 -0
  36. data/lib/schematic/types/company_credit_balance_response_data.rb +2 -0
  37. data/lib/schematic/types/company_plan_credit_grant_view.rb +1 -0
  38. data/lib/schematic/types/component_hydrate_response_data.rb +1 -0
  39. data/lib/schematic/types/component_preview_response_data.rb +1 -0
  40. data/lib/schematic/types/create_billing_plan_credit_grant_request_body.rb +1 -0
  41. data/lib/schematic/types/credit_company_grant_view.rb +3 -0
  42. data/lib/schematic/types/credit_event_ledger_response_data.rb +1 -0
  43. data/lib/schematic/types/credit_usage_reason.rb +15 -0
  44. data/lib/schematic/types/event_detail_response_data.rb +1 -0
  45. data/lib/schematic/types/event_response_data.rb +1 -0
  46. data/lib/schematic/types/feature_entitlement.rb +2 -0
  47. data/lib/schematic/types/install_integration_request_body.rb +14 -0
  48. data/lib/schematic/types/integration_config.rb +1 -0
  49. data/lib/schematic/types/integration_install_config.rb +18 -0
  50. data/lib/schematic/types/integration_install_response_data.rb +11 -0
  51. data/lib/schematic/types/integration_type.rb +1 -0
  52. data/lib/schematic/types/manage_plan_request.rb +1 -0
  53. data/lib/schematic/types/metronome_integration_config.rb +7 -0
  54. data/lib/schematic/types/migration_error_code.rb +1 -0
  55. data/lib/schematic/types/plan_change_response_data.rb +1 -0
  56. data/lib/schematic/types/plan_credit_grant_view.rb +1 -0
  57. data/lib/schematic/types/plan_group_detail_response_data.rb +1 -0
  58. data/lib/schematic/types/preview_subscription_change_response_data.rb +3 -0
  59. data/lib/schematic/types/rulesengine_feature_entitlement.rb +2 -0
  60. data/lib/schematic/types/update_billing_plan_credit_grant_request_body.rb +1 -0
  61. data/lib/schematic/version.rb +1 -1
  62. data/lib/schematic/wasm/rulesengine.wasm +0 -0
  63. data/lib/schematic.rb +23 -15
  64. data/reference.md +386 -293
  65. metadata +19 -11
  66. data/lib/schematic/credits/types/count_credit_ledger_params.rb +0 -19
  67. data/lib/schematic/credits/types/count_credit_ledger_request.rb +0 -18
  68. data/lib/schematic/credits/types/count_credit_ledger_response.rb +0 -12
  69. data/lib/schematic/credits/types/get_enriched_credit_ledger_params.rb +0 -19
  70. data/lib/schematic/credits/types/get_enriched_credit_ledger_request.rb +0 -18
  71. data/lib/schematic/credits/types/get_enriched_credit_ledger_response.rb +0 -12
  72. data/lib/schematic/types/credit_ledger_enriched_entry_response_data.rb +0 -30
  73. data/lib/schematic/types/credit_ledger_period.rb +0 -14
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 52c3520cf93bf33b5709b843536791c05f1f79cde2521fed583179802ea01a13
4
- data.tar.gz: b321618dcbe10659189f72554ee428638e022fc8afb044a11cc87fc59c441624
3
+ metadata.gz: 85d9efb3046014492cb89dee9ca47427babf900a5738b4d2ebef375b014668c6
4
+ data.tar.gz: a3d972bc92e02315a9dba121231a4715e28704f1442f8f271177a0f1ce2f5779
5
5
  SHA512:
6
- metadata.gz: c3c953562cb84790a3bd211f88eac886330a71e20c2d83d815a5216883a6eade2981e34df357c923dd9bae64571203600528855b747df3798e7e1d11ef9051b8
7
- data.tar.gz: 2c3cead033c8c600ef24f886541fa8102b6cff15299aef884225f58e292793eedb356305ebc5c80fc0cdd4f5288ddd1cd33b29dca4284a999a1e904a25129bb9
6
+ metadata.gz: df77700f492d8ed2fc5dc9ba25722864ab60d995761a3c142966560ca66cfb230e117a5f51f17b713cde545a487d419731a70c77761dc0d0a2d06dc099c6037d
7
+ data.tar.gz: e99075d5f93882c41af77da371fac5d3b28b225979f5acf78b3438f9a42d4b6694543e62c1892c1a05b93b8aec0d39b4bdbdcf12e63754f6add9533292bc066b
data/.fern/metadata.json CHANGED
@@ -13,6 +13,6 @@
13
13
  "webrick": ">= 1.0"
14
14
  }
15
15
  },
16
- "originGitCommit": "a3057c10aa6a1daa708dc714c89ce943f67a722a",
17
- "sdkVersion": "1.4.3"
16
+ "originGitCommit": "786b643cc0bf17aff1114597108246b5cf8af395",
17
+ "sdkVersion": "1.4.5"
18
18
  }
data/.fern/replay.lock CHANGED
@@ -6,5 +6,504 @@ generations:
6
6
  timestamp: 2026-05-19T19:48:36.665Z
7
7
  cli_version: unknown
8
8
  generator_versions: {}
9
- current_generation: 298b1d0ee88b6637d99742bff32b353cfbd5b32e
10
- patches: []
9
+ - commit_sha: 0dd219c7b903329d9db2fb5b9fb570f2fd6e1a39
10
+ tree_hash: 7a0aadd77d8091d4846b0968fc36d335be98a66d
11
+ timestamp: 2026-06-08T18:13:52.357Z
12
+ cli_version: unknown
13
+ generator_versions:
14
+ fernapi/fern-ruby-sdk: 1.1.13
15
+ current_generation: 0dd219c7b903329d9db2fb5b9fb570f2fd6e1a39
16
+ patches:
17
+ - id: patch-6a2db2b5
18
+ content_hash: sha256:630a8c3df9ae971e973e4dcb3d71d0d9f28b8da9877b0ff27ff3cca2a1eb924a
19
+ original_commit: 6a2db2b584601e13f90302155d37a22e45542fab
20
+ original_message: handle partial messages properly
21
+ original_author: Christopher Brady <chris@schematichq.com>
22
+ base_generation: e75de47f4e4f08f8e683366dd9b3936bc5c6452f
23
+ files:
24
+ - lib/schematic/datastream/merge.rb
25
+ patch_content: |
26
+ diff --git a/lib/schematic/datastream/merge.rb b/lib/schematic/datastream/merge.rb
27
+ index 0ab68fe..7f1084d 100644
28
+ --- a/lib/schematic/datastream/merge.rb
29
+ +++ b/lib/schematic/datastream/merge.rb
30
+ @@ -16,25 +16,42 @@ module Schematic
31
+ COMPANY_MAP_FIELDS = %i[credit_balances keys traits].freeze
32
+ COMPANY_ARRAY_FIELDS = %i[billing_product_ids entitlements plan_ids plan_version_ids rules].freeze
33
+
34
+ + # Partials don't carry refreshed entitlements, so when their derived
35
+ + # fields change in another part of the company we sync them here to match
36
+ + # server behavior (see schematic-python merge.partial_company):
37
+ + # - credit_remaining <- credit_balances[credit_id]
38
+ + # - usage <- metric value matching (event_name, metric_period, month_reset)
39
+ + # Both are skipped when the partial also sends entitlements wholesale.
40
+ def partial_company(existing, partial_data)
41
+ return existing unless partial_data.is_a?(Hash)
42
+
43
+ result = deep_copy(existing)
44
+ + entitlements_in_partial = partial_data.key?(:entitlements) || partial_data.key?("entitlements")
45
+ + updated_balances = nil
46
+ + metrics_updated = false
47
+
48
+ partial_data.each do |key, value|
49
+ sym_key = key.to_sym
50
+ if COMPANY_MAP_FIELDS.include?(sym_key)
51
+ result[sym_key] ||= {}
52
+ result[sym_key] = result[sym_key].merge(value) if value.is_a?(Hash)
53
+ + updated_balances = (value.is_a?(Hash) ? value : {}) if sym_key == :credit_balances
54
+ elsif COMPANY_ARRAY_FIELDS.include?(sym_key)
55
+ result[sym_key] = value if value.is_a?(Array)
56
+ elsif sym_key == :metrics
57
+ result[sym_key] = upsert_metrics(result[sym_key] || [], value || [])
58
+ + metrics_updated = true
59
+ else
60
+ result[sym_key] = value
61
+ end
62
+ end
63
+
64
+ + if (updated_balances || metrics_updated) && !entitlements_in_partial
65
+ + result[:entitlements] = sync_entitlements(
66
+ + result[:entitlements], result[:metrics], updated_balances, metrics_updated
67
+ + )
68
+ + end
69
+ +
70
+ result
71
+ end
72
+
73
+ @@ -90,6 +107,60 @@ module Schematic
74
+ def get_metric_field(metric, field)
75
+ metric[field] || metric[field.to_s]
76
+ end
77
+ +
78
+ + # Re-derive entitlement usage / credit_remaining from the merged metrics
79
+ + # and the just-updated credit balances. Mirrors schematic-python so that
80
+ + # entitlement usage reflects DataStream track events immediately.
81
+ + def sync_entitlements(entitlements, metrics, updated_balances, metrics_updated)
82
+ + return entitlements unless entitlements.is_a?(Array) && !entitlements.empty?
83
+ +
84
+ + metrics_lookup = {}
85
+ + if metrics_updated && metrics.is_a?(Array)
86
+ + metrics.each do |metric|
87
+ + next unless metric.is_a?(Hash)
88
+ +
89
+ + key = [
90
+ + get_metric_field(metric, :event_subtype) || "",
91
+ + get_metric_field(metric, :period) || "",
92
+ + get_metric_field(metric, :month_reset) || ""
93
+ + ]
94
+ + value = get_metric_field(metric, :value)
95
+ + metrics_lookup[key] = value.nil? ? 0 : value
96
+ + end
97
+ + end
98
+ +
99
+ + entitlements.map do |ent|
100
+ + next ent unless ent.is_a?(Hash)
101
+ +
102
+ + new_ent = deep_copy(ent)
103
+ +
104
+ + credit_id = get_metric_field(ent, :credit_id)
105
+ + if updated_balances && credit_id
106
+ + present, balance = fetch_balance(updated_balances, credit_id)
107
+ + new_ent[:credit_remaining] = balance if present
108
+ + end
109
+ +
110
+ + event_name = get_metric_field(ent, :event_name)
111
+ + unless metrics_lookup.empty? || event_name.nil?
112
+ + period = get_metric_field(ent, :metric_period) || "all_time"
113
+ + month_reset = get_metric_field(ent, :month_reset) || "first_of_month"
114
+ + matched = metrics_lookup[[event_name, period, month_reset]]
115
+ + new_ent[:usage] = matched unless matched.nil?
116
+ + end
117
+ +
118
+ + new_ent
119
+ + end
120
+ + end
121
+ +
122
+ + # credit_balances keys may be symbols (deep_copy symbolizes) while an
123
+ + # entitlement's credit_id is always a string, so check both forms.
124
+ + def fetch_balance(balances, credit_id)
125
+ + return [true, balances[credit_id]] if balances.key?(credit_id)
126
+ + return [true, balances[credit_id.to_sym]] if balances.key?(credit_id.to_sym)
127
+ + return [true, balances[credit_id.to_s]] if balances.key?(credit_id.to_s)
128
+ +
129
+ + [false, nil]
130
+ + end
131
+ end
132
+ end
133
+ end
134
+ theirs_snapshot:
135
+ lib/schematic/datastream/merge.rb: |
136
+ # frozen_string_literal: true
137
+
138
+ require "json"
139
+
140
+ module Schematic
141
+ module DataStream
142
+ module Merge
143
+ module_function
144
+
145
+ def deep_copy(obj)
146
+ JSON.parse(JSON.generate(obj), symbolize_names: true)
147
+ rescue StandardError
148
+ obj.dup
149
+ end
150
+
151
+ COMPANY_MAP_FIELDS = %i[credit_balances keys traits].freeze
152
+ COMPANY_ARRAY_FIELDS = %i[billing_product_ids entitlements plan_ids plan_version_ids rules].freeze
153
+
154
+ # Partials don't carry refreshed entitlements, so when their derived
155
+ # fields change in another part of the company we sync them here to match
156
+ # server behavior (see schematic-python merge.partial_company):
157
+ # - credit_remaining <- credit_balances[credit_id]
158
+ # - usage <- metric value matching (event_name, metric_period, month_reset)
159
+ # Both are skipped when the partial also sends entitlements wholesale.
160
+ def partial_company(existing, partial_data)
161
+ return existing unless partial_data.is_a?(Hash)
162
+
163
+ result = deep_copy(existing)
164
+ entitlements_in_partial = partial_data.key?(:entitlements) || partial_data.key?("entitlements")
165
+ updated_balances = nil
166
+ metrics_updated = false
167
+
168
+ partial_data.each do |key, value|
169
+ sym_key = key.to_sym
170
+ if COMPANY_MAP_FIELDS.include?(sym_key)
171
+ result[sym_key] ||= {}
172
+ result[sym_key] = result[sym_key].merge(value) if value.is_a?(Hash)
173
+ updated_balances = (value.is_a?(Hash) ? value : {}) if sym_key == :credit_balances
174
+ elsif COMPANY_ARRAY_FIELDS.include?(sym_key)
175
+ result[sym_key] = value if value.is_a?(Array)
176
+ elsif sym_key == :metrics
177
+ result[sym_key] = upsert_metrics(result[sym_key] || [], value || [])
178
+ metrics_updated = true
179
+ else
180
+ result[sym_key] = value
181
+ end
182
+ end
183
+
184
+ if (updated_balances || metrics_updated) && !entitlements_in_partial
185
+ result[:entitlements] = sync_entitlements(
186
+ result[:entitlements], result[:metrics], updated_balances, metrics_updated
187
+ )
188
+ end
189
+
190
+ result
191
+ end
192
+
193
+ USER_MAP_FIELDS = %i[keys traits].freeze
194
+
195
+ def partial_user(existing, partial_data)
196
+ return existing unless partial_data.is_a?(Hash)
197
+
198
+ result = deep_copy(existing)
199
+
200
+ partial_data.each do |key, value|
201
+ sym_key = key.to_sym
202
+ if USER_MAP_FIELDS.include?(sym_key)
203
+ result[sym_key] ||= {}
204
+ result[sym_key] = result[sym_key].merge(value) if value.is_a?(Hash)
205
+ elsif sym_key == :rules
206
+ result[sym_key] = value if value.is_a?(Array)
207
+ else
208
+ result[sym_key] = value
209
+ end
210
+ end
211
+
212
+ result
213
+ end
214
+
215
+ def upsert_metrics(existing, incoming)
216
+ return incoming if existing.nil? || existing.empty?
217
+ return existing if incoming.nil? || incoming.empty?
218
+
219
+ result = existing.map { |metric| deep_copy(metric) }
220
+
221
+ incoming.each do |inc_metric|
222
+ match_idx = result.index do |ex|
223
+ metrics_match?(ex, inc_metric)
224
+ end
225
+
226
+ if match_idx
227
+ result[match_idx] = inc_metric
228
+ else
229
+ result << inc_metric
230
+ end
231
+ end
232
+
233
+ result
234
+ end
235
+
236
+ def metrics_match?(left, right)
237
+ get_metric_field(left, :event_subtype) == get_metric_field(right, :event_subtype) &&
238
+ get_metric_field(left, :period) == get_metric_field(right, :period) &&
239
+ get_metric_field(left, :month_reset) == get_metric_field(right, :month_reset)
240
+ end
241
+
242
+ def get_metric_field(metric, field)
243
+ metric[field] || metric[field.to_s]
244
+ end
245
+
246
+ # Re-derive entitlement usage / credit_remaining from the merged metrics
247
+ # and the just-updated credit balances. Mirrors schematic-python so that
248
+ # entitlement usage reflects DataStream track events immediately.
249
+ def sync_entitlements(entitlements, metrics, updated_balances, metrics_updated)
250
+ return entitlements unless entitlements.is_a?(Array) && !entitlements.empty?
251
+
252
+ metrics_lookup = {}
253
+ if metrics_updated && metrics.is_a?(Array)
254
+ metrics.each do |metric|
255
+ next unless metric.is_a?(Hash)
256
+
257
+ key = [
258
+ get_metric_field(metric, :event_subtype) || "",
259
+ get_metric_field(metric, :period) || "",
260
+ get_metric_field(metric, :month_reset) || ""
261
+ ]
262
+ value = get_metric_field(metric, :value)
263
+ metrics_lookup[key] = value.nil? ? 0 : value
264
+ end
265
+ end
266
+
267
+ entitlements.map do |ent|
268
+ next ent unless ent.is_a?(Hash)
269
+
270
+ new_ent = deep_copy(ent)
271
+
272
+ credit_id = get_metric_field(ent, :credit_id)
273
+ if updated_balances && credit_id
274
+ present, balance = fetch_balance(updated_balances, credit_id)
275
+ new_ent[:credit_remaining] = balance if present
276
+ end
277
+
278
+ event_name = get_metric_field(ent, :event_name)
279
+ unless metrics_lookup.empty? || event_name.nil?
280
+ period = get_metric_field(ent, :metric_period) || "all_time"
281
+ month_reset = get_metric_field(ent, :month_reset) || "first_of_month"
282
+ matched = metrics_lookup[[event_name, period, month_reset]]
283
+ new_ent[:usage] = matched unless matched.nil?
284
+ end
285
+
286
+ new_ent
287
+ end
288
+ end
289
+
290
+ # credit_balances keys may be symbols (deep_copy symbolizes) while an
291
+ # entitlement's credit_id is always a string, so check both forms.
292
+ def fetch_balance(balances, credit_id)
293
+ return [true, balances[credit_id]] if balances.key?(credit_id)
294
+ return [true, balances[credit_id.to_sym]] if balances.key?(credit_id.to_sym)
295
+ return [true, balances[credit_id.to_s]] if balances.key?(credit_id.to_s)
296
+
297
+ [false, nil]
298
+ end
299
+ end
300
+ end
301
+ end
302
+ status: unresolved
303
+ - id: patch-46462128
304
+ content_hash: sha256:94f06e4e687a0908d3f305a4d605d9dc1271db6ed83014a10b86be900187dd83
305
+ original_commit: 46462128cacb25b73fac7770060cf427ff0d9e6d
306
+ original_message: add tests and address nits
307
+ original_author: Christopher Brady <chris@schematichq.com>
308
+ base_generation: e75de47f4e4f08f8e683366dd9b3936bc5c6452f
309
+ files:
310
+ - lib/schematic/datastream/merge.rb
311
+ patch_content: |
312
+ diff --git a/lib/schematic/datastream/merge.rb b/lib/schematic/datastream/merge.rb
313
+ index 7f1084d..f4dd4e5 100644
314
+ --- a/lib/schematic/datastream/merge.rb
315
+ +++ b/lib/schematic/datastream/merge.rb
316
+ @@ -46,7 +46,7 @@ module Schematic
317
+ end
318
+ end
319
+
320
+ - if (updated_balances || metrics_updated) && !entitlements_in_partial
321
+ + if (updated_balances&.any? || metrics_updated) && !entitlements_in_partial
322
+ result[:entitlements] = sync_entitlements(
323
+ result[:entitlements], result[:metrics], updated_balances, metrics_updated
324
+ )
325
+ @@ -152,12 +152,12 @@ module Schematic
326
+ end
327
+ end
328
+
329
+ - # credit_balances keys may be symbols (deep_copy symbolizes) while an
330
+ - # entitlement's credit_id is always a string, so check both forms.
331
+ + # The partial's credit_balances may be keyed by string or symbol depending
332
+ + # on how the message was parsed, while an entitlement's credit_id is a
333
+ + # string value, so check both key forms.
334
+ def fetch_balance(balances, credit_id)
335
+ return [true, balances[credit_id]] if balances.key?(credit_id)
336
+ return [true, balances[credit_id.to_sym]] if balances.key?(credit_id.to_sym)
337
+ - return [true, balances[credit_id.to_s]] if balances.key?(credit_id.to_s)
338
+
339
+ [false, nil]
340
+ end
341
+ theirs_snapshot:
342
+ lib/schematic/datastream/merge.rb: |
343
+ # frozen_string_literal: true
344
+
345
+ require "json"
346
+
347
+ module Schematic
348
+ module DataStream
349
+ module Merge
350
+ module_function
351
+
352
+ def deep_copy(obj)
353
+ JSON.parse(JSON.generate(obj), symbolize_names: true)
354
+ rescue StandardError
355
+ obj.dup
356
+ end
357
+
358
+ COMPANY_MAP_FIELDS = %i[credit_balances keys traits].freeze
359
+ COMPANY_ARRAY_FIELDS = %i[billing_product_ids entitlements plan_ids plan_version_ids rules].freeze
360
+
361
+ # Partials don't carry refreshed entitlements, so when their derived
362
+ # fields change in another part of the company we sync them here to match
363
+ # server behavior (see schematic-python merge.partial_company):
364
+ # - credit_remaining <- credit_balances[credit_id]
365
+ # - usage <- metric value matching (event_name, metric_period, month_reset)
366
+ # Both are skipped when the partial also sends entitlements wholesale.
367
+ def partial_company(existing, partial_data)
368
+ return existing unless partial_data.is_a?(Hash)
369
+
370
+ result = deep_copy(existing)
371
+ entitlements_in_partial = partial_data.key?(:entitlements) || partial_data.key?("entitlements")
372
+ updated_balances = nil
373
+ metrics_updated = false
374
+
375
+ partial_data.each do |key, value|
376
+ sym_key = key.to_sym
377
+ if COMPANY_MAP_FIELDS.include?(sym_key)
378
+ result[sym_key] ||= {}
379
+ result[sym_key] = result[sym_key].merge(value) if value.is_a?(Hash)
380
+ updated_balances = (value.is_a?(Hash) ? value : {}) if sym_key == :credit_balances
381
+ elsif COMPANY_ARRAY_FIELDS.include?(sym_key)
382
+ result[sym_key] = value if value.is_a?(Array)
383
+ elsif sym_key == :metrics
384
+ result[sym_key] = upsert_metrics(result[sym_key] || [], value || [])
385
+ metrics_updated = true
386
+ else
387
+ result[sym_key] = value
388
+ end
389
+ end
390
+
391
+ if (updated_balances&.any? || metrics_updated) && !entitlements_in_partial
392
+ result[:entitlements] = sync_entitlements(
393
+ result[:entitlements], result[:metrics], updated_balances, metrics_updated
394
+ )
395
+ end
396
+
397
+ result
398
+ end
399
+
400
+ USER_MAP_FIELDS = %i[keys traits].freeze
401
+
402
+ def partial_user(existing, partial_data)
403
+ return existing unless partial_data.is_a?(Hash)
404
+
405
+ result = deep_copy(existing)
406
+
407
+ partial_data.each do |key, value|
408
+ sym_key = key.to_sym
409
+ if USER_MAP_FIELDS.include?(sym_key)
410
+ result[sym_key] ||= {}
411
+ result[sym_key] = result[sym_key].merge(value) if value.is_a?(Hash)
412
+ elsif sym_key == :rules
413
+ result[sym_key] = value if value.is_a?(Array)
414
+ else
415
+ result[sym_key] = value
416
+ end
417
+ end
418
+
419
+ result
420
+ end
421
+
422
+ def upsert_metrics(existing, incoming)
423
+ return incoming if existing.nil? || existing.empty?
424
+ return existing if incoming.nil? || incoming.empty?
425
+
426
+ result = existing.map { |metric| deep_copy(metric) }
427
+
428
+ incoming.each do |inc_metric|
429
+ match_idx = result.index do |ex|
430
+ metrics_match?(ex, inc_metric)
431
+ end
432
+
433
+ if match_idx
434
+ result[match_idx] = inc_metric
435
+ else
436
+ result << inc_metric
437
+ end
438
+ end
439
+
440
+ result
441
+ end
442
+
443
+ def metrics_match?(left, right)
444
+ get_metric_field(left, :event_subtype) == get_metric_field(right, :event_subtype) &&
445
+ get_metric_field(left, :period) == get_metric_field(right, :period) &&
446
+ get_metric_field(left, :month_reset) == get_metric_field(right, :month_reset)
447
+ end
448
+
449
+ def get_metric_field(metric, field)
450
+ metric[field] || metric[field.to_s]
451
+ end
452
+
453
+ # Re-derive entitlement usage / credit_remaining from the merged metrics
454
+ # and the just-updated credit balances. Mirrors schematic-python so that
455
+ # entitlement usage reflects DataStream track events immediately.
456
+ def sync_entitlements(entitlements, metrics, updated_balances, metrics_updated)
457
+ return entitlements unless entitlements.is_a?(Array) && !entitlements.empty?
458
+
459
+ metrics_lookup = {}
460
+ if metrics_updated && metrics.is_a?(Array)
461
+ metrics.each do |metric|
462
+ next unless metric.is_a?(Hash)
463
+
464
+ key = [
465
+ get_metric_field(metric, :event_subtype) || "",
466
+ get_metric_field(metric, :period) || "",
467
+ get_metric_field(metric, :month_reset) || ""
468
+ ]
469
+ value = get_metric_field(metric, :value)
470
+ metrics_lookup[key] = value.nil? ? 0 : value
471
+ end
472
+ end
473
+
474
+ entitlements.map do |ent|
475
+ next ent unless ent.is_a?(Hash)
476
+
477
+ new_ent = deep_copy(ent)
478
+
479
+ credit_id = get_metric_field(ent, :credit_id)
480
+ if updated_balances && credit_id
481
+ present, balance = fetch_balance(updated_balances, credit_id)
482
+ new_ent[:credit_remaining] = balance if present
483
+ end
484
+
485
+ event_name = get_metric_field(ent, :event_name)
486
+ unless metrics_lookup.empty? || event_name.nil?
487
+ period = get_metric_field(ent, :metric_period) || "all_time"
488
+ month_reset = get_metric_field(ent, :month_reset) || "first_of_month"
489
+ matched = metrics_lookup[[event_name, period, month_reset]]
490
+ new_ent[:usage] = matched unless matched.nil?
491
+ end
492
+
493
+ new_ent
494
+ end
495
+ end
496
+
497
+ # The partial's credit_balances may be keyed by string or symbol depending
498
+ # on how the message was parsed, while an entitlement's credit_id is a
499
+ # string value, so check both key forms.
500
+ def fetch_balance(balances, credit_id)
501
+ return [true, balances[credit_id]] if balances.key?(credit_id)
502
+ return [true, balances[credit_id.to_sym]] if balances.key?(credit_id.to_sym)
503
+
504
+ [false, nil]
505
+ end
506
+ end
507
+ end
508
+ end
509
+ status: unresolved
data/WASM_VERSION CHANGED
@@ -1 +1 @@
1
- 0.2.0
1
+ 0.3.0
@@ -86,6 +86,50 @@ module Schematic
86
86
  end
87
87
  end
88
88
 
89
+ # @param request_options [Hash]
90
+ # @param params [Hash]
91
+ # @option request_options [String] :base_url
92
+ # @option request_options [Hash{String => Object}] :additional_headers
93
+ # @option request_options [Hash{String => Object}] :additional_query_parameters
94
+ # @option request_options [Hash{String => Object}] :additional_body_parameters
95
+ # @option request_options [Integer] :timeout_in_seconds
96
+ # @option params [String, nil] :ids
97
+ # @option params [String, nil] :q
98
+ # @option params [Integer, nil] :limit
99
+ # @option params [Integer, nil] :offset
100
+ #
101
+ # @return [Schematic::Accounts::Types::CountAccountMembersResponse]
102
+ def count_account_members(request_options: {}, **params)
103
+ params = Schematic::Internal::Types::Utils.normalize_keys(params)
104
+ query_param_names = %i[ids q limit offset]
105
+ query_params = {}
106
+ query_params["ids"] = params[:ids] if params.key?(:ids)
107
+ query_params["q"] = params[:q] if params.key?(:q)
108
+ query_params["limit"] = params[:limit] if params.key?(:limit)
109
+ query_params["offset"] = params[:offset] if params.key?(:offset)
110
+ params.except(*query_param_names)
111
+
112
+ request = Schematic::Internal::JSON::Request.new(
113
+ base_url: request_options[:base_url],
114
+ method: "GET",
115
+ path: "account-members/count",
116
+ query: query_params,
117
+ request_options: request_options
118
+ )
119
+ begin
120
+ response = @client.send(request)
121
+ rescue Net::HTTPRequestTimeout
122
+ raise Schematic::Errors::TimeoutError
123
+ end
124
+ code = response.code.to_i
125
+ if code.between?(200, 299)
126
+ Schematic::Accounts::Types::CountAccountMembersResponse.load(response.body)
127
+ else
128
+ error_class = Schematic::Errors::ResponseError.subclass_for_code(code)
129
+ raise error_class.new(response.body, code: code)
130
+ end
131
+ end
132
+
89
133
  # @param request_options [Hash]
90
134
  # @param params [Hash]
91
135
  # @option request_options [String] :base_url
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schematic
4
+ module Accounts
5
+ module Types
6
+ # Input parameters
7
+ class CountAccountMembersParams < Internal::Types::Model
8
+ field :ids, -> { Internal::Types::Array[String] }, optional: true, nullable: false
9
+ field :limit, -> { Integer }, optional: true, nullable: false
10
+ field :offset, -> { Integer }, optional: true, nullable: false
11
+ field :q, -> { String }, optional: true, nullable: false
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schematic
4
+ module Accounts
5
+ module Types
6
+ class CountAccountMembersRequest < Internal::Types::Model
7
+ field :ids, -> { String }, optional: true, nullable: false
8
+ field :q, -> { String }, optional: true, nullable: false
9
+ field :limit, -> { Integer }, optional: true, nullable: false
10
+ field :offset, -> { Integer }, optional: true, nullable: false
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Schematic
4
+ module Accounts
5
+ module Types
6
+ class CountAccountMembersResponse < Internal::Types::Model
7
+ field :data, -> { Schematic::Types::CountResponse }, optional: false, nullable: false
8
+ field :params, -> { Schematic::Accounts::Types::CountAccountMembersParams }, optional: false, nullable: false
9
+ end
10
+ end
11
+ end
12
+ end
@@ -10,7 +10,7 @@ module Schematic
10
10
  @raw_client = Schematic::Internal::Http::RawClient.new(
11
11
  base_url: base_url || Schematic::Environment::DEFAULT,
12
12
  headers: {
13
- "User-Agent" => "schematichq/1.4.3",
13
+ "User-Agent" => "schematichq/1.4.5",
14
14
  "X-Fern-Language" => "Ruby",
15
15
  "X-Schematic-Api-Key" => api_key.to_s
16
16
  }