@sylphx/lens-server 2.0.1 → 2.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/dist/index.d.ts CHANGED
@@ -436,11 +436,27 @@ declare class PluginManager {
436
436
  */
437
437
  declare function createPluginManager(): PluginManager;
438
438
  import { FieldType } from "@sylphx/lens-core";
439
- /** Selection object type for nested field selection */
439
+ /**
440
+ * Nested selection object with optional input.
441
+ * Used for relations that need their own params.
442
+ */
443
+ interface NestedSelection {
444
+ /** Input/params for this nested query */
445
+ input?: Record<string, unknown>;
446
+ /** Field selection for this nested query */
447
+ select?: SelectionObject;
448
+ }
449
+ /**
450
+ * Selection object for field selection.
451
+ * Supports:
452
+ * - `true` - Select this field
453
+ * - `{ select: {...} }` - Nested selection only
454
+ * - `{ input: {...}, select?: {...} }` - Nested with input params
455
+ */
440
456
  interface SelectionObject {
441
457
  [key: string]: boolean | SelectionObject | {
442
458
  select: SelectionObject;
443
- };
459
+ } | NestedSelection;
444
460
  }
445
461
  /** Entity map type */
446
462
  type EntitiesMap = Record<string, EntityDef<string, any>>;
package/dist/index.js CHANGED
@@ -215,6 +215,20 @@ class DataLoader {
215
215
  }
216
216
 
217
217
  // src/server/selection.ts
218
+ function extractSelect(value) {
219
+ if (value === true)
220
+ return null;
221
+ if (typeof value !== "object" || value === null)
222
+ return null;
223
+ const obj = value;
224
+ if ("input" in obj || "select" in obj && typeof obj.select === "object") {
225
+ return obj.select ?? null;
226
+ }
227
+ if ("select" in obj && typeof obj.select === "object") {
228
+ return obj.select;
229
+ }
230
+ return value;
231
+ }
218
232
  function applySelection(data, select) {
219
233
  if (!data)
220
234
  return data;
@@ -233,8 +247,12 @@ function applySelection(data, select) {
233
247
  if (value === true) {
234
248
  result[key] = obj[key];
235
249
  } else if (typeof value === "object" && value !== null) {
236
- const nestedSelect = "select" in value ? value.select : value;
237
- result[key] = applySelection(obj[key], nestedSelect);
250
+ const nestedSelect = extractSelect(value);
251
+ if (nestedSelect) {
252
+ result[key] = applySelection(obj[key], nestedSelect);
253
+ } else {
254
+ result[key] = obj[key];
255
+ }
238
256
  }
239
257
  }
240
258
  return result;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sylphx/lens-server",
3
- "version": "2.0.1",
3
+ "version": "2.1.0",
4
4
  "description": "Server runtime for Lens API framework",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -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
+ }
@@ -20,9 +20,26 @@ import type { PluginManager, ServerPlugin } from "../plugin/types.js";
20
20
  // Selection Types
21
21
  // =============================================================================
22
22
 
23
- /** Selection object type for nested field selection */
23
+ /**
24
+ * Nested selection object with optional input.
25
+ * Used for relations that need their own params.
26
+ */
27
+ export interface NestedSelection {
28
+ /** Input/params for this nested query */
29
+ input?: Record<string, unknown>;
30
+ /** Field selection for this nested query */
31
+ select?: SelectionObject;
32
+ }
33
+
34
+ /**
35
+ * Selection object for field selection.
36
+ * Supports:
37
+ * - `true` - Select this field
38
+ * - `{ select: {...} }` - Nested selection only
39
+ * - `{ input: {...}, select?: {...} }` - Nested with input params
40
+ */
24
41
  export interface SelectionObject {
25
- [key: string]: boolean | SelectionObject | { select: SelectionObject };
42
+ [key: string]: boolean | SelectionObject | { select: SelectionObject } | NestedSelection;
26
43
  }
27
44
 
28
45
  // =============================================================================