@sylphx/lens-server 2.13.2 → 2.14.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 +5 -5
- package/dist/index.js +128 -225
- package/package.json +2 -2
- package/src/e2e/server.test.ts +61 -43
- package/src/handlers/framework.ts +25 -10
- package/src/handlers/http.ts +14 -4
- package/src/handlers/ws.ts +37 -29
- package/src/server/create.test.ts +311 -173
- package/src/server/create.ts +89 -309
- package/src/server/types.ts +5 -5
package/dist/index.d.ts
CHANGED
|
@@ -536,11 +536,11 @@ interface LensOperation {
|
|
|
536
536
|
path: string;
|
|
537
537
|
input?: unknown;
|
|
538
538
|
}
|
|
539
|
-
/**
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
539
|
+
/**
|
|
540
|
+
* Result from operation execution.
|
|
541
|
+
* Uses the new Message protocol format.
|
|
542
|
+
*/
|
|
543
|
+
type LensResult<T = unknown> = import("@sylphx/lens-core").Message<T>;
|
|
544
544
|
/**
|
|
545
545
|
* Client send function type for subscription updates.
|
|
546
546
|
*/
|
package/dist/index.js
CHANGED
|
@@ -35,7 +35,6 @@ function extendContext(current, extension) {
|
|
|
35
35
|
}
|
|
36
36
|
// src/server/create.ts
|
|
37
37
|
import {
|
|
38
|
-
applyUpdate,
|
|
39
38
|
collectModelsFromOperations,
|
|
40
39
|
collectModelsFromRouter,
|
|
41
40
|
createEmit,
|
|
@@ -49,6 +48,7 @@ import {
|
|
|
49
48
|
isMutationDef,
|
|
50
49
|
isQueryDef,
|
|
51
50
|
mergeModelCollections,
|
|
51
|
+
toOps,
|
|
52
52
|
valuesEqual
|
|
53
53
|
} from "@sylphx/lens-core";
|
|
54
54
|
|
|
@@ -293,45 +293,6 @@ function extractNestedInputs(select, prefix = "") {
|
|
|
293
293
|
function isAsyncIterable(value) {
|
|
294
294
|
return value != null && typeof value === "object" && Symbol.asyncIterator in value;
|
|
295
295
|
}
|
|
296
|
-
|
|
297
|
-
class RingBuffer {
|
|
298
|
-
capacity;
|
|
299
|
-
buffer;
|
|
300
|
-
head = 0;
|
|
301
|
-
tail = 0;
|
|
302
|
-
count = 0;
|
|
303
|
-
constructor(capacity) {
|
|
304
|
-
this.capacity = capacity;
|
|
305
|
-
this.buffer = new Array(capacity).fill(null);
|
|
306
|
-
}
|
|
307
|
-
get size() {
|
|
308
|
-
return this.count;
|
|
309
|
-
}
|
|
310
|
-
get isEmpty() {
|
|
311
|
-
return this.count === 0;
|
|
312
|
-
}
|
|
313
|
-
enqueue(item) {
|
|
314
|
-
let dropped = false;
|
|
315
|
-
if (this.count >= this.capacity) {
|
|
316
|
-
this.head = (this.head + 1) % this.capacity;
|
|
317
|
-
this.count--;
|
|
318
|
-
dropped = true;
|
|
319
|
-
}
|
|
320
|
-
this.buffer[this.tail] = item;
|
|
321
|
-
this.tail = (this.tail + 1) % this.capacity;
|
|
322
|
-
this.count++;
|
|
323
|
-
return dropped;
|
|
324
|
-
}
|
|
325
|
-
dequeue() {
|
|
326
|
-
if (this.count === 0)
|
|
327
|
-
return null;
|
|
328
|
-
const item = this.buffer[this.head];
|
|
329
|
-
this.buffer[this.head] = null;
|
|
330
|
-
this.head = (this.head + 1) % this.capacity;
|
|
331
|
-
this.count--;
|
|
332
|
-
return item;
|
|
333
|
-
}
|
|
334
|
-
}
|
|
335
296
|
var noopLogger = {};
|
|
336
297
|
|
|
337
298
|
class LensServerImpl {
|
|
@@ -520,7 +481,11 @@ class LensServerImpl {
|
|
|
520
481
|
if (!isQuery && !isMutation) {
|
|
521
482
|
return {
|
|
522
483
|
subscribe: (observer) => {
|
|
523
|
-
observer.next?.({
|
|
484
|
+
observer.next?.({
|
|
485
|
+
$: "error",
|
|
486
|
+
error: `Operation not found: ${path}`,
|
|
487
|
+
code: "NOT_FOUND"
|
|
488
|
+
});
|
|
524
489
|
observer.complete?.();
|
|
525
490
|
return { unsubscribe: () => {} };
|
|
526
491
|
}
|
|
@@ -532,7 +497,6 @@ class LensServerImpl {
|
|
|
532
497
|
return {
|
|
533
498
|
subscribe: (observer) => {
|
|
534
499
|
let cancelled = false;
|
|
535
|
-
let currentState;
|
|
536
500
|
let lastEmittedResult;
|
|
537
501
|
let lastEmittedHash;
|
|
538
502
|
const cleanups = [];
|
|
@@ -545,13 +509,17 @@ class LensServerImpl {
|
|
|
545
509
|
}
|
|
546
510
|
lastEmittedResult = data;
|
|
547
511
|
lastEmittedHash = dataHash;
|
|
548
|
-
observer.next?.({ data });
|
|
512
|
+
observer.next?.({ $: "snapshot", data });
|
|
549
513
|
};
|
|
550
514
|
(async () => {
|
|
551
515
|
try {
|
|
552
516
|
const def = isQuery ? this.queries[path] : this.mutations[path];
|
|
553
517
|
if (!def) {
|
|
554
|
-
observer.next?.({
|
|
518
|
+
observer.next?.({
|
|
519
|
+
$: "error",
|
|
520
|
+
error: `Operation not found: ${path}`,
|
|
521
|
+
code: "NOT_FOUND"
|
|
522
|
+
});
|
|
555
523
|
observer.complete?.();
|
|
556
524
|
return;
|
|
557
525
|
}
|
|
@@ -566,7 +534,9 @@ class LensServerImpl {
|
|
|
566
534
|
const result = def._input.safeParse(cleanInput);
|
|
567
535
|
if (!result.success) {
|
|
568
536
|
observer.next?.({
|
|
569
|
-
|
|
537
|
+
$: "error",
|
|
538
|
+
error: `Invalid input: ${JSON.stringify(result.error)}`,
|
|
539
|
+
code: "VALIDATION_ERROR"
|
|
570
540
|
});
|
|
571
541
|
observer.complete?.();
|
|
572
542
|
return;
|
|
@@ -576,39 +546,19 @@ class LensServerImpl {
|
|
|
576
546
|
await runWithContext(this.ctx, context, async () => {
|
|
577
547
|
const resolver = def._resolve;
|
|
578
548
|
if (!resolver) {
|
|
579
|
-
observer.next?.({
|
|
549
|
+
observer.next?.({
|
|
550
|
+
$: "error",
|
|
551
|
+
error: `Operation ${path} has no resolver`,
|
|
552
|
+
code: "NO_RESOLVER"
|
|
553
|
+
});
|
|
580
554
|
observer.complete?.();
|
|
581
555
|
return;
|
|
582
556
|
}
|
|
583
|
-
let emitProcessing = false;
|
|
584
|
-
const MAX_EMIT_QUEUE_SIZE = 100;
|
|
585
|
-
const emitQueue = new RingBuffer(MAX_EMIT_QUEUE_SIZE);
|
|
586
|
-
const processEmitQueue = async () => {
|
|
587
|
-
if (emitProcessing || cancelled)
|
|
588
|
-
return;
|
|
589
|
-
emitProcessing = true;
|
|
590
|
-
let command = emitQueue.dequeue();
|
|
591
|
-
while (command !== null && !cancelled) {
|
|
592
|
-
this.clearLoaders();
|
|
593
|
-
currentState = this.applyEmitCommand(command, currentState);
|
|
594
|
-
const fieldEmitFactory = isQuery ? this.createFieldEmitFactory(() => currentState, (state) => {
|
|
595
|
-
currentState = state;
|
|
596
|
-
}, emitIfChanged, select, context, onCleanup) : undefined;
|
|
597
|
-
const processed = isQuery ? await this.processQueryResult(path, currentState, select, context, onCleanup, fieldEmitFactory) : currentState;
|
|
598
|
-
emitIfChanged(processed);
|
|
599
|
-
command = emitQueue.dequeue();
|
|
600
|
-
}
|
|
601
|
-
emitProcessing = false;
|
|
602
|
-
};
|
|
603
557
|
const emitHandler = (command) => {
|
|
604
558
|
if (cancelled)
|
|
605
559
|
return;
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
if (!cancelled) {
|
|
609
|
-
observer.next?.({ error: err instanceof Error ? err : new Error(String(err)) });
|
|
610
|
-
}
|
|
611
|
-
});
|
|
560
|
+
const ops = toOps(command);
|
|
561
|
+
observer.next?.({ $: "ops", ops });
|
|
612
562
|
};
|
|
613
563
|
const isArrayOutput = Array.isArray(def._output);
|
|
614
564
|
const emit = createEmit(emitHandler, isArrayOutput);
|
|
@@ -620,16 +570,16 @@ class LensServerImpl {
|
|
|
620
570
|
cleanups.splice(idx, 1);
|
|
621
571
|
};
|
|
622
572
|
};
|
|
623
|
-
const createFieldEmit = isQuery ? this.createFieldEmitFactory(() =>
|
|
624
|
-
|
|
625
|
-
|
|
573
|
+
const createFieldEmit = isQuery ? this.createFieldEmitFactory((command) => {
|
|
574
|
+
const ops = toOps(command);
|
|
575
|
+
observer.next?.({ $: "ops", ops });
|
|
576
|
+
}) : undefined;
|
|
626
577
|
const lensContext = { ...context, emit, onCleanup };
|
|
627
578
|
const result = resolver({ args: cleanInput, input: cleanInput, ctx: lensContext });
|
|
628
579
|
if (isAsyncIterable(result)) {
|
|
629
580
|
for await (const value of result) {
|
|
630
581
|
if (cancelled)
|
|
631
582
|
break;
|
|
632
|
-
currentState = value;
|
|
633
583
|
const processed = await this.processQueryResult(path, value, select, context, onCleanup, createFieldEmit);
|
|
634
584
|
emitIfChanged(processed);
|
|
635
585
|
}
|
|
@@ -638,7 +588,6 @@ class LensServerImpl {
|
|
|
638
588
|
}
|
|
639
589
|
} else {
|
|
640
590
|
const value = await result;
|
|
641
|
-
currentState = value;
|
|
642
591
|
const processed = isQuery ? await this.processQueryResult(path, value, select, context, onCleanup, createFieldEmit) : value;
|
|
643
592
|
emitIfChanged(processed);
|
|
644
593
|
if (isQuery && isLiveQueryDef(def) && !cancelled) {
|
|
@@ -655,8 +604,11 @@ class LensServerImpl {
|
|
|
655
604
|
}
|
|
656
605
|
} catch (err) {
|
|
657
606
|
if (!cancelled) {
|
|
607
|
+
const errMsg = err instanceof Error ? err.message : String(err);
|
|
658
608
|
observer.next?.({
|
|
659
|
-
error
|
|
609
|
+
$: "error",
|
|
610
|
+
error: errMsg,
|
|
611
|
+
code: "SUBSCRIBE_ERROR"
|
|
660
612
|
});
|
|
661
613
|
}
|
|
662
614
|
}
|
|
@@ -669,7 +621,8 @@ class LensServerImpl {
|
|
|
669
621
|
});
|
|
670
622
|
} catch (error) {
|
|
671
623
|
if (!cancelled) {
|
|
672
|
-
|
|
624
|
+
const errMsg = error instanceof Error ? error.message : String(error);
|
|
625
|
+
observer.next?.({ $: "error", error: errMsg, code: "INTERNAL_ERROR" });
|
|
673
626
|
observer.complete?.();
|
|
674
627
|
}
|
|
675
628
|
} finally {
|
|
@@ -687,128 +640,54 @@ class LensServerImpl {
|
|
|
687
640
|
}
|
|
688
641
|
};
|
|
689
642
|
}
|
|
690
|
-
|
|
691
|
-
switch (command.type) {
|
|
692
|
-
case "full":
|
|
693
|
-
if (command.replace) {
|
|
694
|
-
return command.data;
|
|
695
|
-
}
|
|
696
|
-
if (state && typeof state === "object" && typeof command.data === "object") {
|
|
697
|
-
return { ...state, ...command.data };
|
|
698
|
-
}
|
|
699
|
-
return command.data;
|
|
700
|
-
case "field": {
|
|
701
|
-
if (command.field === "") {
|
|
702
|
-
return applyUpdate(state, command.update);
|
|
703
|
-
}
|
|
704
|
-
if (state && typeof state === "object") {
|
|
705
|
-
const currentValue = state[command.field];
|
|
706
|
-
const newValue = applyUpdate(currentValue, command.update);
|
|
707
|
-
return {
|
|
708
|
-
...state,
|
|
709
|
-
[command.field]: newValue
|
|
710
|
-
};
|
|
711
|
-
}
|
|
712
|
-
return { [command.field]: applyUpdate(undefined, command.update) };
|
|
713
|
-
}
|
|
714
|
-
case "batch":
|
|
715
|
-
if (state && typeof state === "object") {
|
|
716
|
-
const result = { ...state };
|
|
717
|
-
for (const update of command.updates) {
|
|
718
|
-
const currentValue = result[update.field];
|
|
719
|
-
result[update.field] = applyUpdate(currentValue, update.update);
|
|
720
|
-
}
|
|
721
|
-
return result;
|
|
722
|
-
}
|
|
723
|
-
return state;
|
|
724
|
-
case "array": {
|
|
725
|
-
const arr = Array.isArray(state) ? [...state] : [];
|
|
726
|
-
const op = command.operation;
|
|
727
|
-
switch (op.op) {
|
|
728
|
-
case "push":
|
|
729
|
-
return [...arr, op.item];
|
|
730
|
-
case "unshift":
|
|
731
|
-
return [op.item, ...arr];
|
|
732
|
-
case "insert":
|
|
733
|
-
arr.splice(op.index, 0, op.item);
|
|
734
|
-
return arr;
|
|
735
|
-
case "remove":
|
|
736
|
-
arr.splice(op.index, 1);
|
|
737
|
-
return arr;
|
|
738
|
-
case "update":
|
|
739
|
-
arr[op.index] = op.item;
|
|
740
|
-
return arr;
|
|
741
|
-
default:
|
|
742
|
-
return arr;
|
|
743
|
-
}
|
|
744
|
-
}
|
|
745
|
-
default:
|
|
746
|
-
return state;
|
|
747
|
-
}
|
|
748
|
-
}
|
|
749
|
-
createFieldEmitFactory(getCurrentState, setCurrentState, notifyObserver, select, context, onCleanup) {
|
|
643
|
+
createFieldEmitFactory(sendUpdate) {
|
|
750
644
|
return (fieldPath, resolvedValue) => {
|
|
751
645
|
if (!fieldPath)
|
|
752
646
|
return;
|
|
753
|
-
const state = getCurrentState();
|
|
754
|
-
const currentFieldValue = resolvedValue !== undefined ? resolvedValue : state ? this.getFieldByPath(state, fieldPath) : undefined;
|
|
755
647
|
let outputType = "object";
|
|
756
|
-
if (Array.isArray(
|
|
648
|
+
if (Array.isArray(resolvedValue)) {
|
|
757
649
|
outputType = "array";
|
|
758
|
-
} else if (
|
|
650
|
+
} else if (resolvedValue === null || typeof resolvedValue === "string" || typeof resolvedValue === "number" || typeof resolvedValue === "boolean") {
|
|
759
651
|
outputType = "scalar";
|
|
760
652
|
}
|
|
761
|
-
let localFieldValue = resolvedValue;
|
|
762
653
|
const emitHandler = (command) => {
|
|
763
|
-
const
|
|
764
|
-
|
|
765
|
-
return;
|
|
766
|
-
const stateFieldValue = this.getFieldByPath(fullState, fieldPath);
|
|
767
|
-
const fieldValue = stateFieldValue !== undefined ? stateFieldValue : localFieldValue;
|
|
768
|
-
const newFieldValue = this.applyEmitCommand(command, fieldValue);
|
|
769
|
-
localFieldValue = newFieldValue;
|
|
770
|
-
const updatedState = this.setFieldByPath(fullState, fieldPath, newFieldValue);
|
|
771
|
-
setCurrentState(updatedState);
|
|
772
|
-
(async () => {
|
|
773
|
-
try {
|
|
774
|
-
const nestedInputs = select ? extractNestedInputs(select) : undefined;
|
|
775
|
-
const processed = await this.resolveEntityFields(updatedState, nestedInputs, context, "", onCleanup, this.createFieldEmitFactory(getCurrentState, setCurrentState, notifyObserver, select, context, onCleanup));
|
|
776
|
-
const result = select ? applySelection(processed, select) : processed;
|
|
777
|
-
notifyObserver(result);
|
|
778
|
-
} catch (err) {
|
|
779
|
-
console.error(`Field emit error at path "${fieldPath}":`, err);
|
|
780
|
-
}
|
|
781
|
-
})();
|
|
654
|
+
const prefixedCommand = this.prefixCommandPath(command, fieldPath);
|
|
655
|
+
sendUpdate(prefixedCommand);
|
|
782
656
|
};
|
|
783
657
|
return createEmit(emitHandler, outputType);
|
|
784
658
|
};
|
|
785
659
|
}
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
660
|
+
prefixCommandPath(command, prefix) {
|
|
661
|
+
switch (command.type) {
|
|
662
|
+
case "full":
|
|
663
|
+
return {
|
|
664
|
+
type: "field",
|
|
665
|
+
field: prefix,
|
|
666
|
+
update: { strategy: "value", data: command.data }
|
|
667
|
+
};
|
|
668
|
+
case "field":
|
|
669
|
+
return {
|
|
670
|
+
type: "field",
|
|
671
|
+
field: command.field ? `${prefix}.${command.field}` : prefix,
|
|
672
|
+
update: command.update
|
|
673
|
+
};
|
|
674
|
+
case "batch":
|
|
675
|
+
return {
|
|
676
|
+
type: "batch",
|
|
677
|
+
updates: command.updates.map((u) => ({
|
|
678
|
+
field: `${prefix}.${u.field}`,
|
|
679
|
+
update: u.update
|
|
680
|
+
}))
|
|
681
|
+
};
|
|
682
|
+
case "array":
|
|
683
|
+
return {
|
|
684
|
+
type: "array",
|
|
685
|
+
operation: command.operation,
|
|
686
|
+
field: prefix
|
|
687
|
+
};
|
|
688
|
+
default:
|
|
689
|
+
return command;
|
|
810
690
|
}
|
|
811
|
-
return obj;
|
|
812
691
|
}
|
|
813
692
|
async processQueryResult(_operationName, data, select, context, onCleanup, createFieldEmit) {
|
|
814
693
|
if (!data)
|
|
@@ -1199,7 +1078,7 @@ function createSSEHandler(config = {}) {
|
|
|
1199
1078
|
}
|
|
1200
1079
|
|
|
1201
1080
|
// src/handlers/http.ts
|
|
1202
|
-
import { firstValueFrom } from "@sylphx/lens-core";
|
|
1081
|
+
import { firstValueFrom, isError, isSnapshot } from "@sylphx/lens-core";
|
|
1203
1082
|
function sanitizeError(error, isDevelopment) {
|
|
1204
1083
|
if (isDevelopment) {
|
|
1205
1084
|
return error.message;
|
|
@@ -1340,8 +1219,8 @@ function createHTTPHandler(server, options = {}) {
|
|
|
1340
1219
|
path: operationPath2,
|
|
1341
1220
|
input: body.input
|
|
1342
1221
|
}));
|
|
1343
|
-
if (result2
|
|
1344
|
-
return new Response(JSON.stringify({ error:
|
|
1222
|
+
if (isError(result2)) {
|
|
1223
|
+
return new Response(JSON.stringify({ error: result2.error }), {
|
|
1345
1224
|
status: 500,
|
|
1346
1225
|
headers: {
|
|
1347
1226
|
"Content-Type": "application/json",
|
|
@@ -1349,7 +1228,15 @@ function createHTTPHandler(server, options = {}) {
|
|
|
1349
1228
|
}
|
|
1350
1229
|
});
|
|
1351
1230
|
}
|
|
1352
|
-
|
|
1231
|
+
if (isSnapshot(result2)) {
|
|
1232
|
+
return new Response(JSON.stringify({ data: result2.data }), {
|
|
1233
|
+
headers: {
|
|
1234
|
+
"Content-Type": "application/json",
|
|
1235
|
+
...baseHeaders
|
|
1236
|
+
}
|
|
1237
|
+
});
|
|
1238
|
+
}
|
|
1239
|
+
return new Response(JSON.stringify(result2), {
|
|
1353
1240
|
headers: {
|
|
1354
1241
|
"Content-Type": "application/json",
|
|
1355
1242
|
...baseHeaders
|
|
@@ -1408,7 +1295,7 @@ function createHandler(server, options = {}) {
|
|
|
1408
1295
|
return result;
|
|
1409
1296
|
}
|
|
1410
1297
|
// src/handlers/framework.ts
|
|
1411
|
-
import { firstValueFrom as firstValueFrom2 } from "@sylphx/lens-core";
|
|
1298
|
+
import { firstValueFrom as firstValueFrom2, isError as isError2, isSnapshot as isSnapshot2 } from "@sylphx/lens-core";
|
|
1412
1299
|
function createServerClientProxy(server) {
|
|
1413
1300
|
function createProxy(path) {
|
|
1414
1301
|
return new Proxy(() => {}, {
|
|
@@ -1423,10 +1310,13 @@ function createServerClientProxy(server) {
|
|
|
1423
1310
|
async apply(_, __, args) {
|
|
1424
1311
|
const input = args[0];
|
|
1425
1312
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1426
|
-
if (result
|
|
1427
|
-
throw result.error;
|
|
1313
|
+
if (isError2(result)) {
|
|
1314
|
+
throw new Error(result.error);
|
|
1315
|
+
}
|
|
1316
|
+
if (isSnapshot2(result)) {
|
|
1317
|
+
return result.data;
|
|
1428
1318
|
}
|
|
1429
|
-
return
|
|
1319
|
+
return null;
|
|
1430
1320
|
}
|
|
1431
1321
|
});
|
|
1432
1322
|
}
|
|
@@ -1437,10 +1327,13 @@ async function handleWebQuery(server, path, url) {
|
|
|
1437
1327
|
const inputParam = url.searchParams.get("input");
|
|
1438
1328
|
const input = inputParam ? JSON.parse(inputParam) : undefined;
|
|
1439
1329
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1440
|
-
if (result
|
|
1441
|
-
return Response.json({ error: result.error
|
|
1330
|
+
if (isError2(result)) {
|
|
1331
|
+
return Response.json({ error: result.error }, { status: 400 });
|
|
1442
1332
|
}
|
|
1443
|
-
|
|
1333
|
+
if (isSnapshot2(result)) {
|
|
1334
|
+
return Response.json({ data: result.data });
|
|
1335
|
+
}
|
|
1336
|
+
return Response.json(result);
|
|
1444
1337
|
} catch (error) {
|
|
1445
1338
|
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 });
|
|
1446
1339
|
}
|
|
@@ -1450,10 +1343,13 @@ async function handleWebMutation(server, path, request) {
|
|
|
1450
1343
|
const body = await request.json();
|
|
1451
1344
|
const input = body.input;
|
|
1452
1345
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1453
|
-
if (result
|
|
1454
|
-
return Response.json({ error: result.error
|
|
1346
|
+
if (isError2(result)) {
|
|
1347
|
+
return Response.json({ error: result.error }, { status: 400 });
|
|
1348
|
+
}
|
|
1349
|
+
if (isSnapshot2(result)) {
|
|
1350
|
+
return Response.json({ data: result.data });
|
|
1455
1351
|
}
|
|
1456
|
-
return Response.json(
|
|
1352
|
+
return Response.json(result);
|
|
1457
1353
|
} catch (error) {
|
|
1458
1354
|
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 });
|
|
1459
1355
|
}
|
|
@@ -1522,7 +1418,9 @@ function createFrameworkHandler(server, options = {}) {
|
|
|
1522
1418
|
}
|
|
1523
1419
|
// src/handlers/ws.ts
|
|
1524
1420
|
import {
|
|
1525
|
-
firstValueFrom as firstValueFrom3
|
|
1421
|
+
firstValueFrom as firstValueFrom3,
|
|
1422
|
+
isError as isError3,
|
|
1423
|
+
isSnapshot as isSnapshot3
|
|
1526
1424
|
} from "@sylphx/lens-core";
|
|
1527
1425
|
function createWSHandler(server, options = {}) {
|
|
1528
1426
|
const { logger = {} } = options;
|
|
@@ -1660,17 +1558,20 @@ function createWSHandler(server, options = {}) {
|
|
|
1660
1558
|
}));
|
|
1661
1559
|
return;
|
|
1662
1560
|
}
|
|
1663
|
-
let
|
|
1561
|
+
let resultData;
|
|
1664
1562
|
try {
|
|
1665
|
-
result = await firstValueFrom3(server.execute({ path: operation, input }));
|
|
1666
|
-
if (result
|
|
1563
|
+
const result = await firstValueFrom3(server.execute({ path: operation, input }));
|
|
1564
|
+
if (isError3(result)) {
|
|
1667
1565
|
conn.ws.send(JSON.stringify({
|
|
1668
1566
|
type: "error",
|
|
1669
1567
|
id,
|
|
1670
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1568
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1671
1569
|
}));
|
|
1672
1570
|
return;
|
|
1673
1571
|
}
|
|
1572
|
+
if (isSnapshot3(result)) {
|
|
1573
|
+
resultData = result.data;
|
|
1574
|
+
}
|
|
1674
1575
|
} catch (error) {
|
|
1675
1576
|
conn.ws.send(JSON.stringify({
|
|
1676
1577
|
type: "error",
|
|
@@ -1679,7 +1580,7 @@ function createWSHandler(server, options = {}) {
|
|
|
1679
1580
|
}));
|
|
1680
1581
|
return;
|
|
1681
1582
|
}
|
|
1682
|
-
const entities =
|
|
1583
|
+
const entities = resultData ? extractEntities(resultData) : [];
|
|
1683
1584
|
const existingSub = conn.subscriptions.get(id);
|
|
1684
1585
|
if (existingSub) {
|
|
1685
1586
|
for (const cleanup of existingSub.cleanups) {
|
|
@@ -1704,7 +1605,7 @@ function createWSHandler(server, options = {}) {
|
|
|
1704
1605
|
fields,
|
|
1705
1606
|
entityKeys: new Set(entities.map(({ entity, entityId }) => `${entity}:${entityId}`)),
|
|
1706
1607
|
cleanups: [],
|
|
1707
|
-
lastData:
|
|
1608
|
+
lastData: resultData
|
|
1708
1609
|
};
|
|
1709
1610
|
for (const { entity, entityId, entityData } of entities) {
|
|
1710
1611
|
const allowed = await server.subscribe({
|
|
@@ -1792,20 +1693,22 @@ function createWSHandler(server, options = {}) {
|
|
|
1792
1693
|
path: message.operation,
|
|
1793
1694
|
input: message.input
|
|
1794
1695
|
}));
|
|
1795
|
-
if (result
|
|
1696
|
+
if (isError3(result)) {
|
|
1796
1697
|
conn.ws.send(JSON.stringify({
|
|
1797
1698
|
type: "error",
|
|
1798
1699
|
id: message.id,
|
|
1799
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1700
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1800
1701
|
}));
|
|
1801
1702
|
return;
|
|
1802
1703
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1704
|
+
if (isSnapshot3(result)) {
|
|
1705
|
+
const selected = message.fields ? applySelection2(result.data, message.fields) : result.data;
|
|
1706
|
+
conn.ws.send(JSON.stringify({
|
|
1707
|
+
type: "result",
|
|
1708
|
+
id: message.id,
|
|
1709
|
+
data: selected
|
|
1710
|
+
}));
|
|
1711
|
+
}
|
|
1809
1712
|
} catch (error) {
|
|
1810
1713
|
conn.ws.send(JSON.stringify({
|
|
1811
1714
|
type: "error",
|
|
@@ -1820,25 +1723,25 @@ function createWSHandler(server, options = {}) {
|
|
|
1820
1723
|
path: message.operation,
|
|
1821
1724
|
input: message.input
|
|
1822
1725
|
}));
|
|
1823
|
-
if (result
|
|
1726
|
+
if (isError3(result)) {
|
|
1824
1727
|
conn.ws.send(JSON.stringify({
|
|
1825
1728
|
type: "error",
|
|
1826
1729
|
id: message.id,
|
|
1827
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1730
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1828
1731
|
}));
|
|
1829
1732
|
return;
|
|
1830
1733
|
}
|
|
1831
|
-
if (result
|
|
1734
|
+
if (isSnapshot3(result)) {
|
|
1832
1735
|
const entities = extractEntities(result.data);
|
|
1833
1736
|
for (const { entity, entityId, entityData } of entities) {
|
|
1834
1737
|
await server.broadcast(entity, entityId, entityData);
|
|
1835
1738
|
}
|
|
1739
|
+
conn.ws.send(JSON.stringify({
|
|
1740
|
+
type: "result",
|
|
1741
|
+
id: message.id,
|
|
1742
|
+
data: result.data
|
|
1743
|
+
}));
|
|
1836
1744
|
}
|
|
1837
|
-
conn.ws.send(JSON.stringify({
|
|
1838
|
-
type: "result",
|
|
1839
|
-
id: message.id,
|
|
1840
|
-
data: result.data
|
|
1841
|
-
}));
|
|
1842
1745
|
} catch (error) {
|
|
1843
1746
|
conn.ws.send(JSON.stringify({
|
|
1844
1747
|
type: "error",
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sylphx/lens-server",
|
|
3
|
-
"version": "2.
|
|
3
|
+
"version": "2.14.0",
|
|
4
4
|
"description": "Server runtime for Lens API framework",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./dist/index.js",
|
|
@@ -30,7 +30,7 @@
|
|
|
30
30
|
"author": "SylphxAI",
|
|
31
31
|
"license": "MIT",
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"@sylphx/lens-core": "^2.
|
|
33
|
+
"@sylphx/lens-core": "^2.12.0"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "^5.9.3",
|