Push Device schema — System Protocol reference
Push Device Protocol — the stored device shape of the push device registry.
Push Device Protocol — the stored device shape of the push device registry (ADR-0012 §6, narrowed to what core ships).
One row per (user, app install): what a device registration
(RegisterDeviceRequestSchema, api/protocol.zod.ts) produces, what the
push channel reads to address a phone, and what the registry's listing
answers (INotificationService.listDevices).
Declared ahead of its producer. No table stores it and no route serves
it yet; the schema carries a [PLANNED — not yet served] marker in its
.describe() until the device registry lands (in the push channel plugin's
sys_user_device, ADR-0012 §11).
Three rules the registry owes, stated here because they decide the shape:
- (a) A device belongs to a user, not to an organization. The row has no
organization member. A phone signed in to one person receives that
person's notifications whichever organization they come from; the
organization travels on the notification, never on the device. The
identity of a row is (
userId,deviceId). - (b) One live row per token. A vendor token is delivered to one row
only. A token this user already holds under another
deviceId(the app's storage was cleared, so the registration carries nodeviceId) refreshes that row and answers itsdeviceId; it never makes a second live row. When a second user registers a token another user's device holds, the token moves to the second user and the first user's row for it is removed, so a push meant for the phone's previous user never reaches its new one. - (c) A dead token keeps its row. When the vendor reports a token dead
(APNs
BadDeviceToken/Unregistered, FCMUNREGISTERED), the row is kept withenabled: falseandinvalidatedAtset, so "why did this phone stop getting pushes" stays answerable. Nothing is delivered to a disabled row, and a later registration of the same device re-enables it and clearsinvalidatedAt.
Narrowed from ADR-0012 §6: vendor is the core pair (apns | fcm; the
domestic Android vendors plug in as alternate channel implementations) and
platform is the registration's own three (ios | android | web).
Source: packages/spec/src/system/push-device.zod.ts
TypeScript Usage
import { UserDeviceSchema } from '@objectstack/spec/system';
import type { UserDevice } from '@objectstack/spec/system';
// Validate data
const result = UserDeviceSchema.parse(data);UserDevice
[PLANNED — not yet served] A push device of one user: one row per app install. No table stores it yet; the device registry lands with the push channel
Properties
| Property | Type | Required | Description |
|---|---|---|---|
| deviceId | string | ✅ | Client-held device id, stable per app install: the one the registration carried or the server minted. Unique per user |
| userId | string | ✅ | The user the device belongs to: the session user who registered it. No organization member |
| vendor | Enum<'apns' | 'fcm'> | ✅ | Push vendor the token belongs to: the registration's vendor, or the one derived from platform |
| token | string | ✅ | Vendor push token. Held by one live device at a time: a second user's registration moves it |
| platform | Enum<'ios' | 'android' | 'web'> | ✅ | Device platform, as the registration named it |
| appId | string | optional | App the token was issued to: the iOS bundle ID or the Android application ID |
| locale | string | optional | Device locale (BCP 47, for example zh-CN) that pushes to this device are rendered in |
| name | string | optional | Device friendly name, as the registration named it |
| enabled | boolean | ✅ | False once the vendor reported the token dead; nothing is delivered to a disabled device |
| invalidatedAt | string | optional | When the vendor reported the token dead. Present exactly while enabled is false: absent on an enabled device, and cleared by the registration that re-enables it |
| lastSeenAt | string | optional | When the device last registered or refreshed. Every registration and refresh sets it, so it is absent only on a row no registration wrote; listDevices answers such a device last |