@sylphx/lens-server 3.0.1 → 4.0.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.
@@ -7,14 +7,15 @@
7
7
  import type {
8
8
  AnyQueryDef,
9
9
  ContextValue,
10
- EntityDef,
11
10
  InferRouterContext,
11
+ ModelDef,
12
12
  MutationDef,
13
13
  Observable,
14
14
  OptimisticDSL,
15
15
  QueryDef,
16
16
  Resolvers,
17
17
  RouterDef,
18
+ SubscriptionDef,
18
19
  } from "@sylphx/lens-core";
19
20
  import type { PluginManager, ServerPlugin } from "../plugin/types.js";
20
21
 
@@ -49,7 +50,7 @@ export interface SelectionObject {
49
50
  // =============================================================================
50
51
 
51
52
  /** Entity map type */
52
- export type EntitiesMap = Record<string, EntityDef<string, any>>;
53
+ export type EntitiesMap = Record<string, ModelDef<string, any>>;
53
54
 
54
55
  /** Queries map type */
55
56
  export type QueriesMap = Record<string, AnyQueryDef<unknown, unknown>>;
@@ -57,6 +58,9 @@ export type QueriesMap = Record<string, AnyQueryDef<unknown, unknown>>;
57
58
  /** Mutations map type */
58
59
  export type MutationsMap = Record<string, MutationDef<unknown, unknown>>;
59
60
 
61
+ /** Subscriptions map type */
62
+ export type SubscriptionsMap = Record<string, SubscriptionDef<unknown, unknown>>;
63
+
60
64
  // =============================================================================
61
65
  // Operation Metadata
62
66
  // =============================================================================
@@ -70,6 +74,11 @@ export interface OperationMeta {
70
74
  * Used by client for field-level subscription detection.
71
75
  */
72
76
  returnType?: string;
77
+ /**
78
+ * Indicates this is a live query (Publisher pattern with _subscriber).
79
+ * Client should use streaming transport even though type is "query".
80
+ */
81
+ live?: boolean;
73
82
  }
74
83
 
75
84
  /** Nested operations structure for handshake */
@@ -238,16 +247,24 @@ export interface LensServer {
238
247
  import type { FieldType } from "@sylphx/lens-core";
239
248
 
240
249
  export type InferInput<T> =
241
- T extends QueryDef<infer I, any> ? I : T extends MutationDef<infer I, any> ? I : never;
250
+ T extends QueryDef<infer I, any>
251
+ ? I
252
+ : T extends MutationDef<infer I, any>
253
+ ? I
254
+ : T extends SubscriptionDef<infer I, any>
255
+ ? I
256
+ : never;
242
257
 
243
258
  export type InferOutput<T> =
244
259
  T extends QueryDef<any, infer O>
245
260
  ? O
246
261
  : T extends MutationDef<any, infer O>
247
262
  ? O
248
- : T extends FieldType<infer F>
249
- ? F
250
- : never;
263
+ : T extends SubscriptionDef<any, infer O>
264
+ ? O
265
+ : T extends FieldType<infer F>
266
+ ? F
267
+ : never;
251
268
 
252
269
  export type InferApi<T> = T extends { _types: infer Types } ? Types : never;
253
270
 
@@ -42,6 +42,26 @@ function makeKey(entity: string, entityId: string): EntityKey {
42
42
  return `${entity}:${entityId}`;
43
43
  }
44
44
 
45
+ /**
46
+ * Stable JSON stringify that sorts object keys for consistent comparison.
47
+ * This ensures { a: 1, b: 2 } and { b: 2, a: 1 } produce the same string.
48
+ */
49
+ function stableStringify(value: unknown): string {
50
+ if (value === null || typeof value !== "object") {
51
+ return JSON.stringify(value);
52
+ }
53
+
54
+ if (Array.isArray(value)) {
55
+ return `[${value.map(stableStringify).join(",")}]`;
56
+ }
57
+
58
+ const sortedKeys = Object.keys(value as Record<string, unknown>).sort();
59
+ const pairs = sortedKeys.map(
60
+ (key) => `${JSON.stringify(key)}:${stableStringify((value as Record<string, unknown>)[key])}`,
61
+ );
62
+ return `{${pairs.join(",")}}`;
63
+ }
64
+
45
65
  /**
46
66
  * Compute JSON Patch operations between two states.
47
67
  */
@@ -61,8 +81,8 @@ function computePatch(
61
81
  if (!oldKeys.has(key)) {
62
82
  // New field
63
83
  patch.push({ op: "add", path: `/${key}`, value: newValue });
64
- } else if (JSON.stringify(oldValue) !== JSON.stringify(newValue)) {
65
- // Changed field
84
+ } else if (stableStringify(oldValue) !== stableStringify(newValue)) {
85
+ // Changed field - use stable stringify for consistent comparison
66
86
  patch.push({ op: "replace", path: `/${key}`, value: newValue });
67
87
  }
68
88
  }
@@ -79,9 +99,10 @@ function computePatch(
79
99
 
80
100
  /**
81
101
  * Hash entity state for change detection.
102
+ * Uses stable stringify to ensure consistent hashing regardless of key order.
82
103
  */
83
104
  function hashState(state: Record<string, unknown>): string {
84
- return JSON.stringify(state);
105
+ return stableStringify(state);
85
106
  }
86
107
 
87
108
  /**