// © 2026 Adobe. MIT License. See /LICENSE for details. /** * Compile-time tests proving that `DatabaseElement.service` exposes a * UI-safe view of the underlying Database — every Database method that * originally returned a non-`void`, non-`Observe` result has its return * type rewritten to `void`, while Observe surfaces pass through unchanged. * * If `UIService.FromService` ever stops applying to the inferred * `service` type, one or more of these assertions will fail at build time. */ /* eslint-disable @typescript-eslint/no-unused-vars */ import { Database } from "@adobe/data/ecs"; import { Observe } from "@adobe/data/observe"; import { UIService } from "@adobe/data/service"; import { DatabaseElement } from "./database-element.js"; const plugin = Database.Plugin.create({ resources: { count: { default: 0 as number }, }, transactions: { increment: (t) => { t.resources.count = t.resources.count + 1; }, }, }); class _CountElement extends DatabaseElement { get plugin() { return plugin; } } type ServiceType = _CountElement["service"]; type RawDatabase = Database.Plugin.ToDatabase; // 0. `service` is a read/write accessor: the getter returns the restricted view, // while the setter accepts the full database (injection). The divergent // get/set types are intentional (legal since TS 5.1). const _injectFullDatabase = (el: _CountElement, db: RawDatabase): void => { el.service = db; }; // A side effect of the divergence: `el.service = el.service` is a type error, // since the restricted getter type is not assignable to the full setter type. const _rejectRestrictedAssignment = (el: _CountElement): void => { // @ts-expect-error restricted view is not assignable back to the full database el.service = el.service; }; // 0a. The full database is fully encapsulated: there is no `database` member of // any visibility on the instance type. `service` is the only surface. const _noPublicDatabaseProperty = (el: _CountElement): void => { // @ts-expect-error `database` no longer exists; the full db is hard-private void el.database; }; type _CheckNoDatabaseKey = Assert, never>>; // 1. The exposed service type is the UIService-restricted view of the database. type _CheckRestricted = Assert>>; // 2. Observe surfaces survive the restriction. type _CheckResourceObserveSurvives = Assert< Equal> >; type _CheckEnvelopeObserveSurvives = Assert< Equal >; // 3. `apply` raw return type is non-void; restriction rewrites it to void. type RawApply = RawDatabase["apply"]; type RestrictedApply = ServiceType["apply"]; type _CheckRawApplyIsNotVoid = Assert, void>>; type _CheckRestrictedApplyIsVoid = Assert, void>>; type _CheckRestrictedApplyKeepsArgs = Assert< Equal, Parameters> >; // 4. `toData` raw return is `unknown`; restriction rewrites to void. type _CheckRawToDataNotVoid = Assert, void>>; type _CheckRestrictedToDataVoid = Assert, void>>; // 5. Already-void functions pass through unchanged. type _CheckResetStillVoid = Assert, void>>; type _CheckCancelStillVoid = Assert, void>>; // 6. Transaction functions (nested under `transactions`) get their return // rewritten to void by the same recursion. The plugin's `increment` // happens to already be void, so we check the structure stays a function // returning void after the restriction. type _CheckIncrementReturnsVoid = Assert< Equal, void> >; // ---------------------------------------------------------------------------- // Local helpers (kept inline so this file stays a self-contained type test). // ---------------------------------------------------------------------------- type Assert = T; type Equal = (() => T extends X ? 1 : 2) extends (() => T extends Y ? 1 : 2) ? true : false; type NotEqual = Equal extends true ? false : true;