{"id":"sonoransoftwaregit/sonoran-lua","name":"sonoran-lua","scope":"sonoransoftwaregit","platform":"roblox","description":"Sonoran CAD v2 client for Roblox","version":"0.1.61","latest":"0.1.61","versions":["0.1.16","0.1.17","0.1.18","0.1.19","0.1.20","0.1.21","0.1.22","0.1.23","0.1.24","0.1.25","0.1.26","0.1.27","0.1.28","0.1.29","0.1.30","0.1.31","0.1.32","0.1.33","0.1.34","0.1.35","0.1.36","0.1.37","0.1.38","0.1.39","0.1.40","0.1.41","0.1.42","0.1.43","0.1.44","0.1.45","0.1.46","0.1.47","0.1.48","0.1.49","0.1.50","0.1.51","0.1.52","0.1.53","0.1.54","0.1.55","0.1.56","0.1.57","0.1.58","0.1.59","0.1.60","0.1.61"],"license":"MIT","licenseRating":"safe","licenseCaveats":["License identified from the packaged LICENSE file; the manifest's declared license (\"PolyForm-Noncommercial-1.0.0\") is not a recognized identifier."],"licenseVerified":true,"dependencies":{},"integrity":"de19e5e72e4e8a3008034105bebf0fb742036a47780bbaf2f45b01d7c88552ad","likes":0,"downloads":0,"install":"forest install sonoransoftwaregit/sonoran-lua","url":"https://forest.dev/p/roblox/sonoransoftwaregit/sonoran-lua","files":"https://api.forest.dev/ai/package/roblox/sonoransoftwaregit/sonoran-lua/files","readme":"# Sonoran.lua\n\n`Sonoran.lua` is a Lua SDK for Sonoran CAD v2 endpoints with shared client code for FiveM and Roblox runtimes.\n\n## Installation\n\n### FiveM\n\nInstall from LuaRocks:\n\n```sh\nluarocks install sonoran.lua\n```\n\nLuaRocks package:\n[sonoran.lua on LuaRocks](https://luarocks.org/modules/sonoransoftware/sonoran.lua)\n\nOr use the generated FiveM resource release asset and start it before resources that consume it.\n\n```cfg\nensure Sonoran.lua\n```\n\nIn your consuming resource:\n\n```lua\nfx_version 'cerulean'\ngame 'gta5'\nlua54 'yes'\n\nserver_scripts {\n  'server.lua'\n}\n\ndependency 'Sonoran.lua'\n```\n\n### Roblox\n\nInstall from Wally:\n\n```toml\n[dependencies]\nSonoran = \"sonoransoftwaregit/sonoran-lua@^0.1.0\"\n```\n\nThen require the package module:\n\n```lua\nlocal Sonoran = require(ReplicatedStorage.Packages.Sonoran)\n```\n\n## Usage\n\nFiveM resource usage:\n\n```lua\nlocal Sonoran = require(\"sonoran\")\n\nlocal sonoran = exports[\"Sonoran.lua\"]:createClient({\n  product = Sonoran.productEnums.CAD,\n  apiKey = \"your-cad-api-key\",\n  communityId = \"your-community-id\",\n  apiUrl = \"https://api.sonorancad.com\",\n  defaultServerId = 1,\n  timeoutMs = 30000,\n  logLevel = Sonoran.logLevels.ERROR\n})\n\nsonoran:setLogLevel(Sonoran.logLevels.DEBUG)\nsonoran:setRoomId(1)\n```\n\nRoblox usage:\n\n```lua\nlocal sonoran = Sonoran.createClient({\n  product = Sonoran.productEnums.CAD,\n  apiKey = \"your-cad-api-key\",\n  communityId = \"your-community-id\",\n  apiUrl = \"https://api.sonorancad.com\",\n  defaultServerId = 1,\n  timeoutMs = 30000,\n  logLevel = Sonoran.logLevels.ERROR\n})\n\nsonoran:setLogLevel(Sonoran.logLevels.DEBUG)\n```\n\n### Config\n\n- `apiKey`: required for authenticated endpoints.\n- `product`: required; use `Sonoran.productEnums.CAD`, `Sonoran.productEnums.CMS`, or `Sonoran.productEnums.RADIO`.\n- `communityId`: optional; used by `getLoginPageV2()` when no explicit `communityId` is supplied.\n- `apiUrl`: optional; defaults to `https://api.sonorancad.com`.\n- `defaultServerId`: optional; defaults to `1` for CAD/CMS-style server-scoped helpers. Radio v2 helpers resolve the community route from `communityId`.\n- `roomId`: optional for CAD/CMS and required for Radio v2 room-scoped helpers.\n- `headers`: optional extra headers merged into every request.\n- `timeoutMs`: optional timeout for the FiveM adapter; defaults to `30000`.\n- `logLevel`: optional; `Sonoran.logLevels.ERROR` by default. Supported values are `OFF`, `ERROR`, and `DEBUG`.\n\n### Debug Logging\n\nUse `setLogLevel()` to toggle HTTP debug output at runtime:\n\n```lua\nsonoran:setLogLevel(Sonoran.logLevels.DEBUG)\n```\n\nUse `setRoomId()` to update the Radio room used by later room-scoped requests without creating a new client:\n\n```lua\nsonoran:setRoomId(1)\n```\n\nUse `ERROR` to only print failed requests and rate-limit events:\n\n```lua\nsonoran:setLogLevel(Sonoran.logLevels.ERROR)\n```\n\nWhen `DEBUG` is enabled, `Sonoran.lua` prints every HTTP request and response to the console, including the method, URL, headers, body, response status, and response headers. Sensitive request values such as `Authorization` and API-key style fields are redacted in both `DEBUG` and `ERROR` logs.\n\n### Response Shape\n\nAll public methods return:\n\n```lua\n{ success = true, data = ... }\n```\n\nor:\n\n```lua\n{ success = false, reason = ... }\n```\n\nSuccessful JSON responses are decoded automatically. Plain-text error responses are returned as strings. `204 No Content` responses return `data = nil`.\n\n### Rate Limit Handling\n\nFor CAD v2 endpoints, `Sonoran.lua` automatically retries `429 Too Many Requests` responses up to 2 times when it can safely honor the server's wait window. The client checks `Retry-After` first, including both delta-seconds and HTTP-date formats, then falls back to `RateLimit-Reset`, `X-RateLimit-Reset`, and finally a short exponential backoff. If the server asks for a longer wait than the client's automatic retry limit, or the active adapter cannot sleep, the request fails instead of retrying too early.\n\n## Examples\n\nGeneral endpoint:\n\n```lua\nlocal version = sonoran.cad:getVersionV2()\nif version.success then\n  print(version.data)\nend\n```\n\nCivilian endpoint:\n\n```lua\nlocal characters = sonoran.cad:getCharactersV2({\n  roblox = 1234567890\n})\n\nif characters.success then\n  print((\"Found %s character(s)\"):format(#characters.data))\nend\n```\n\nEmergency endpoint:\n\n```lua\nlocal created = sonoran.cad:createDispatchCallV2({\n  serverId = 1,\n  origin = 1,\n  status = 1,\n  priority = 1,\n  block = \"123\",\n  address = \"Main St\",\n  postal = \"100\",\n  title = \"Traffic Stop\",\n  code = \"TS\",\n  description = \"Blue sedan heading north\",\n  notes = {},\n  communityUserIds = { \"1234567890\" }\n})\n\nif not created.success then\n  print(json.encode(created.reason))\nend\n```\n\n## Public API\n\nAll CAD v2 helpers are available under `client.cad.*`. The root-level methods are still present for backward compatibility.\n\n### General\n\n- `getLoginPageV2(params?)`\n- `checkApiIdV2(apiId)`\n- `applyPermissionKeyV2(data)`\n- `banUserV2(data)`\n- `getPenalCodesV2()`\n- `setPenalCodesV2(codes)`\n- `setApiIdsV2(data)`\n- `getTemplatesV2(recordTypeId?)`\n- `createRecordV2(data)`\n- `updateRecordV2(recordId, data)`\n- `removeRecordV2(recordId)`\n- `sendRecordDraftV2(data)`\n- `lookupV2(data)`\n- `lookupByValueV2(data)`\n- `lookupCustomV2(data)`\n- `getAccountV2(query?)`\n- `getAccountsV2(query?)`\n- `createCommunityLinkV2(data)`\n- `checkCommunityLinkV2(data)`\n- `setCommunityLinkV2(data)`\n- `setAccountPermissionsV2(data)`\n- `heartbeatV2(serverId, playerCount)`\n- `getVersionV2()`\n- `getServersV2()`\n- `setServersV2(servers, deployMap?)`\n- `verifySecretV2(secret)`\n- `authorizeStreetSignsV2(serverId?)`\n- `setPostalsV2(postals)`\n- `sendPhotoV2(data)`\n- `uploadBodycamRecordingV2(data)`\n- `getInfoV2()`\n\n### Civilian\n\n- `getCharactersV2(query?)`\n- `removeCharacterV2(characterId)`\n- `setSelectedCharacterV2(data)`\n- `getCharacterLinksV2(query?)`\n- `addCharacterLinkV2(syncId, data)`\n- `removeCharacterLinkV2(syncId, data)`\n\n### Emergency\n\n- `getUnitsV2(query?)`\n- `getCallsV2(query?)`\n- `getCurrentCallV2(accountUuid)`\n- `updateUnitLocationsV2(data)`\n- `setUnitPanicV2(data)`\n- `setUnitStatusV2(data)`\n- `kickUnitV2(data)`\n- `getIdentifiersV2(accountUuid)`\n- `getAccountUnitsV2(data)`\n- `selectIdentifierV2(accountUuid, identId)`\n- `createIdentifierV2(accountUuid, data)`\n- `updateIdentifierV2(accountUuid, identId, data)`\n- `deleteIdentifierV2(accountUuid, identId)`\n- `addIdentifiersToGroupV2(data)`\n- `createEmergencyCallV2(data)`\n- `deleteEmergencyCallV2(callId, serverId?)`\n- `getDispatchTemplatesV2(templateId?)`\n- `createDispatchCallV2(data)`\n- `createCustomDispatchCallV2(data)`\n- `updateDispatchCallV2(callId, data)`\n- `attachUnitsToDispatchCallV2(callId, data)`\n- `detachUnitsFromDispatchCallV2(data)`\n- `setDispatchPostalV2(callId, postal, serverId?)`\n- `setDispatchPrimaryV2(callId, identId, trackPrimary?, serverId?)`\n- `addDispatchNoteV2(callId, data)`\n- `closeDispatchCallsV2(callIds, serverId?)`\n- `updateStreetSignsV2(data)`\n- `setStreetSignConfigV2(signs, serverId?)`\n- `setAvailableCalloutsV2(callouts, serverId?)`\n- `getPagerConfigV2(serverId?)`\n- `setPagerConfigV2(data)`\n- `setStationsV2(config, serverId?)`\n  Sends the provided top-level station payload as-is. Pass `locations`, `tones`, and `unitColors` directly on the request body.\n- `getBlipsV2(serverId?)`\n- `createBlipV2(data)`\n- `updateBlipV2(blipId, data)`\n- `deleteBlipsV2(ids, serverId?)`\n\n## Notes\n\n- Account-targeted CAD v2 helpers accept `accountUuid`, `communityUserId`, `roblox`, `discord`, and legacy `apiId` where supported by the backend.\n- `updateUnitLocationsV2(data)` uses the HTTP v2 endpoint for slower unit location updates, and each update can target `communityUserId`, `roblox`, or `discord`.\n- Unit location updates can target `communityUserId`, `roblox`, or `discord` through the v2 HTTP endpoint.\n- FiveM uses `PerformHttpRequest`, `promise.new()`, and `Citizen.Await`.\n- Roblox uses `HttpService:RequestAsync()`.\n- Radio, CMS, and legacy CAD endpoints are intentionally out of scope for this initial port.\n","readmeTruncated":false}