Skip to main content

DataStoreCmdrService

This item only works when running on the server. Server

Cmdr commands for inspecting and stress-testing player datastores, for customer service and debugging.

Targets come in as playerIds, so the same command reaches a player in this server (., *, a name) and one who is not (a name Cmdr resolves through GetUserIdFromNameAsync, or #userId for an account whose name is unknown or since changed).

There are two families here, and they reach the key differently.

The lock commands (datastore-lock-info, datastore-lock, datastore-unlock) write the key directly without opening a session, so they act on a lock left behind by a server that died -- the usual reason to reach for them.

The data commands (datastore-read-json, datastore-write-json, datastore-delete, datastore-copy) go through a real DataStore, which means they steal the session from whichever server holds it, including this one. That is deliberate: these exist to stress-test the session-locking system, and a read that cannot be starved of a current write is exactly what is being tested. A player whose session is stolen mid-play is disrupted -- they are kicked when their server notices -- so treat these as debug tooling rather than customer-service tooling.

WARNING

The session lock is soft. A loading session steals it once its retry ladder is exhausted, so datastore-lock parks a key for that long and no longer. Unlocking is the durable half.

INFO

TODO: give the read paths a read-only DataStore so they stop stealing. The load path already uses a plain GetAsync and takes no lock, so the store itself is a thin flag -- but a read that is guaranteed to see the current write needs MessagingService to ask the holding session to flush first, plus the edge cases around a session that never answers. Deferred rather than half-built.

Functions

Init

DataStoreCmdrService.Init(
serviceBagServiceBag
) → ()

Initializes the service. Should be done via ServiceBag.Init.

SetReplyConfig

DataStoreCmdrService.SetReplyConfig() → ()

Sets how long a command may run before it tells the executor it is still working, and how that line is colored.

Start

DataStoreCmdrService.Start(selfDataStoreCmdrService) → ()

Registers the commands. Should be done via ServiceBag.Start.

_promiseWithDataStore

DataStoreCmdrService._promiseWithDataStore(
userIdnumber,
doesWriteboolean,--

whether to flush before handing a live store back

handler(DataStore) → Promise<T>
) → Promise<T>

Opens the store for userId, runs handler against it, and puts it back.

PlayerDataStoreManager.PromiseDataStore opens a real session, which takes the lock from whoever holds it -- kicking that player when their server notices the theft. Releasing what was taken is therefore the part that matters: an orphaned lock is exactly what datastore-unlock exists to clear, and until it is dropped the player cannot rejoin.

So a store opened for an absent player is closed again, and the promise waits for that flush to land rather than reporting success while the lock is still held. A store belonging to a player in this server is left alone instead: no session was stolen, and removing it would strand a live one.

_executeForUserIds

DataStoreCmdrService._executeForUserIds(
contextCommandContext,
userIds{number},
handler(
number
) → Promise<string>
) → string

Runs handler once per target userId and renders the results back into the console.

Runs sequentially, to keep a batch from firing concurrent writes at the datastore. A target that fails reports on its own line rather than throwing into Cmdr or costing the rest of the batch its result.

Nothing reaches the console until every target is done, and a single target can sit in a load retry ladder for a long time, so a target still going after the reply config's slowReplySeconds says so.

Show raw api
{
    "functions": [
        {
            "name": "Init",
            "desc": "Initializes the service. Should be done via [ServiceBag.Init].",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "DataStoreCmdrService"
                },
                {
                    "name": "serviceBag",
                    "desc": "",
                    "lua_type": "ServiceBag"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 93,
                "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
            }
        },
        {
            "name": "SetReplyConfig",
            "desc": "Sets how long a command may run before it tells the executor it is still working, and how that\nline is colored.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "DataStoreCmdrService"
                },
                {
                    "name": "replyConfig",
                    "desc": "see [CmdrReplyUtils.createConfig]",
                    "lua_type": "CmdrReplyConfig"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 116,
                "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
            }
        },
        {
            "name": "Start",
            "desc": "Registers the commands. Should be done via [ServiceBag.Start].",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "DataStoreCmdrService"
                }
            ],
            "returns": [],
            "function_type": "static",
            "source": {
                "line": 128,
                "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
            }
        },
        {
            "name": "_promiseWithDataStore",
            "desc": "Opens the store for `userId`, runs `handler` against it, and puts it back.\n\n[PlayerDataStoreManager.PromiseDataStore] opens a real session, which takes the lock from whoever\nholds it -- kicking that player when their server notices the theft. Releasing what was taken is\ntherefore the part that matters: an orphaned lock is exactly what `datastore-unlock` exists to\nclear, and until it is dropped the player cannot rejoin.\n\nSo a store opened for an absent player is closed again, and the promise waits for that flush to\nland rather than reporting success while the lock is still held. A store belonging to a player in\nthis server is left alone instead: no session was stolen, and removing it would strand a live one.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "DataStoreCmdrService"
                },
                {
                    "name": "manager",
                    "desc": "",
                    "lua_type": "PlayerDataStoreManager"
                },
                {
                    "name": "userId",
                    "desc": "",
                    "lua_type": "number"
                },
                {
                    "name": "doesWrite",
                    "desc": "whether to flush before handing a live store back",
                    "lua_type": "boolean"
                },
                {
                    "name": "handler",
                    "desc": "",
                    "lua_type": "(DataStore) -> Promise<T>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "Promise<T>"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 316,
                "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
            }
        },
        {
            "name": "_executeForUserIds",
            "desc": "Runs `handler` once per target userId and renders the results back into the console.\n\nRuns sequentially, to keep a batch from firing concurrent writes at the datastore. A target that\nfails reports on its own line rather than throwing into Cmdr or costing the rest of the batch its\nresult.\n\nNothing reaches the console until every target is done, and a single target can sit in a load\nretry ladder for a long time, so a target still going after the reply config's\n`slowReplySeconds` says so.",
            "params": [
                {
                    "name": "self",
                    "desc": "",
                    "lua_type": "DataStoreCmdrService"
                },
                {
                    "name": "context",
                    "desc": "",
                    "lua_type": "CommandContext"
                },
                {
                    "name": "userIds",
                    "desc": "",
                    "lua_type": "{ number }"
                },
                {
                    "name": "handler",
                    "desc": "",
                    "lua_type": "(PlayerDataStoreManager, number) -> Promise<string>"
                }
            ],
            "returns": [
                {
                    "desc": "",
                    "lua_type": "string"
                }
            ],
            "function_type": "static",
            "source": {
                "line": 398,
                "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
            }
        }
    ],
    "properties": [],
    "types": [],
    "name": "DataStoreCmdrService",
    "desc": "Cmdr commands for inspecting and stress-testing player datastores, for customer service and\ndebugging.\n\nTargets come in as `playerIds`, so the same command reaches a player in this server (`.`, `*`, a\nname) and one who is not (a name Cmdr resolves through `GetUserIdFromNameAsync`, or `#userId` for\nan account whose name is unknown or since changed).\n\nThere are two families here, and they reach the key differently.\n\nThe lock commands (`datastore-lock-info`, `datastore-lock`, `datastore-unlock`) write the key\ndirectly without opening a session, so they act on a lock left behind by a server that died --\nthe usual reason to reach for them.\n\nThe data commands (`datastore-read-json`, `datastore-write-json`, `datastore-delete`,\n`datastore-copy`) go through a real [DataStore], which means they **steal the session** from\nwhichever server holds it, including this one. That is deliberate: these exist to stress-test the\nsession-locking system, and a read that cannot be starved of a current write is exactly what is\nbeing tested. A player whose session is stolen mid-play is disrupted -- they are kicked when their\nserver notices -- so treat these as debug tooling rather than customer-service tooling.\n\n:::warning\nThe session lock is soft. A loading session steals it once its retry ladder is exhausted, so\n`datastore-lock` parks a key for that long and no longer. Unlocking is the durable half.\n:::\n\n:::info\nTODO: give the read paths a read-only [DataStore] so they stop stealing. The load path already\nuses a plain `GetAsync` and takes no lock, so the store itself is a thin flag -- but a read that\nis guaranteed to see the *current* write needs MessagingService to ask the holding session to\nflush first, plus the edge cases around a session that never answers. Deferred rather than\nhalf-built.\n:::",
    "realm": [
        "Server"
    ],
    "source": {
        "line": 39,
        "path": "src/datastore/src/Server/Cmdr/DataStoreCmdrService.lua"
    }
}