@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.
- package/dist/index.d.ts +16 -7
- package/dist/index.js +318 -114
- package/package.json +2 -2
- package/src/e2e/server.test.ts +70 -56
- package/src/handlers/framework.ts +65 -32
- package/src/handlers/http.test.ts +8 -8
- package/src/handlers/http.ts +3 -5
- package/src/handlers/ws-types.ts +1 -0
- package/src/handlers/ws.test.ts +6 -6
- package/src/handlers/ws.ts +14 -3
- package/src/index.ts +0 -2
- package/src/plugin/optimistic.ts +6 -6
- package/src/reconnect/operation-log.ts +20 -9
- package/src/server/create.test.ts +223 -316
- package/src/server/create.ts +328 -123
- package/src/server/types.ts +23 -6
- package/src/storage/memory.ts +24 -3
package/src/server/types.ts
CHANGED
|
@@ -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,
|
|
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>
|
|
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
|
|
249
|
-
?
|
|
250
|
-
:
|
|
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
|
|
package/src/storage/memory.ts
CHANGED
|
@@ -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 (
|
|
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
|
|
105
|
+
return stableStringify(state);
|
|
85
106
|
}
|
|
86
107
|
|
|
87
108
|
/**
|