{"id":"khanpython/analytics-service","name":"analytics-service","scope":"khanpython","platform":"roblox","description":"A rate-limited AnalyticsService wrapper with event coalescing for Roblox.","version":"1.0.0","latest":"1.0.0","versions":["0.0.1","0.0.2","0.0.3","0.0.4","0.0.5","0.0.6","0.0.7","0.0.8","1.0.0"],"license":"MIT","licenseRating":"safe","licenseCaveats":[],"licenseVerified":true,"dependencies":{},"integrity":"fc85209a389988369ed178506c4f354961eba52ca9797a946860a622b2583dea","likes":0,"downloads":0,"install":"forest install khanpython/analytics-service","url":"https://forest.dev/p/roblox/khanpython/analytics-service","files":"https://api.forest.dev/ai/package/roblox/khanpython/analytics-service/files","readme":"<div align=\"center\">\r\n\t<h1>Analytics Service Wrapper</h1>\r\n  <p>A rate-limited Analytics Service wrapper for Roblox.</p>\r\n</div>\r\n\r\n\r\n### Features:\r\n\r\n- **Server-wide token bucket:** Smoothly refills at Roblox's real rate limit (`120 + 20 * CCU` events/min) so bursts don't get dropped.\r\n- **Self-stopping drain:** No permanent polling loop - a drain task only runs while the queue has work, then exits.\r\n- **Economy event coalescing:** Identical economy events (same player/currency/transaction/SKU/fields) are summed and flushed every 5 seconds as a single call.\r\n- **Funnel step precedence:** Out-of-order funnel steps are skipped with a warning instead of spamming AnalyticsService.\r\n- **Fire-and-forget API:** Methods return nothing. Internal errors are logged via `warn`; validation errors surface as assertions.\r\n- **Type-safe inputs:** Strict validation of custom fields, step numbers, and transaction types.\r\n\r\n---\r\n\r\n### Installation via Wally:\r\n\r\n1. Ensure you have the [Wally package manager](https://github.com/UpliftGames/wally) installed on your system.\r\n2. Add the following line to your `wally.toml` file under the `[dependencies]` section:\r\n   ```toml\r\n   analytics-service-wrapper = \"khanpython/analytics-service@1.0.0\"\r\n   ```\r\n3. Run the Wally install command to download and integrate the package:\r\n    ```bash\r\n    wally install\r\n    ```\r\n4. The package will be placed in your Packages folder. Use the following code snippet to require it in your project:\r\n    ```lua\r\n    local AnalyticsServiceWrapper = require(path-to-package)\r\n    ```\r\n\r\n---\r\n\r\n\r\n### Methods:\r\n\r\nThis wrapper includes all methods provided by the default Analytics Service, with the exception to ProgressionEvents. For more detailed information on the available parameters, visit the official [Analytics Service Documentation](https://create.roblox.com/docs/reference/engine/classes/AnalyticsService#summary).\r\n\r\nAll methods are fire-and-forget - they return nothing. Invalid inputs raise an assertion (programmer error); runtime failures are logged internally with `warn`.\r\n\r\n- **`LogCustomEvent(player, eventName, value?, customFields?)`**\r\n  - Logs a custom event with optional value and custom fields.\r\n- **`LogEconomyEvent(player, flowType, currencyType, amount, endingBalance, transactionType, itemSKU?, customFields?)`**\r\n  - Logs an economy event, such as purchases or earnings. Coalesced and flushed in batches.\r\n- **`LogFunnelStepEvent(player, funnelName, funnelSessionId?, stepNumber, stepName?, customFields?)`**\r\n  - Logs a step in the funnel. If no `funnelSessionId` is provided, one is generated and cached per player.\r\n- **`LogOnboardingFunnelStepEvent(player, stepNumber, stepName?, customFields?)`**\r\n  - Logs a step in the onboarding funnel.\r\n---\r\n\r\n### Example Usage:\r\n\r\n#### Log a custom event\r\n\r\n```lua\r\nAnalyticsWrapper:LogCustomEvent(player, \"Item\", nil, {\r\n    CustomField01 = itemId,\r\n})\r\n```\r\n\r\n#### Log a funnel step\r\n\r\n```lua\r\n-- Pass nil to auto-generate (and cache) a funnelSessionId per player.\r\nAnalyticsWrapper:LogFunnelStepEvent(player, \"LevelProgression\", nil, 1, \"LevelStart\")\r\n```\r\n\r\n#### Log an economy event\r\n\r\n```lua\r\nAnalyticsWrapper:LogEconomyEvent(\r\n    player,\r\n    Enum.AnalyticsEconomyFlowType.Sink,\r\n    \"Coins\",\r\n    50,\r\n    currentBalance - 50,\r\n    Enum.AnalyticsEconomyTransactionType.Shop,\r\n    \"sword_001\"\r\n)\r\n```\r\n\r\n---\r\n### FAQ:\r\n1. **How are events processed?**\r\n\r\n   Each event pulls a token from a server-wide bucket. If a token is available, the event fires immediately. Otherwise, it's queued and drained as tokens refill. The drain task stops as soon as the queue empties.\r\n\r\n2. **How is rate limiting calculated?**\r\n\r\n    The bucket follows Roblox's soft limit of `120 + (20 * CCU)` events per minute server-wide. Tokens refill continuously at `(120 + 20 * CCU) / 60` per second - no periodic \"reset\" that would cause bursts to fail.\r\n\r\n3. **What happens under sustained overload?**\r\n\r\n    The queue is bounded at 500 entries. If new events arrive past that, the oldest is dropped with a warning. In practice this should never trigger unless your project is firing events faster than the Roblox limit allows on average.\r\n\r\n4. **How are economy events batched?**\r\n\r\n    Events with the same `(player, currency, transactionType, itemSKU, customFields)` are summed into a single net delta and flushed every 5 seconds. This keeps a high-frequency economy from burning through the rate limit.\r\n\r\n5. **What happens if a funnel step is logged out of sequence?**\r\n\r\n    Only duplicate or backward steps are rejected. Any step `<=` the highest already logged for that `(funnelName, funnelSessionId)` pair (or for onboarding) is dropped with a warning. Skipping forward (e.g., `1` → `5`, then `5` → `50`) is accepted; the skipped numbers just show up as drop-off in the funnel report. Distinct funnels track step progression independently. See [Repeated steps](https://create.roblox.com/docs/production/analytics/funnel-events#repeated-steps) and [Skipped steps](https://create.roblox.com/docs/production/analytics/funnel-events#skipping-steps).\r\n\r\n6. **What happens when a player leaves?**\r\n\r\n    Pending economy buckets for that player are flushed best-effort (bypassing the token bucket) so the last <=5s of activity isn't silently lost. Any custom/funnel events still waiting in the queue for that player are dropped, and per-player state (funnel progress, cached session IDs) is cleared.\r\n\r\n7. **What happens on server shutdown?**\r\n\r\n    A `BindToClose` handler flushes all pending economy buckets and fires any remaining queued events best-effort before the server terminates. As with the leave path, the token bucket is bypassed since AnalyticsService enforces its own server-side soft limit.\r\n\r\n---\r\n\r\n### Resources:\r\n\r\n- [Official Analytics Service Documentation](https://create.roblox.com/docs/reference/engine/classes/AnalyticsService#summary)\r\n","readmeTruncated":false}