@sylphx/lens-server 2.0.1 → 2.2.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.
@@ -4,12 +4,53 @@
4
4
  * Field selection logic for query results.
5
5
  */
6
6
 
7
- import type { SelectionObject } from "./types.js";
7
+ import type { NestedSelection, SelectionObject } from "./types.js";
8
+
9
+ /**
10
+ * Check if a value is a NestedSelection with input.
11
+ */
12
+ function isNestedSelection(value: unknown): value is NestedSelection {
13
+ return (
14
+ typeof value === "object" &&
15
+ value !== null &&
16
+ ("input" in value || "select" in value) &&
17
+ !Array.isArray(value)
18
+ );
19
+ }
20
+
21
+ /**
22
+ * Extract the select object from a selection value.
23
+ * Handles: true, SelectionObject, { select: ... }, { input: ..., select: ... }
24
+ */
25
+ function extractSelect(value: unknown): SelectionObject | null {
26
+ if (value === true) return null;
27
+ if (typeof value !== "object" || value === null) return null;
28
+
29
+ const obj = value as Record<string, unknown>;
30
+
31
+ // { input?: ..., select?: ... } pattern
32
+ if ("input" in obj || ("select" in obj && typeof obj.select === "object")) {
33
+ return (obj.select as SelectionObject) ?? null;
34
+ }
35
+
36
+ // { select: ... } pattern (without input)
37
+ if ("select" in obj && typeof obj.select === "object") {
38
+ return obj.select as SelectionObject;
39
+ }
40
+
41
+ // Direct SelectionObject
42
+ return value as SelectionObject;
43
+ }
8
44
 
9
45
  /**
10
46
  * Apply field selection to data.
11
47
  * Recursively filters data to only include selected fields.
12
48
  *
49
+ * Supports:
50
+ * - `field: true` - Include field as-is
51
+ * - `field: { select: {...} }` - Nested selection
52
+ * - `field: { input: {...}, select: {...} }` - Nested with input (input passed to resolvers)
53
+ *
13
54
  * @param data - The data to filter
14
55
  * @param select - Selection object specifying which fields to include
15
56
  * @returns Filtered data with only selected fields
@@ -35,10 +76,48 @@ export function applySelection(data: unknown, select: SelectionObject): unknown
35
76
  if (value === true) {
36
77
  result[key] = obj[key];
37
78
  } else if (typeof value === "object" && value !== null) {
38
- const nestedSelect = "select" in value ? value.select : value;
39
- result[key] = applySelection(obj[key], nestedSelect as SelectionObject);
79
+ const nestedSelect = extractSelect(value);
80
+ if (nestedSelect) {
81
+ result[key] = applySelection(obj[key], nestedSelect);
82
+ } else {
83
+ // No nested select means include the whole field
84
+ result[key] = obj[key];
85
+ }
40
86
  }
41
87
  }
42
88
 
43
89
  return result;
44
90
  }
91
+
92
+ /**
93
+ * Extract nested inputs from a selection object.
94
+ * Returns a map of field paths to their input params.
95
+ * Used by resolvers to fetch nested data with the right params.
96
+ */
97
+ export function extractNestedInputs(
98
+ select: SelectionObject,
99
+ prefix = "",
100
+ ): Map<string, Record<string, unknown>> {
101
+ const inputs = new Map<string, Record<string, unknown>>();
102
+
103
+ for (const [key, value] of Object.entries(select)) {
104
+ const path = prefix ? `${prefix}.${key}` : key;
105
+
106
+ if (isNestedSelection(value) && value.input) {
107
+ inputs.set(path, value.input);
108
+ }
109
+
110
+ // Recurse into nested selections
111
+ if (typeof value === "object" && value !== null) {
112
+ const nestedSelect = extractSelect(value);
113
+ if (nestedSelect) {
114
+ const nestedInputs = extractNestedInputs(nestedSelect, path);
115
+ for (const [nestedPath, nestedInput] of nestedInputs) {
116
+ inputs.set(nestedPath, nestedInput);
117
+ }
118
+ }
119
+ }
120
+ }
121
+
122
+ return inputs;
123
+ }
@@ -9,6 +9,7 @@ import type {
9
9
  EntityDef,
10
10
  InferRouterContext,
11
11
  MutationDef,
12
+ Observable,
12
13
  OptimisticDSL,
13
14
  QueryDef,
14
15
  Resolvers,
@@ -20,9 +21,26 @@ import type { PluginManager, ServerPlugin } from "../plugin/types.js";
20
21
  // Selection Types
21
22
  // =============================================================================
22
23
 
23
- /** Selection object type for nested field selection */
24
+ /**
25
+ * Nested selection object with optional input.
26
+ * Used for relations that need their own params.
27
+ */
28
+ export interface NestedSelection {
29
+ /** Input/params for this nested query */
30
+ input?: Record<string, unknown>;
31
+ /** Field selection for this nested query */
32
+ select?: SelectionObject;
33
+ }
34
+
35
+ /**
36
+ * Selection object for field selection.
37
+ * Supports:
38
+ * - `true` - Select this field
39
+ * - `{ select: {...} }` - Nested selection only
40
+ * - `{ input: {...}, select?: {...} }` - Nested with input params
41
+ */
24
42
  export interface SelectionObject {
25
- [key: string]: boolean | SelectionObject | { select: SelectionObject };
43
+ [key: string]: boolean | SelectionObject | { select: SelectionObject } | NestedSelection;
26
44
  }
27
45
 
28
46
  // =============================================================================
@@ -164,8 +182,20 @@ export interface WebSocketLike {
164
182
  export interface LensServer {
165
183
  /** Get server metadata for transport handshake */
166
184
  getMetadata(): ServerMetadata;
167
- /** Execute operation - auto-detects query vs mutation */
168
- execute(op: LensOperation): Promise<LensResult>;
185
+
186
+ /**
187
+ * Execute operation - auto-detects query vs mutation.
188
+ *
189
+ * Always returns Observable<LensResult>:
190
+ * - One-shot: emits once, then completes
191
+ * - Streaming: emits multiple times (AsyncIterable or emit-based)
192
+ *
193
+ * Usage:
194
+ * - HTTP: `await firstValueFrom(server.execute(op))`
195
+ * - WS/SSE: `server.execute(op).subscribe(...)`
196
+ * - direct: pass through Observable directly
197
+ */
198
+ execute(op: LensOperation): Observable<LensResult>;
169
199
 
170
200
  // =========================================================================
171
201
  // Subscription Support (Optional - used by WS/SSE handlers)