Skip to main content

Printer Config Module

Technical reference for apps/backend/src/printer-config/.

This module is the tenant-scoped home for printer definitions that used to live only in Print Bridge's per-device config.json. Three features layered on the same hexagonal folder:

FeatureWhat it added
050-mobile-printing-foundationTables + device-token read path + adoption flag
052-mobile-generic-printingMerchant session CRUD, test-print render, station claims
053-mobile-vendor-printingVendor columns, discovery telemetry hashing, businessId on list

Foundation overview: Mobile Printing Foundation. Vendor registry: Printer Vendor Port.


Hexagonal map​

Domain​

Repository ports only — no framework imports:

  • IPrinterConfigRepository
  • IPrinterDiscoveryEventRepository
  • IPrinterOrchestratorSettingRepository

Application​

ServiceUse case
PrinterConfigServiceDevice-token list for Print Bridge
PrinterOrchestratorFlagServiceAdoption flag. No row means off.
PrinterConfigManagementServiceMerchant save/update/remove, station assignment
TestPrintRenderServiceEncode a test slip; does not print
MobileClientRegistrationServiceStation claims, release, heartbeat
StationServiceStatusServiceWho is serving each assigned station
PrinterDiscoveryEventServiceHashed scan telemetry
VendorResolutionServicePassive backfill of vendor on saved rows
DiscoveryIdentityHasherServicePer-business identity hashes

Infrastructure​

Kysely adapters in infrastructure/. Soft-delete on remove keeps history (FR-021) and releases station assignments so nothing is still routed there.

Interfaces​

Two auth surfaces, deliberately separate (design §1a — Print Bridge's path is untouched):

ControllerAuthAudience
PrinterConfigController GET /printer-config@IsPublic() + DeviceGuardPrint Bridge
PrinterDiscoveryEventControllerDevice tokenPrint Bridge scans
PrinterConfigManagementController /printer-config/manageFirebase session + RolesGuardMobile / PWA merchants

Domain concepts​

A printer is a tenant-scoped row (business_id + location_id) with a connection, optional device binding, optional station list, and a role (receipt or kitchen). Station assignment is not exclusive — a station may appear on more than one printer.

printer_orchestrator_setting is the adoption flag. Absence of a row is off. No row is auto-created.

printer_discovery_event is append-only scan telemetry. Identifiers are hashed per business before write. Display names, hosts, and MACs are stripped.


Merchant API (/printer-config/manage)​

Tenant scope is the session plus ?locationId=. businessId is never taken from a body.

MethodPathPermissionNotes
GET/printer-config/manage?locationId=Printer ReadWith deviceId, returns network printers plus this device's bound ones. Response includes businessId and servedBy.
POST/printer-config/manage?locationId=Printer CreateUpsert by stable identity — a DHCP reassignment does not duplicate the row.
PATCH/printer-config/manage/:idPrinter UpdateRename, address, profile, vendorLockedGeneric.
PUT/printer-config/manage/:id/stationsPrinterStationAssignment UpdateOnly writer of station assignments.
POST/printer-config/manage/:id/test-printPrinter UpdateReturns { payloadB64, cols }. Caller enqueues into the device outbox.
DELETE/printer-config/manage/:idPrinter DeleteSoft delete; 204.
POST/printer-config/manage/claimsPrinterStationAssignment UpdateClaim stations. Caller must already hold a ready printer connection.
DELETE/printer-config/manage/claims/:deviceId?stationId=PrinterStationAssignment UpdateRelease one station.
POST/printer-config/manage/claims/heartbeatPrinterStationAssignment UpdateEvery 20s; 60s staleness cutoff.
POST/printer-config/manage/discovery-eventsPrinter ReadBest-effort telemetry. Failure must not block setup.

Printer vs PrinterStationAssignment is blast radius: setting up a printer affects one device; assigning a station stands Print Bridge down for that station. Retail merchants with no kitchen stations can still save a receipt printer.


Device API (GET /printer-config)​

Print Bridge polls this with its pairing token. businessId / locationId come from the device row. Returns { orchestratorEnabled, printers }.

Kitchen-ticket dispatch uses the shared orchestrator only when the flag is on. Document/receipt jobs keyed by receiptPrinter / targetPrinterId stay on the legacy path.


Constraints​

  • The backend never delivers bytes to a printer. Test-print render and document encode stop at payload; the mobile outbox or Print Bridge writes.
  • vendor accepted on save is a device-side resolution. A wrong value costs capability on that device, not tenant isolation.
  • vendorLockedGeneric does not clear vendor. Undo restores the vendor path without a re-probe.
  • Listing omits soft-deleted rows. History stays in the table.