@constructive-io/graphql-codegen 4.38.2 → 4.39.1
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/core/codegen/barrel.d.ts +6 -0
- package/core/codegen/barrel.js +26 -1
- package/core/codegen/index.d.ts +3 -1
- package/core/codegen/index.js +33 -1
- package/core/codegen/orm/client-generator.d.ts +6 -0
- package/core/codegen/orm/client-generator.js +12 -0
- package/core/codegen/orm/client.d.ts +43 -0
- package/core/codegen/orm/client.js +16 -0
- package/core/codegen/orm/index.d.ts +1 -1
- package/core/codegen/orm/index.js +5 -2
- package/core/codegen/subscriptions.d.ts +30 -0
- package/core/codegen/subscriptions.js +270 -0
- package/core/codegen/templates/orm-client.ts +91 -0
- package/core/codegen/templates/orm-realtime.ts +267 -0
- package/core/codegen/utils.d.ts +15 -0
- package/core/codegen/utils.js +26 -0
- package/esm/core/codegen/barrel.d.ts +6 -0
- package/esm/core/codegen/barrel.js +26 -2
- package/esm/core/codegen/index.d.ts +3 -1
- package/esm/core/codegen/index.js +30 -2
- package/esm/core/codegen/orm/client-generator.d.ts +6 -0
- package/esm/core/codegen/orm/client-generator.js +11 -0
- package/esm/core/codegen/orm/client.d.ts +43 -0
- package/esm/core/codegen/orm/client.js +16 -0
- package/esm/core/codegen/orm/index.d.ts +1 -1
- package/esm/core/codegen/orm/index.js +5 -3
- package/esm/core/codegen/subscriptions.d.ts +30 -0
- package/esm/core/codegen/subscriptions.js +232 -0
- package/esm/core/codegen/utils.d.ts +15 -0
- package/esm/core/codegen/utils.js +23 -0
- package/esm/types/schema.d.ts +2 -0
- package/package.json +4 -4
- package/types/schema.d.ts +2 -0
|
@@ -15,12 +15,36 @@ import type {
|
|
|
15
15
|
} from '@constructive-io/graphql-query/runtime';
|
|
16
16
|
import { createFetch } from '@constructive-io/graphql-query/runtime';
|
|
17
17
|
|
|
18
|
+
import type {
|
|
19
|
+
ConnectionState,
|
|
20
|
+
ConnectionStateListener,
|
|
21
|
+
RealtimeConfig,
|
|
22
|
+
SubscribeOptions,
|
|
23
|
+
SubscriptionEvent,
|
|
24
|
+
SubscriptionFieldMeta,
|
|
25
|
+
Unsubscribe,
|
|
26
|
+
} from './realtime';
|
|
27
|
+
import { RealtimeManager } from './realtime';
|
|
28
|
+
|
|
18
29
|
export type {
|
|
19
30
|
GraphQLAdapter,
|
|
20
31
|
GraphQLError,
|
|
21
32
|
QueryResult,
|
|
22
33
|
} from '@constructive-io/graphql-query/runtime';
|
|
23
34
|
|
|
35
|
+
export type {
|
|
36
|
+
ConnectionState,
|
|
37
|
+
ConnectionStateListener,
|
|
38
|
+
RealtimeConfig,
|
|
39
|
+
SubscribeOptions,
|
|
40
|
+
SubscriptionEvent,
|
|
41
|
+
SubscriptionFieldMeta,
|
|
42
|
+
SubscriptionOperation,
|
|
43
|
+
Unsubscribe,
|
|
44
|
+
WsClient,
|
|
45
|
+
} from './realtime';
|
|
46
|
+
export { RealtimeManager } from './realtime';
|
|
47
|
+
|
|
24
48
|
/**
|
|
25
49
|
* Default adapter that uses fetch for HTTP requests.
|
|
26
50
|
*
|
|
@@ -117,6 +141,12 @@ export interface OrmClientConfig {
|
|
|
117
141
|
fetch?: typeof globalThis.fetch;
|
|
118
142
|
/** Custom adapter for GraphQL execution (overrides endpoint/headers/fetch) */
|
|
119
143
|
adapter?: GraphQLAdapter;
|
|
144
|
+
/**
|
|
145
|
+
* Optional realtime (WebSocket) configuration.
|
|
146
|
+
* When provided, enables subscription methods on models.
|
|
147
|
+
* The WebSocket connection is created lazily on first subscribe().
|
|
148
|
+
*/
|
|
149
|
+
realtime?: RealtimeConfig;
|
|
120
150
|
}
|
|
121
151
|
|
|
122
152
|
/**
|
|
@@ -135,6 +165,7 @@ export class GraphQLRequestError extends Error {
|
|
|
135
165
|
|
|
136
166
|
export class OrmClient {
|
|
137
167
|
private adapter: GraphQLAdapter;
|
|
168
|
+
private realtimeManager?: RealtimeManager;
|
|
138
169
|
|
|
139
170
|
constructor(config: OrmClientConfig) {
|
|
140
171
|
if (config.adapter) {
|
|
@@ -150,6 +181,10 @@ export class OrmClient {
|
|
|
150
181
|
'OrmClientConfig requires either an endpoint or a custom adapter',
|
|
151
182
|
);
|
|
152
183
|
}
|
|
184
|
+
|
|
185
|
+
if (config.realtime) {
|
|
186
|
+
this.realtimeManager = new RealtimeManager(config.realtime);
|
|
187
|
+
}
|
|
153
188
|
}
|
|
154
189
|
|
|
155
190
|
async execute<T>(
|
|
@@ -159,6 +194,34 @@ export class OrmClient {
|
|
|
159
194
|
return this.adapter.execute<T>(document, variables);
|
|
160
195
|
}
|
|
161
196
|
|
|
197
|
+
/**
|
|
198
|
+
* Subscribe to a GraphQL subscription operation.
|
|
199
|
+
* Used by generated model subscribe() methods.
|
|
200
|
+
* @throws Error if realtime is not configured
|
|
201
|
+
*/
|
|
202
|
+
subscribe<T>(
|
|
203
|
+
meta: SubscriptionFieldMeta,
|
|
204
|
+
document: string,
|
|
205
|
+
variables: Record<string, unknown>,
|
|
206
|
+
options: {
|
|
207
|
+
onEvent: (event: SubscriptionEvent<T>) => void;
|
|
208
|
+
onError?: (error: Error) => void;
|
|
209
|
+
onComplete?: () => void;
|
|
210
|
+
},
|
|
211
|
+
): Unsubscribe {
|
|
212
|
+
if (!this.realtimeManager) {
|
|
213
|
+
throw new Error(
|
|
214
|
+
'Realtime not configured. Pass a `realtime` option to createClient() to enable subscriptions.',
|
|
215
|
+
);
|
|
216
|
+
}
|
|
217
|
+
return this.realtimeManager.subscribe<T>(
|
|
218
|
+
meta,
|
|
219
|
+
document,
|
|
220
|
+
variables,
|
|
221
|
+
options,
|
|
222
|
+
);
|
|
223
|
+
}
|
|
224
|
+
|
|
162
225
|
/**
|
|
163
226
|
* Set headers for requests.
|
|
164
227
|
* Only works if the adapter supports headers.
|
|
@@ -176,4 +239,32 @@ export class OrmClient {
|
|
|
176
239
|
getEndpoint(): string {
|
|
177
240
|
return this.adapter.getEndpoint?.() ?? '';
|
|
178
241
|
}
|
|
242
|
+
|
|
243
|
+
/** Get current WebSocket connection state */
|
|
244
|
+
getConnectionState(): ConnectionState {
|
|
245
|
+
return this.realtimeManager?.getConnectionState() ?? 'disconnected';
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
/** Register a listener for WebSocket connection state changes */
|
|
249
|
+
onConnectionStateChange(
|
|
250
|
+
listener: ConnectionStateListener,
|
|
251
|
+
): Unsubscribe {
|
|
252
|
+
if (!this.realtimeManager) return () => {};
|
|
253
|
+
return this.realtimeManager.onConnectionStateChange(listener);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
/** Number of active subscriptions */
|
|
257
|
+
getActiveSubscriptionCount(): number {
|
|
258
|
+
return this.realtimeManager?.getActiveSubscriptionCount() ?? 0;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
/** Whether realtime is configured */
|
|
262
|
+
get isRealtimeEnabled(): boolean {
|
|
263
|
+
return this.realtimeManager !== undefined;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/** Dispose the realtime manager (close WebSocket) */
|
|
267
|
+
dispose(): void {
|
|
268
|
+
this.realtimeManager?.dispose();
|
|
269
|
+
}
|
|
179
270
|
}
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ORM Realtime - WebSocket subscription manager
|
|
3
|
+
*
|
|
4
|
+
* This is the RUNTIME code that gets copied to generated output.
|
|
5
|
+
* Provides the WebSocket connection manager and subscription types
|
|
6
|
+
* for realtime subscriptions integrated into the ORM client.
|
|
7
|
+
*
|
|
8
|
+
* NOTE: This file is read at codegen time and written to output.
|
|
9
|
+
* Any changes here will affect all generated ORM clients.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
// Minimal type shims so this module compiles without graphql-ws
|
|
13
|
+
// installed. Consumers supply a WsClient via RealtimeConfig;
|
|
14
|
+
// the SDK itself never imports or requires graphql-ws.
|
|
15
|
+
|
|
16
|
+
interface WsGraphQLError {
|
|
17
|
+
readonly message: string;
|
|
18
|
+
readonly [key: string]: unknown;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
interface WsExecutionResult<TData = Record<string, unknown>> {
|
|
22
|
+
data?: TData | null;
|
|
23
|
+
errors?: readonly WsGraphQLError[];
|
|
24
|
+
extensions?: Record<string, unknown>;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
interface WsSink<T> {
|
|
28
|
+
next(value: T): void;
|
|
29
|
+
error(error: unknown): void;
|
|
30
|
+
complete(): void;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Minimal interface matching the graphql-ws Client.
|
|
35
|
+
* Consumers pass a concrete instance via RealtimeConfig.client.
|
|
36
|
+
*/
|
|
37
|
+
export interface WsClient {
|
|
38
|
+
subscribe<TData = Record<string, unknown>>(
|
|
39
|
+
payload: { query: string; variables?: Record<string, unknown> },
|
|
40
|
+
sink: WsSink<WsExecutionResult<TData>>,
|
|
41
|
+
): () => void;
|
|
42
|
+
dispose(): void;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// ============================================================================
|
|
46
|
+
// Types
|
|
47
|
+
// ============================================================================
|
|
48
|
+
|
|
49
|
+
/** The DML operation that triggered the subscription event */
|
|
50
|
+
export type SubscriptionOperation = 'INSERT' | 'UPDATE' | 'DELETE';
|
|
51
|
+
|
|
52
|
+
/** Connection state of the WebSocket */
|
|
53
|
+
export type ConnectionState =
|
|
54
|
+
| 'disconnected'
|
|
55
|
+
| 'connecting'
|
|
56
|
+
| 'connected'
|
|
57
|
+
| 'reconnecting';
|
|
58
|
+
|
|
59
|
+
/** Listener for connection state changes */
|
|
60
|
+
export type ConnectionStateListener = (state: ConnectionState) => void;
|
|
61
|
+
|
|
62
|
+
/** Function returned by subscribe() to cancel the subscription */
|
|
63
|
+
export type Unsubscribe = () => void;
|
|
64
|
+
|
|
65
|
+
/**
|
|
66
|
+
* A realtime subscription event delivered to the client.
|
|
67
|
+
*
|
|
68
|
+
* @typeParam T - The row type of the subscribed table
|
|
69
|
+
*/
|
|
70
|
+
export interface SubscriptionEvent<T> {
|
|
71
|
+
/** The DML operation that triggered this event */
|
|
72
|
+
operation: SubscriptionOperation;
|
|
73
|
+
/** The current row data (null for DELETE if row is no longer visible) */
|
|
74
|
+
data: T | null;
|
|
75
|
+
/** Previous field values (populated on UPDATE when available) */
|
|
76
|
+
previousValues?: Partial<T>;
|
|
77
|
+
/** Server-side timestamp of when the change occurred */
|
|
78
|
+
timestamp: string;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Options for creating a subscription.
|
|
83
|
+
*
|
|
84
|
+
* @typeParam T - The row type of the subscribed table
|
|
85
|
+
* @typeParam TFilter - The filter type for the table
|
|
86
|
+
*/
|
|
87
|
+
export interface SubscribeOptions<
|
|
88
|
+
T,
|
|
89
|
+
TFilter = Record<string, unknown>,
|
|
90
|
+
> {
|
|
91
|
+
/** Server-side filter to limit which events are delivered */
|
|
92
|
+
filter?: TFilter;
|
|
93
|
+
/** Called when a subscription event is received */
|
|
94
|
+
onEvent: (event: SubscriptionEvent<T>) => void;
|
|
95
|
+
/** Called when the subscription encounters an error */
|
|
96
|
+
onError?: (error: Error) => void;
|
|
97
|
+
/** Called when the subscription completes (server-initiated close) */
|
|
98
|
+
onComplete?: () => void;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/**
|
|
102
|
+
* Metadata about a subscription field, used internally to map
|
|
103
|
+
* table names to GraphQL subscription field names and types.
|
|
104
|
+
*/
|
|
105
|
+
export interface SubscriptionFieldMeta {
|
|
106
|
+
/** The GraphQL subscription field name (e.g., 'onContactChanged') */
|
|
107
|
+
fieldName: string;
|
|
108
|
+
/** The table name in the source schema (e.g., 'contact') */
|
|
109
|
+
tableName: string;
|
|
110
|
+
/** The data field name inside the subscription payload (e.g., 'contact') */
|
|
111
|
+
dataFieldName: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
/**
|
|
115
|
+
* Configuration for the realtime (WebSocket) connection.
|
|
116
|
+
* Pass this as the `realtime` option in OrmClientConfig.
|
|
117
|
+
*
|
|
118
|
+
* @example
|
|
119
|
+
* ```ts
|
|
120
|
+
* import { createClient } from 'graphql-ws';
|
|
121
|
+
*
|
|
122
|
+
* const client = createOrmClient({
|
|
123
|
+
* endpoint: 'https://api.example.com/graphql',
|
|
124
|
+
* realtime: {
|
|
125
|
+
* client: createClient({ url: 'wss://api.example.com/graphql' }),
|
|
126
|
+
* },
|
|
127
|
+
* });
|
|
128
|
+
* ```
|
|
129
|
+
*/
|
|
130
|
+
export interface RealtimeConfig {
|
|
131
|
+
/**
|
|
132
|
+
* A graphql-ws Client instance (or any object satisfying WsClient).
|
|
133
|
+
* The consumer creates this themselves, giving full control over
|
|
134
|
+
* connection options, auth, and transport.
|
|
135
|
+
*
|
|
136
|
+
* @example
|
|
137
|
+
* ```ts
|
|
138
|
+
* import { createClient } from 'graphql-ws';
|
|
139
|
+
* const wsClient = createClient({ url: 'wss://...' });
|
|
140
|
+
* ```
|
|
141
|
+
*/
|
|
142
|
+
client: WsClient;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// ============================================================================
|
|
146
|
+
// RealtimeManager
|
|
147
|
+
// ============================================================================
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Manages a graphql-ws WebSocket client and multiplexes
|
|
151
|
+
* subscriptions over it. Created by OrmClient when `realtime`
|
|
152
|
+
* config is provided.
|
|
153
|
+
*/
|
|
154
|
+
export class RealtimeManager {
|
|
155
|
+
private wsClient: WsClient;
|
|
156
|
+
private connectionState: ConnectionState = 'disconnected';
|
|
157
|
+
private stateListeners: Set<ConnectionStateListener> = new Set();
|
|
158
|
+
private activeSubscriptions = 0;
|
|
159
|
+
|
|
160
|
+
constructor(config: RealtimeConfig) {
|
|
161
|
+
this.wsClient = config.client;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
/**
|
|
165
|
+
* Subscribe to a GraphQL subscription operation.
|
|
166
|
+
* Models call this with typed metadata and documents.
|
|
167
|
+
*/
|
|
168
|
+
subscribe<T>(
|
|
169
|
+
meta: SubscriptionFieldMeta,
|
|
170
|
+
document: string,
|
|
171
|
+
variables: Record<string, unknown>,
|
|
172
|
+
options: {
|
|
173
|
+
onEvent: (event: SubscriptionEvent<T>) => void;
|
|
174
|
+
onError?: (error: Error) => void;
|
|
175
|
+
onComplete?: () => void;
|
|
176
|
+
},
|
|
177
|
+
): Unsubscribe {
|
|
178
|
+
this.activeSubscriptions++;
|
|
179
|
+
let disposed = false;
|
|
180
|
+
|
|
181
|
+
const cleanup = this.wsClient.subscribe<Record<string, unknown>>(
|
|
182
|
+
{ query: document, variables },
|
|
183
|
+
{
|
|
184
|
+
next: (result) => {
|
|
185
|
+
if (disposed) return;
|
|
186
|
+
if (result.errors) {
|
|
187
|
+
options.onError?.(
|
|
188
|
+
new Error(
|
|
189
|
+
result.errors.map((e) => e.message).join('; '),
|
|
190
|
+
),
|
|
191
|
+
);
|
|
192
|
+
return;
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const payload = result.data?.[meta.fieldName] as
|
|
196
|
+
| { event?: string; [key: string]: unknown }
|
|
197
|
+
| undefined;
|
|
198
|
+
|
|
199
|
+
if (!payload) return;
|
|
200
|
+
|
|
201
|
+
const event: SubscriptionEvent<T> = {
|
|
202
|
+
operation:
|
|
203
|
+
(payload.event as SubscriptionOperation) ?? 'UPDATE',
|
|
204
|
+
data: (payload[meta.dataFieldName] as T) ?? null,
|
|
205
|
+
previousValues: payload.previousValues as
|
|
206
|
+
| Partial<T>
|
|
207
|
+
| undefined,
|
|
208
|
+
timestamp:
|
|
209
|
+
(payload.timestamp as string) ?? new Date().toISOString(),
|
|
210
|
+
};
|
|
211
|
+
options.onEvent(event);
|
|
212
|
+
},
|
|
213
|
+
error: (err) => {
|
|
214
|
+
if (disposed) return;
|
|
215
|
+
options.onError?.(
|
|
216
|
+
err instanceof Error ? err : new Error(String(err)),
|
|
217
|
+
);
|
|
218
|
+
},
|
|
219
|
+
complete: () => {
|
|
220
|
+
if (disposed) return;
|
|
221
|
+
options.onComplete?.();
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
return () => {
|
|
227
|
+
if (disposed) return;
|
|
228
|
+
disposed = true;
|
|
229
|
+
this.activeSubscriptions--;
|
|
230
|
+
cleanup();
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
/** Register a listener for connection state changes */
|
|
235
|
+
onConnectionStateChange(listener: ConnectionStateListener): Unsubscribe {
|
|
236
|
+
this.stateListeners.add(listener);
|
|
237
|
+
return () => {
|
|
238
|
+
this.stateListeners.delete(listener);
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/** Get current connection state */
|
|
243
|
+
getConnectionState(): ConnectionState {
|
|
244
|
+
return this.connectionState;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
/** Number of active subscriptions */
|
|
248
|
+
getActiveSubscriptionCount(): number {
|
|
249
|
+
return this.activeSubscriptions;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
/** Dispose the manager and close the WebSocket connection */
|
|
253
|
+
dispose(): void {
|
|
254
|
+
this.wsClient.dispose();
|
|
255
|
+
this.stateListeners.clear();
|
|
256
|
+
this.activeSubscriptions = 0;
|
|
257
|
+
this.setConnectionState('disconnected');
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private setConnectionState(state: ConnectionState): void {
|
|
261
|
+
if (this.connectionState === state) return;
|
|
262
|
+
this.connectionState = state;
|
|
263
|
+
for (const listener of this.stateListeners) {
|
|
264
|
+
listener(state);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
package/core/codegen/utils.d.ts
CHANGED
|
@@ -65,6 +65,21 @@ export declare function getUpdateMutationFileName(table: Table): string;
|
|
|
65
65
|
* Generate file name for delete mutation hook
|
|
66
66
|
*/
|
|
67
67
|
export declare function getDeleteMutationFileName(table: Table): string;
|
|
68
|
+
/**
|
|
69
|
+
* Generate hook function name for subscription
|
|
70
|
+
* e.g., "useContactSubscription"
|
|
71
|
+
*/
|
|
72
|
+
export declare function getSubscriptionHookName(table: Table): string;
|
|
73
|
+
/**
|
|
74
|
+
* Generate file name for subscription hook
|
|
75
|
+
* e.g., "useContactSubscription.ts"
|
|
76
|
+
*/
|
|
77
|
+
export declare function getSubscriptionFileName(table: Table): string;
|
|
78
|
+
/**
|
|
79
|
+
* Generate the GraphQL subscription field name
|
|
80
|
+
* e.g., "onContactChanged"
|
|
81
|
+
*/
|
|
82
|
+
export declare function getSubscriptionFieldName(table: Table): string;
|
|
68
83
|
/**
|
|
69
84
|
* Get the GraphQL query name for fetching all rows
|
|
70
85
|
* Uses inflection from introspection, falls back to convention
|
package/core/codegen/utils.js
CHANGED
|
@@ -12,6 +12,9 @@ exports.getSingleQueryFileName = getSingleQueryFileName;
|
|
|
12
12
|
exports.getCreateMutationFileName = getCreateMutationFileName;
|
|
13
13
|
exports.getUpdateMutationFileName = getUpdateMutationFileName;
|
|
14
14
|
exports.getDeleteMutationFileName = getDeleteMutationFileName;
|
|
15
|
+
exports.getSubscriptionHookName = getSubscriptionHookName;
|
|
16
|
+
exports.getSubscriptionFileName = getSubscriptionFileName;
|
|
17
|
+
exports.getSubscriptionFieldName = getSubscriptionFieldName;
|
|
15
18
|
exports.getAllRowsQueryName = getAllRowsQueryName;
|
|
16
19
|
exports.getSingleRowQueryName = getSingleRowQueryName;
|
|
17
20
|
exports.getCreateMutationName = getCreateMutationName;
|
|
@@ -138,6 +141,29 @@ function getUpdateMutationFileName(table) {
|
|
|
138
141
|
function getDeleteMutationFileName(table) {
|
|
139
142
|
return `${getDeleteMutationHookName(table)}.ts`;
|
|
140
143
|
}
|
|
144
|
+
/**
|
|
145
|
+
* Generate hook function name for subscription
|
|
146
|
+
* e.g., "useContactSubscription"
|
|
147
|
+
*/
|
|
148
|
+
function getSubscriptionHookName(table) {
|
|
149
|
+
const { singularName } = getTableNames(table);
|
|
150
|
+
return `use${(0, inflekt_1.ucFirst)(singularName)}Subscription`;
|
|
151
|
+
}
|
|
152
|
+
/**
|
|
153
|
+
* Generate file name for subscription hook
|
|
154
|
+
* e.g., "useContactSubscription.ts"
|
|
155
|
+
*/
|
|
156
|
+
function getSubscriptionFileName(table) {
|
|
157
|
+
return `${getSubscriptionHookName(table)}.ts`;
|
|
158
|
+
}
|
|
159
|
+
/**
|
|
160
|
+
* Generate the GraphQL subscription field name
|
|
161
|
+
* e.g., "onContactChanged"
|
|
162
|
+
*/
|
|
163
|
+
function getSubscriptionFieldName(table) {
|
|
164
|
+
const { singularName } = getTableNames(table);
|
|
165
|
+
return `on${(0, inflekt_1.ucFirst)(singularName)}Changed`;
|
|
166
|
+
}
|
|
141
167
|
// ============================================================================
|
|
142
168
|
// GraphQL operation names
|
|
143
169
|
// ============================================================================
|
|
@@ -15,6 +15,8 @@ export declare function generateMutationsBarrel(tables: Table[]): string;
|
|
|
15
15
|
*/
|
|
16
16
|
export interface MainBarrelOptions {
|
|
17
17
|
hasMutations?: boolean;
|
|
18
|
+
/** Whether subscriptions/ directory was generated */
|
|
19
|
+
hasSubscriptions?: boolean;
|
|
18
20
|
/** Whether query-keys.ts was generated */
|
|
19
21
|
hasQueryKeys?: boolean;
|
|
20
22
|
/** Whether mutation-keys.ts was generated */
|
|
@@ -44,6 +46,10 @@ export declare function generateRootBarrel(options?: RootBarrelOptions): string;
|
|
|
44
46
|
* export * as public_ from './public';
|
|
45
47
|
*/
|
|
46
48
|
export declare function generateMultiTargetBarrel(targetNames: string[]): string;
|
|
49
|
+
/**
|
|
50
|
+
* Generate the subscriptions/index.ts barrel file
|
|
51
|
+
*/
|
|
52
|
+
export declare function generateSubscriptionsBarrel(tables: Table[]): string;
|
|
47
53
|
/**
|
|
48
54
|
* Generate queries barrel including custom query operations
|
|
49
55
|
*/
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
import * as t from '@babel/types';
|
|
7
7
|
import { addJSDocComment, generateCode } from './babel-ast';
|
|
8
8
|
import { getOperationHookName } from './type-resolver';
|
|
9
|
-
import { getCreateMutationHookName, getDeleteMutationHookName, getListQueryHookName, getSingleQueryHookName, getUpdateMutationHookName, hasValidPrimaryKey, } from './utils';
|
|
9
|
+
import { getCreateMutationHookName, getDeleteMutationHookName, getListQueryHookName, getSingleQueryHookName, getSubscriptionHookName, getUpdateMutationHookName, hasValidPrimaryKey, } from './utils';
|
|
10
10
|
/**
|
|
11
11
|
* Helper to create export * from './module' statement
|
|
12
12
|
*/
|
|
@@ -69,7 +69,7 @@ export function generateMutationsBarrel(tables) {
|
|
|
69
69
|
}
|
|
70
70
|
export function generateMainBarrel(tables, options = {}) {
|
|
71
71
|
const opts = options;
|
|
72
|
-
const { hasMutations = true, hasQueryKeys = false, hasMutationKeys = false, hasInvalidation = false, } = opts;
|
|
72
|
+
const { hasMutations = true, hasSubscriptions = false, hasQueryKeys = false, hasMutationKeys = false, hasInvalidation = false, } = opts;
|
|
73
73
|
const tableNames = tables.map((tbl) => tbl.name).join(', ');
|
|
74
74
|
const statements = [];
|
|
75
75
|
// Client configuration (ORM wrapper with configure/getClient)
|
|
@@ -92,6 +92,10 @@ export function generateMainBarrel(tables, options = {}) {
|
|
|
92
92
|
if (hasMutations) {
|
|
93
93
|
statements.push(exportAllFrom('./mutations'));
|
|
94
94
|
}
|
|
95
|
+
// Subscription hooks
|
|
96
|
+
if (hasSubscriptions) {
|
|
97
|
+
statements.push(exportAllFrom('./subscriptions'));
|
|
98
|
+
}
|
|
95
99
|
// Add file header as leading comment on first statement
|
|
96
100
|
if (statements.length > 0) {
|
|
97
101
|
addJSDocComment(statements[0], [
|
|
@@ -201,6 +205,26 @@ export function generateMultiTargetBarrel(targetNames) {
|
|
|
201
205
|
}
|
|
202
206
|
return generateCode(statements);
|
|
203
207
|
}
|
|
208
|
+
/**
|
|
209
|
+
* Generate the subscriptions/index.ts barrel file
|
|
210
|
+
*/
|
|
211
|
+
export function generateSubscriptionsBarrel(tables) {
|
|
212
|
+
const statements = [];
|
|
213
|
+
for (const table of tables) {
|
|
214
|
+
const hookName = getSubscriptionHookName(table);
|
|
215
|
+
statements.push(exportAllFrom(`./${hookName}`));
|
|
216
|
+
}
|
|
217
|
+
// Connection state hook
|
|
218
|
+
statements.push(exportAllFrom('./useConnectionState'));
|
|
219
|
+
if (statements.length > 0) {
|
|
220
|
+
addJSDocComment(statements[0], [
|
|
221
|
+
'Subscription hooks barrel export',
|
|
222
|
+
'@generated by @constructive-io/graphql-codegen',
|
|
223
|
+
'DO NOT EDIT - changes will be overwritten',
|
|
224
|
+
]);
|
|
225
|
+
}
|
|
226
|
+
return generateCode(statements);
|
|
227
|
+
}
|
|
204
228
|
// ============================================================================
|
|
205
229
|
// Custom operation barrels (includes both table and custom hooks)
|
|
206
230
|
// ============================================================================
|
|
@@ -36,6 +36,7 @@ export interface GenerateResult {
|
|
|
36
36
|
tables: number;
|
|
37
37
|
queryHooks: number;
|
|
38
38
|
mutationHooks: number;
|
|
39
|
+
subscriptionHooks: number;
|
|
39
40
|
customQueryHooks: number;
|
|
40
41
|
customMutationHooks: number;
|
|
41
42
|
totalFiles: number;
|
|
@@ -71,7 +72,7 @@ export declare function generateAllFiles(tables: Table[], config: GraphQLSDKConf
|
|
|
71
72
|
* (they're expected to exist in the shared types directory).
|
|
72
73
|
*/
|
|
73
74
|
export declare function generate(options: GenerateOptions): GenerateResult;
|
|
74
|
-
export { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, } from './barrel';
|
|
75
|
+
export { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, generateSubscriptionsBarrel, } from './barrel';
|
|
75
76
|
export { generateClientFile } from './client';
|
|
76
77
|
export { generateAllCustomMutationHooks, generateCustomMutationHook, } from './custom-mutations';
|
|
77
78
|
export { generateAllCustomQueryHooks, generateCustomQueryHook, } from './custom-queries';
|
|
@@ -80,3 +81,4 @@ export { generateMutationKeysFile } from './mutation-keys';
|
|
|
80
81
|
export { generateAllMutationHooks, generateCreateMutationHook, generateDeleteMutationHook, generateUpdateMutationHook, } from './mutations';
|
|
81
82
|
export { generateAllQueryHooks, generateListQueryHook, generateSingleQueryHook, } from './queries';
|
|
82
83
|
export { generateQueryKeysFile } from './query-keys';
|
|
84
|
+
export { generateAllSubscriptionHooks, generateConnectionStateHook, generateSubscriptionHook, } from './subscriptions';
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { DEFAULT_QUERY_KEY_CONFIG } from '../../types/config';
|
|
2
|
-
import { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, } from './barrel';
|
|
2
|
+
import { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, generateSubscriptionsBarrel, } from './barrel';
|
|
3
3
|
import { generateClientFile } from './client';
|
|
4
4
|
import { generateAllCustomMutationHooks } from './custom-mutations';
|
|
5
5
|
import { generateAllCustomQueryHooks } from './custom-queries';
|
|
@@ -8,6 +8,7 @@ import { generateMutationKeysFile } from './mutation-keys';
|
|
|
8
8
|
import { generateAllMutationHooks } from './mutations';
|
|
9
9
|
import { generateAllQueryHooks } from './queries';
|
|
10
10
|
import { generateQueryKeysFile } from './query-keys';
|
|
11
|
+
import { generateAllSubscriptionHooks, generateConnectionStateHook, } from './subscriptions';
|
|
11
12
|
import { generateSelectionFile } from './selection';
|
|
12
13
|
import { getTableNames } from './utils';
|
|
13
14
|
// ============================================================================
|
|
@@ -168,12 +169,37 @@ export function generate(options) {
|
|
|
168
169
|
: generateMutationsBarrel(tables),
|
|
169
170
|
});
|
|
170
171
|
}
|
|
172
|
+
// 8b. Generate subscription hooks (subscriptions/*.ts)
|
|
173
|
+
// Only generate for tables with the @realtime smart tag
|
|
174
|
+
const realtimeTables = tables.filter((t) => t.smartTags?.['@realtime'] !== undefined);
|
|
175
|
+
const subscriptionHooks = generateAllSubscriptionHooks(realtimeTables);
|
|
176
|
+
for (const hook of subscriptionHooks) {
|
|
177
|
+
files.push({
|
|
178
|
+
path: `subscriptions/${hook.fileName}`,
|
|
179
|
+
content: hook.content,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
// 8c. Generate connection state hook + barrel only if any table has @realtime
|
|
183
|
+
const hasSubscriptions = subscriptionHooks.length > 0;
|
|
184
|
+
if (hasSubscriptions) {
|
|
185
|
+
const connectionStateHook = generateConnectionStateHook();
|
|
186
|
+
files.push({
|
|
187
|
+
path: `subscriptions/${connectionStateHook.fileName}`,
|
|
188
|
+
content: connectionStateHook.content,
|
|
189
|
+
});
|
|
190
|
+
// 8d. Generate subscriptions/index.ts barrel
|
|
191
|
+
files.push({
|
|
192
|
+
path: 'subscriptions/index.ts',
|
|
193
|
+
content: generateSubscriptionsBarrel(realtimeTables),
|
|
194
|
+
});
|
|
195
|
+
}
|
|
171
196
|
// 9. Generate main index.ts barrel
|
|
172
197
|
// No longer includes types.ts or schema-types.ts - hooks import from ORM directly
|
|
173
198
|
files.push({
|
|
174
199
|
path: 'index.ts',
|
|
175
200
|
content: generateMainBarrel(tables, {
|
|
176
201
|
hasMutations,
|
|
202
|
+
hasSubscriptions,
|
|
177
203
|
hasQueryKeys,
|
|
178
204
|
hasMutationKeys,
|
|
179
205
|
hasInvalidation,
|
|
@@ -185,6 +211,7 @@ export function generate(options) {
|
|
|
185
211
|
tables: tables.length,
|
|
186
212
|
queryHooks: queryHooks.length,
|
|
187
213
|
mutationHooks: mutationHooks.length,
|
|
214
|
+
subscriptionHooks: subscriptionHooks.length,
|
|
188
215
|
customQueryHooks: customQueryHooks.length,
|
|
189
216
|
customMutationHooks: customMutationHooks.length,
|
|
190
217
|
totalFiles: files.length,
|
|
@@ -194,7 +221,7 @@ export function generate(options) {
|
|
|
194
221
|
// ============================================================================
|
|
195
222
|
// Re-exports for convenience
|
|
196
223
|
// ============================================================================
|
|
197
|
-
export { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, } from './barrel';
|
|
224
|
+
export { generateCustomMutationsBarrel, generateCustomQueriesBarrel, generateMainBarrel, generateMutationsBarrel, generateQueriesBarrel, generateSubscriptionsBarrel, } from './barrel';
|
|
198
225
|
export { generateClientFile } from './client';
|
|
199
226
|
export { generateAllCustomMutationHooks, generateCustomMutationHook, } from './custom-mutations';
|
|
200
227
|
export { generateAllCustomQueryHooks, generateCustomQueryHook, } from './custom-queries';
|
|
@@ -203,3 +230,4 @@ export { generateMutationKeysFile } from './mutation-keys';
|
|
|
203
230
|
export { generateAllMutationHooks, generateCreateMutationHook, generateDeleteMutationHook, generateUpdateMutationHook, } from './mutations';
|
|
204
231
|
export { generateAllQueryHooks, generateListQueryHook, generateSingleQueryHook, } from './queries';
|
|
205
232
|
export { generateQueryKeysFile } from './query-keys';
|
|
233
|
+
export { generateAllSubscriptionHooks, generateConnectionStateHook, generateSubscriptionHook, } from './subscriptions';
|
|
@@ -10,6 +10,12 @@ export interface GeneratedClientFile {
|
|
|
10
10
|
* Reads from the templates directory for proper type checking.
|
|
11
11
|
*/
|
|
12
12
|
export declare function generateOrmClientFile(): GeneratedClientFile;
|
|
13
|
+
/**
|
|
14
|
+
* Generate the realtime.ts file (RealtimeManager + subscription types)
|
|
15
|
+
*
|
|
16
|
+
* Reads from the templates directory for proper type checking.
|
|
17
|
+
*/
|
|
18
|
+
export declare function generateRealtimeFile(): GeneratedClientFile;
|
|
13
19
|
/**
|
|
14
20
|
* Generate the query-builder.ts file (runtime query builder)
|
|
15
21
|
*
|
|
@@ -44,6 +44,17 @@ export function generateOrmClientFile() {
|
|
|
44
44
|
content: readTemplateFile('orm-client.ts', 'ORM Client - Runtime GraphQL executor'),
|
|
45
45
|
};
|
|
46
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Generate the realtime.ts file (RealtimeManager + subscription types)
|
|
49
|
+
*
|
|
50
|
+
* Reads from the templates directory for proper type checking.
|
|
51
|
+
*/
|
|
52
|
+
export function generateRealtimeFile() {
|
|
53
|
+
return {
|
|
54
|
+
fileName: 'realtime.ts',
|
|
55
|
+
content: readTemplateFile('orm-realtime.ts', 'Realtime Manager - WebSocket subscription support'),
|
|
56
|
+
};
|
|
57
|
+
}
|
|
47
58
|
/**
|
|
48
59
|
* Generate the query-builder.ts file (runtime query builder)
|
|
49
60
|
*
|