ObjectStackObjectStack

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 no deviceId) refreshes that row and answers its deviceId; 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, FCM UNREGISTERED), the row is kept with enabled: false and invalidatedAt set, 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 clears invalidatedAt.

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

PropertyTypeRequiredDescription
deviceIdstring✅Client-held device id, stable per app install: the one the registration carried or the server minted. Unique per user
userIdstring✅The user the device belongs to: the session user who registered it. No organization member
vendorEnum<'apns' | 'fcm'>✅Push vendor the token belongs to: the registration's vendor, or the one derived from platform
tokenstring✅Vendor push token. Held by one live device at a time: a second user's registration moves it
platformEnum<'ios' | 'android' | 'web'>✅Device platform, as the registration named it
appIdstringoptionalApp the token was issued to: the iOS bundle ID or the Android application ID
localestringoptionalDevice locale (BCP 47, for example zh-CN) that pushes to this device are rendered in
namestringoptionalDevice friendly name, as the registration named it
enabledboolean✅False once the vendor reported the token dead; nothing is delivered to a disabled device
invalidatedAtstringoptionalWhen 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
lastSeenAtstringoptionalWhen 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

On this page