Call one action and wait for the reply.
defaultValue behaves as it does everywhere else in the SDK: a failed round trip
resolves to it rather than throwing, so a missing server half degrades to an empty
list instead of a crashed app. Omit it when a failure should surface — a write
wrapped in useAppAction wants the error so it can toast it.
Talk to your own server service.
Every other data hook —
useNotes,useContacts,useMessages— is core code named after an app, backed by a store inweb/src/services/and a row per action inshared/routes.ts. That works for apps shipped in this repository and is unavailable to anybody else: an app installed from the Store cannot add a hook to the SDK, a store to core, or a route to the table. Which meant the add-on path supported UI-only apps, andsdk/coreBoundary.test.tsmeasures exactly how much of Notes and Blabber depends on being first-party.This is the general door. One NUI callback carries
{ service, action, data }and the client derives the event from the two segments, so an app reaches its own service without core knowing its name.What this does not change
Authority.
ServiceEndpointstill authenticates the caller, rate-limits per(source, service, action)and reduces the payload to the schema's allowlist. A NUI request was never proof of intent and is not now (§2.9) — this widens who can ask, not what the server agrees to.Whose service. The id is your own app id, or
<appId>_<anything>for a second table of yours.sdk/permissions.test.tsfails any other literal and any non-literal.The named routes. They stay, and
routes.test.tskeeps cross-referencing them againstfetchNuicalls, server registrations and the browser mock. That check catches the missing-layer bug that silently does nothing in game, and it is worth keeping for in-tree apps. This is the path for apps the table cannot cover.What you still have to do yourself
There is no store here, and that is deliberate rather than unfinished.
createCrudStoreandcreatePagedStorelive in core because core's own services use them; an add-on holds its own state in its own module, which is the one part of being an add-on that is genuinely different rather than accidentally so.