-
Notifications
You must be signed in to change notification settings - Fork 6
Expand file tree
/
Copy pathrealtime-service.ts
More file actions
144 lines (133 loc) · 5.22 KB
/
Copy pathrealtime-service.ts
File metadata and controls
144 lines (133 loc) · 5.22 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
// Copyright (c) 2025 ObjectStack. Licensed under the Apache-2.0 license.
/**
* IRealtimeService - Realtime / PubSub Service Contract
*
* Defines the interface for realtime event subscription and publishing
* in ObjectStack. Concrete implementations (WebSocket, SSE, Socket.IO, etc.)
* should implement this interface.
*
* Follows Dependency Inversion Principle - plugins depend on this interface,
* not on concrete realtime transport implementations.
*
* Aligned with CoreServiceName 'realtime' in core-services.zod.ts.
*/
/**
* A realtime event payload
*/
export interface RealtimeEventPayload {
/** Event type (e.g. 'record.created', 'record.updated') */
type: string;
/** Object name the event relates to */
object?: string;
/** Event data */
payload: Record<string, unknown>;
/** Timestamp (ISO 8601) */
timestamp: string;
}
/**
* Handler function for realtime event subscriptions
*/
export type RealtimeEventHandler = (event: RealtimeEventPayload) => void | Promise<void>;
/**
* Subscription options for filtering events
*/
export interface RealtimeSubscriptionOptions {
/** Object name to filter events for */
object?: string;
/** Event types to listen for */
eventTypes?: string[];
/**
* Additional filter conditions.
*
* ⚠️ EXPERIMENTAL — declared but NOT evaluated by the in-memory adapter
* (`matchesSubscription` reads only `object` + `eventTypes`). Do not rely
* on it to narrow delivery, and NEVER as an authorization mechanism
* (see the identity-admission note on {@link IRealtimeService}).
*/
filter?: Record<string, unknown>;
}
/**
* Enhanced subscription filter for metadata and data events
*/
export interface RealtimeSubscriptionFilter {
/** Metadata type filter (object, view, agent, tool, etc.) */
type?: string;
/** Package ID filter */
packageId?: string;
/** Event types to listen for */
eventTypes?: string[];
/** Record ID filter (for data events) */
recordId?: string;
/** Field names filter (for data events) */
fields?: string[];
}
/**
* ⚠️ Identity admission — READ BEFORE WIRING A CLIENT TRANSPORT (#2992,
* ADR-0096 D4).
*
* This contract currently serves TRUSTED, SERVER-INTERNAL subscribers only
* (webhook auto-enqueuer, knowledge sync). Delivery is a pure fan-out with
* **no per-recipient authorization seam**: subscriptions carry no principal,
* `matchesSubscription` filters only by object name + event type, and the
* engine publishes the FULL record body (`after` row) — rows and fields a
* subscriber's own `find` would hide under RLS/FLS/tenant scoping.
*
* Before ANY end-user transport ships (`handleUpgrade` WebSocket, SSE, a REST
* subscribe route, or a real client in `@objectstack/client`), the delivery
* path MUST gain one of:
* 1. a per-recipient re-check on delivery — the subscription carries the
* subscriber's `ExecutionContext` and every event is re-authorized
* (RLS/FLS/tenant) against it before the handler fires; or
* 2. id-only payloads — the client re-fetches the record under its own
* authority.
*
* Wiring a transport without this is a fall-open (full-authority broadcast,
* cross-tenant). The authz conformance matrix pins this posture
* (`realtime-delivery-authz` row + transport tripwire probes in
* `dogfood/test/authz-conformance.test.ts`) so CI blocks it, not review.
*/
export interface IRealtimeService {
/**
* Publish an event to all subscribers
* @param event - The event to publish
*/
publish(event: RealtimeEventPayload): Promise<void>;
/**
* Subscribe to realtime events
* @param channel - Channel/topic name
* @param handler - Event handler function
* @param options - Optional subscription filters
* @returns Subscription identifier for unsubscribing
*/
subscribe(channel: string, handler: RealtimeEventHandler, options?: RealtimeSubscriptionOptions): Promise<string>;
/**
* Unsubscribe from a channel
* @param subscriptionId - Subscription identifier returned by subscribe()
*/
unsubscribe(subscriptionId: string): Promise<void>;
/**
* Handle an incoming HTTP upgrade request (WebSocket handshake)
*
* ⚠️ Deliberately UNIMPLEMENTED platform-wide: implementing this hands
* external clients the unauthorized fan-out described on the interface —
* satisfy the identity-admission requirement above first (#2992).
*
* @param request - Standard Request object
* @returns Standard Response object
*/
handleUpgrade?(request: Request): Promise<Response>;
/**
* Subscribe to metadata events (convenience method)
* @param filter - Subscription filter
* @param handler - Event handler function
* @returns Subscription identifier for unsubscribing
*/
subscribeMetadata?(filter: RealtimeSubscriptionFilter, handler: RealtimeEventHandler): Promise<string>;
/**
* Subscribe to data events (convenience method)
* @param filter - Subscription filter
* @param handler - Event handler function
* @returns Subscription identifier for unsubscribing
*/
subscribeData?(filter: RealtimeSubscriptionFilter, handler: RealtimeEventHandler): Promise<string>;
}