@phosphorco/bb-identity 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/client.d.ts ADDED
@@ -0,0 +1,175 @@
1
+ /** Headless client, view transitions, and reusable state/view lifecycle binding. */
2
+ import type { Codec, Directory, Disposable, IdentityError, IdentityKey, Json, OwnerSessionId, Revision,
3
+ ActorReference, ParticipantReader, PersonReference, ReadOptions, Result, Scheduler, TargetSelection, Unsubscribe } from './model.js';
4
+ import type { ServerSession } from './host.js';
5
+ import type { IdentityState, PendingDraft, SyncSnapshot, DraftCheckpoint, ConflictDecision,
6
+ StateResource, StateTransport, DraftStorage, ConflictContext } from './state.js';
7
+ /** Ready snapshots expose instance, actor and session stamp together. */
8
+ export type IdentitySession = ServerSession | { readonly status: 'loading' } | { readonly status: 'disposed' };
9
+ export type ClientInvalidation =
10
+ | { readonly kind: 'session'; readonly reason?: 'actor' | 'provider' | 'capabilities' }
11
+ /** `revision: null` means clear all directory/search cache; it is never a synthetic ordering token. */
12
+ | { readonly kind: 'directory'; readonly keys: readonly IdentityKey[]; readonly revision: Revision | null }
13
+ | { readonly kind: 'participants'; readonly threadIds: readonly string[] }
14
+ | { readonly kind: 'disconnected' | 'reconnected' };
15
+ export declare const clientInvalidationCodec: Codec<ClientInvalidation>;
16
+ export type ConnectionLinkHealth =
17
+ | { readonly status: 'connecting' | 'healthy' | 'reconnecting' }
18
+ | { readonly status: 'unavailable'; readonly error: IdentityError };
19
+ /** Identity and resource paths share one owner but fail/recover independently. */
20
+ export interface ConnectionHealth {
21
+ readonly generation: number;
22
+ readonly identity: ConnectionLinkHealth;
23
+ readonly state: ConnectionLinkHealth;
24
+ }
25
+ export type IdentityConnectionEvent = ClientInvalidation | { readonly kind: 'state'; readonly event: import('./state.js').StateInvalidation };
26
+ /** One root owns this connection. Identity and all StateResource transports borrow it. */
27
+ export interface IdentityConnection extends Disposable {
28
+ getHealth(): ConnectionHealth;
29
+ subscribeHealth(listener: () => void): Unsubscribe;
30
+ /** Package route name; callers cannot send a client-supplied actor or plugin identity. */
31
+ request(method: string, input: Json, options?: ReadOptions): Promise<unknown>;
32
+ subscribe(listener: (event: IdentityConnectionEvent) => void): Unsubscribe;
33
+ /** Re-establishes route reachability and verifies authoritative identity freshness.
34
+ * It does not reload unknown StateResources; their controllers reload/reconcile separately. */
35
+ revalidate(options?: ReadOptions): Promise<Result<void>>;
36
+ }
37
+ export interface IdentityClientTransport {
38
+ bootstrap(options?: ReadOptions): Promise<Result<ServerSession>>;
39
+ selfProfile(options?: ReadOptions): Promise<Result<import('./model.js').IdentityProfile>>;
40
+ readonly directory: Directory; readonly participants: ParticipantReader;
41
+ subscribe(listener: (event: ClientInvalidation) => void): Unsubscribe;
42
+ }
43
+ /** Caller owns this instance; injected providers/views borrow it. start/dispose are idempotent. */
44
+ export interface IdentityClient extends Disposable {
45
+ readonly connection: IdentityConnection;
46
+ getSnapshot(): IdentitySession;
47
+ /** Safe live accessor for controller dispatch. Loading/disposed return errors; never last-ready authority. */
48
+ currentSession(): Result<ServerSession>;
49
+ subscribe(listener: () => void): Unsubscribe;
50
+ start(): Promise<Result<void>>;
51
+ refresh(options?: ReadOptions): Promise<Result<void>>;
52
+ /** Request-aware resolver/session presentation, including resolver-only providers. */
53
+ selfProfile(options?: ReadOptions): Promise<Result<import('./model.js').IdentityProfile>>;
54
+ readonly directory: Directory; readonly participants: ParticipantReader;
55
+ }
56
+ /** Defaults: 512 profiles, 60s stale time, 64 keys/batch, 4 concurrent reads.
57
+ * Snapshot identity is stable until change; shared reads have independent cancellation per waiter.
58
+ * The last observer releases its subscription. Refresh/reconnect fences older bootstrap completions.
59
+ */
60
+ export declare function createIdentityClient(options: {
61
+ readonly connection: IdentityConnection; readonly scheduler?: Scheduler;
62
+ readonly profileCache?: { readonly maxEntries: number; readonly staleAfterMs: number };
63
+ }): IdentityClient;
64
+ export type ViewSnapshot =
65
+ | { readonly status: 'ready'; readonly session: Extract<ServerSession, { status: 'ready' }>;
66
+ readonly subject: ActorReference; readonly overriding: boolean; readonly viewGeneration: number }
67
+ | { readonly status: 'loading' }
68
+ | { readonly status: 'blocked'; readonly session: IdentitySession; readonly requestedSubject: IdentityKey | null;
69
+ readonly lastReady: Extract<ViewSnapshot, { status: 'ready' }> | null; readonly error: IdentityError };
70
+ export type PendingEditPolicy = 'block' | 'flush' | 'preserve' | 'discard';
71
+ export interface ViewTransition {
72
+ readonly id: string; readonly from: ViewSnapshot; readonly selection: TargetSelection;
73
+ readonly pending: PendingEditPolicy; readonly signal: AbortSignal;
74
+ }
75
+ /** Phase 1 is non-destructive. No guard may discard/change ownership before every guard approves. */
76
+ export interface PreparedViewTransition {
77
+ /** May flush/persist. Failure cancels the selection; already accepted writes are not rolled back. */
78
+ prepare(): Promise<Result<void>>;
79
+ /** Synchronous, non-failing finalization only after all preparation and freshness checks pass. */
80
+ commit(): void;
81
+ cancel(): void;
82
+ }
83
+ export interface ViewTransitionGuard {
84
+ inspect(transition: ViewTransition): Result<PreparedViewTransition>;
85
+ /** Mandatory invalidation immediately stops stale writes. It cannot be blocked by voluntary guards. */
86
+ invalidate(reason: 'actor' | 'session' | 'disconnected' | 'disposed'): void;
87
+ }
88
+ export interface IdentityView extends Disposable {
89
+ getSnapshot(): ViewSnapshot;
90
+ subscribe(listener: () => void): Unsubscribe;
91
+ select(selection: TargetSelection, options?: { readonly pendingEdits?: PendingEditPolicy }): Promise<Result<void>>;
92
+ reset(options?: { readonly pendingEdits?: PendingEditPolicy }): Promise<Result<void>>;
93
+ registerGuard(guard: ViewTransitionGuard): Unsubscribe;
94
+ }
95
+ /** Borrows client. Owns guards/subscriptions only. New transitions cancel superseded work.
96
+ * Ready singleton: subject=actor; self/reset idempotent; selecting another target returns unsupported.
97
+ */
98
+ export declare function createIdentityView(options: {
99
+ readonly client: IdentityClient; readonly initialSelection?: TargetSelection;
100
+ readonly pendingEdits?: PendingEditPolicy;
101
+ }): IdentityView;
102
+ export interface IdentityStateBinding<T> extends Disposable {
103
+ getSnapshot(): SyncSnapshot<T> | { readonly status: 'waiting-for-identity' };
104
+ subscribe(listener: () => void): Unsubscribe;
105
+ edit(value: T): Result<void>;
106
+ flush(): Promise<Result<void>>;
107
+ /** Token is the current controller incarnation, including while blocked; null before creation.
108
+ * Recovery/conflict commands check it before work AND after awaits, returning stale-context on change.
109
+ */
110
+ currentOwnerSession(): OwnerSessionId | null;
111
+ /** Revalidates this exact writable controller incarnation, then runs its
112
+ * bounded reconciliation. The owner token is checked before revalidation,
113
+ * before controller work, and after it settles; a changed view/session
114
+ * returns stale-context rather than recovering a replacement controller. */
115
+ reconnect(expected: OwnerSessionId): Promise<Result<void>>;
116
+ recoveryCandidates(expected: OwnerSessionId, options?: ReadOptions): Promise<Result<readonly DraftCheckpoint<T>[]>>;
117
+ checkpoint(expected: OwnerSessionId): Promise<Result<DraftCheckpoint<T> | null>>;
118
+ recover(expected: OwnerSessionId, checkpoint: DraftCheckpoint<T>): Promise<Result<void>>;
119
+ /** Explicitly forget one exact checkpoint from the current actor/resource
120
+ * recovery list. Revision-conditional removal preserves newer drafts. */
121
+ discardRecovery(expected: OwnerSessionId, checkpoint: DraftCheckpoint<T>): Promise<Result<void>>;
122
+ resolveConflict(expected: OwnerSessionId, token: import('./model.js').ConflictToken, decision: ConflictDecision<T>): Promise<Result<void>>;
123
+ }
124
+ export type StateBindingSource<T> =
125
+ | { create(input: { readonly session: Extract<ServerSession, { status: 'ready' }>;
126
+ readonly subject: ActorReference; readonly ownerSession: OwnerSessionId }): IdentityState<T>;
127
+ readonly resource?: never }
128
+ | { readonly create?: never; readonly resource: StateResource<T>; readonly recordId: string;
129
+ /** Omit for preferences whose unsaved edits are deliberately in-session only. */
130
+ readonly drafts?: DraftStorage<T>;
131
+ readonly onConflict: (context: ConflictContext<T>) => ConflictDecision<T>;
132
+ readonly initializeEmpty: boolean; readonly scheduler?: Scheduler };
133
+ /** Owns controllers and transition guards; borrows view/client. Handles invalidation and reconnect centrally. */
134
+ export interface IdentityStateBindingOptions<T> {
135
+ readonly client: IdentityClient; readonly view: IdentityView;
136
+ readonly target: 'actor' | 'viewed-subject';
137
+ readonly editPolicy: 'actor-only' | 'collaborators';
138
+ /** Called only when this binding opted into durable draft preservation. */
139
+ readonly onUnpersistedDraft?: (draft: PendingDraft<T>, error: IdentityError) => void;
140
+ }
141
+ export declare function bindIdentityState<T>(options: IdentityStateBindingOptions<T> & StateBindingSource<T>): IdentityStateBinding<T>;
142
+
143
+ export type DirectorySearchSnapshot =
144
+ | { readonly status: 'loading' }
145
+ | { readonly status: 'ready'; readonly page: import('./model.js').Page<import('./model.js').IdentityProfile> }
146
+ | { readonly status: 'error'; readonly error: IdentityError };
147
+ export interface DirectorySearch extends Disposable {
148
+ getSnapshot(): DirectorySearchSnapshot;
149
+ subscribe(listener: () => void): Unsubscribe;
150
+ search(query: import('./model.js').DirectoryQuery): Promise<Result<void>>;
151
+ next(): Promise<Result<void>>;
152
+ }
153
+ /** Reusable for filters/recipients; does not select a viewed identity. */
154
+ export declare function createDirectorySearch(client: IdentityClient): DirectorySearch;
155
+
156
+ /** Raw network seam for decoder tests and non-React integrations. Never accepts client-supplied actor authority. */
157
+ /** Explicit low-level inputs for tests and non-React native roots. Health controls all borrowed state transports. */
158
+ export interface IdentityConnectionInputs {
159
+ readonly request: IdentityConnection['request'];
160
+ readonly subscribe: (listener: (payload: unknown) => void) => Unsubscribe;
161
+ readonly getHealth: () => ConnectionHealth;
162
+ readonly subscribeHealth: (listener: () => void) => Unsubscribe;
163
+ readonly revalidate: (options?: ReadOptions) => Promise<Result<void>>;
164
+ readonly dispose?: () => void;
165
+ }
166
+ export declare function createIdentityConnection(inputs: IdentityConnectionInputs): IdentityConnection;
167
+ /** Owns runtime decoding of all identity responses and invalidations; malformed != empty/default-user. */
168
+ export declare function createIdentityClientTransport(connection: IdentityConnection): IdentityClientTransport;
169
+ /** Explicit independent-root fallback. It uses ordinary request/response routes, not a retained poll feed.
170
+ * endpoint is the owning plugin's HTTP base URL; fetch and root signal are caller-owned inputs.
171
+ */
172
+ export declare function createIdentityFetchConnection(options: {
173
+ readonly endpoint: URL; readonly fetch: typeof globalThis.fetch; readonly signal: AbortSignal;
174
+ readonly scheduler?: Scheduler;
175
+ }): IdentityConnection;