@sylphx/lens-server 2.13.2 → 2.14.1
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 +141 -230
- 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 +106 -309
- package/src/server/dataloader.test.ts +279 -0
- 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,144 +640,70 @@ 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)
|
|
815
694
|
return data;
|
|
816
695
|
const nestedInputs = select ? extractNestedInputs(select) : undefined;
|
|
817
|
-
const processed = await this.resolveEntityFields(data, nestedInputs, context, "", onCleanup, createFieldEmit);
|
|
696
|
+
const processed = await this.resolveEntityFields(data, nestedInputs, context, "", onCleanup, createFieldEmit, new Set);
|
|
818
697
|
if (select) {
|
|
819
698
|
return applySelection(processed, select);
|
|
820
699
|
}
|
|
821
700
|
return processed;
|
|
822
701
|
}
|
|
823
|
-
async resolveEntityFields(data, nestedInputs, context, fieldPath = "", onCleanup, createFieldEmit) {
|
|
702
|
+
async resolveEntityFields(data, nestedInputs, context, fieldPath = "", onCleanup, createFieldEmit, visited = new Set) {
|
|
824
703
|
if (!data || !this.resolverMap)
|
|
825
704
|
return data;
|
|
826
705
|
if (Array.isArray(data)) {
|
|
827
|
-
return Promise.all(data.map((item) => this.resolveEntityFields(item, nestedInputs, context, fieldPath, onCleanup, createFieldEmit)));
|
|
706
|
+
return Promise.all(data.map((item) => this.resolveEntityFields(item, nestedInputs, context, fieldPath, onCleanup, createFieldEmit, visited)));
|
|
828
707
|
}
|
|
829
708
|
if (typeof data !== "object")
|
|
830
709
|
return data;
|
|
@@ -832,6 +711,14 @@ class LensServerImpl {
|
|
|
832
711
|
const typeName = this.getTypeName(obj);
|
|
833
712
|
if (!typeName)
|
|
834
713
|
return data;
|
|
714
|
+
const entityId = obj.id ?? obj._id ?? obj.uuid;
|
|
715
|
+
if (entityId !== undefined) {
|
|
716
|
+
const entityKey = `${typeName}:${entityId}`;
|
|
717
|
+
if (visited.has(entityKey)) {
|
|
718
|
+
return data;
|
|
719
|
+
}
|
|
720
|
+
visited.add(entityKey);
|
|
721
|
+
}
|
|
835
722
|
const resolverDef = this.resolverMap.get(typeName);
|
|
836
723
|
if (!resolverDef)
|
|
837
724
|
return data;
|
|
@@ -845,7 +732,7 @@ class LensServerImpl {
|
|
|
845
732
|
const hasArgs = Object.keys(args).length > 0;
|
|
846
733
|
const existingValue = result[field];
|
|
847
734
|
if (existingValue !== undefined) {
|
|
848
|
-
result[field] = await this.resolveEntityFields(existingValue, nestedInputs, context, currentPath, onCleanup, createFieldEmit);
|
|
735
|
+
result[field] = await this.resolveEntityFields(existingValue, nestedInputs, context, currentPath, onCleanup, createFieldEmit, visited);
|
|
849
736
|
continue;
|
|
850
737
|
}
|
|
851
738
|
const fieldMode = resolverDef.getFieldMode(field);
|
|
@@ -911,7 +798,7 @@ class LensServerImpl {
|
|
|
911
798
|
result[field] = null;
|
|
912
799
|
}
|
|
913
800
|
}
|
|
914
|
-
result[field] = await this.resolveEntityFields(result[field], nestedInputs, context, currentPath, onCleanup, createFieldEmit);
|
|
801
|
+
result[field] = await this.resolveEntityFields(result[field], nestedInputs, context, currentPath, onCleanup, createFieldEmit, visited);
|
|
915
802
|
}
|
|
916
803
|
return result;
|
|
917
804
|
}
|
|
@@ -1199,7 +1086,7 @@ function createSSEHandler(config = {}) {
|
|
|
1199
1086
|
}
|
|
1200
1087
|
|
|
1201
1088
|
// src/handlers/http.ts
|
|
1202
|
-
import { firstValueFrom } from "@sylphx/lens-core";
|
|
1089
|
+
import { firstValueFrom, isError, isSnapshot } from "@sylphx/lens-core";
|
|
1203
1090
|
function sanitizeError(error, isDevelopment) {
|
|
1204
1091
|
if (isDevelopment) {
|
|
1205
1092
|
return error.message;
|
|
@@ -1340,8 +1227,8 @@ function createHTTPHandler(server, options = {}) {
|
|
|
1340
1227
|
path: operationPath2,
|
|
1341
1228
|
input: body.input
|
|
1342
1229
|
}));
|
|
1343
|
-
if (result2
|
|
1344
|
-
return new Response(JSON.stringify({ error:
|
|
1230
|
+
if (isError(result2)) {
|
|
1231
|
+
return new Response(JSON.stringify({ error: result2.error }), {
|
|
1345
1232
|
status: 500,
|
|
1346
1233
|
headers: {
|
|
1347
1234
|
"Content-Type": "application/json",
|
|
@@ -1349,7 +1236,15 @@ function createHTTPHandler(server, options = {}) {
|
|
|
1349
1236
|
}
|
|
1350
1237
|
});
|
|
1351
1238
|
}
|
|
1352
|
-
|
|
1239
|
+
if (isSnapshot(result2)) {
|
|
1240
|
+
return new Response(JSON.stringify({ data: result2.data }), {
|
|
1241
|
+
headers: {
|
|
1242
|
+
"Content-Type": "application/json",
|
|
1243
|
+
...baseHeaders
|
|
1244
|
+
}
|
|
1245
|
+
});
|
|
1246
|
+
}
|
|
1247
|
+
return new Response(JSON.stringify(result2), {
|
|
1353
1248
|
headers: {
|
|
1354
1249
|
"Content-Type": "application/json",
|
|
1355
1250
|
...baseHeaders
|
|
@@ -1408,7 +1303,7 @@ function createHandler(server, options = {}) {
|
|
|
1408
1303
|
return result;
|
|
1409
1304
|
}
|
|
1410
1305
|
// src/handlers/framework.ts
|
|
1411
|
-
import { firstValueFrom as firstValueFrom2 } from "@sylphx/lens-core";
|
|
1306
|
+
import { firstValueFrom as firstValueFrom2, isError as isError2, isSnapshot as isSnapshot2 } from "@sylphx/lens-core";
|
|
1412
1307
|
function createServerClientProxy(server) {
|
|
1413
1308
|
function createProxy(path) {
|
|
1414
1309
|
return new Proxy(() => {}, {
|
|
@@ -1423,10 +1318,13 @@ function createServerClientProxy(server) {
|
|
|
1423
1318
|
async apply(_, __, args) {
|
|
1424
1319
|
const input = args[0];
|
|
1425
1320
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1426
|
-
if (result
|
|
1427
|
-
throw result.error;
|
|
1321
|
+
if (isError2(result)) {
|
|
1322
|
+
throw new Error(result.error);
|
|
1323
|
+
}
|
|
1324
|
+
if (isSnapshot2(result)) {
|
|
1325
|
+
return result.data;
|
|
1428
1326
|
}
|
|
1429
|
-
return
|
|
1327
|
+
return null;
|
|
1430
1328
|
}
|
|
1431
1329
|
});
|
|
1432
1330
|
}
|
|
@@ -1437,10 +1335,13 @@ async function handleWebQuery(server, path, url) {
|
|
|
1437
1335
|
const inputParam = url.searchParams.get("input");
|
|
1438
1336
|
const input = inputParam ? JSON.parse(inputParam) : undefined;
|
|
1439
1337
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1440
|
-
if (result
|
|
1441
|
-
return Response.json({ error: result.error
|
|
1338
|
+
if (isError2(result)) {
|
|
1339
|
+
return Response.json({ error: result.error }, { status: 400 });
|
|
1340
|
+
}
|
|
1341
|
+
if (isSnapshot2(result)) {
|
|
1342
|
+
return Response.json({ data: result.data });
|
|
1442
1343
|
}
|
|
1443
|
-
return Response.json(
|
|
1344
|
+
return Response.json(result);
|
|
1444
1345
|
} catch (error) {
|
|
1445
1346
|
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 });
|
|
1446
1347
|
}
|
|
@@ -1450,10 +1351,13 @@ async function handleWebMutation(server, path, request) {
|
|
|
1450
1351
|
const body = await request.json();
|
|
1451
1352
|
const input = body.input;
|
|
1452
1353
|
const result = await firstValueFrom2(server.execute({ path, input }));
|
|
1453
|
-
if (result
|
|
1454
|
-
return Response.json({ error: result.error
|
|
1354
|
+
if (isError2(result)) {
|
|
1355
|
+
return Response.json({ error: result.error }, { status: 400 });
|
|
1455
1356
|
}
|
|
1456
|
-
|
|
1357
|
+
if (isSnapshot2(result)) {
|
|
1358
|
+
return Response.json({ data: result.data });
|
|
1359
|
+
}
|
|
1360
|
+
return Response.json(result);
|
|
1457
1361
|
} catch (error) {
|
|
1458
1362
|
return Response.json({ error: error instanceof Error ? error.message : "Unknown error" }, { status: 500 });
|
|
1459
1363
|
}
|
|
@@ -1522,7 +1426,9 @@ function createFrameworkHandler(server, options = {}) {
|
|
|
1522
1426
|
}
|
|
1523
1427
|
// src/handlers/ws.ts
|
|
1524
1428
|
import {
|
|
1525
|
-
firstValueFrom as firstValueFrom3
|
|
1429
|
+
firstValueFrom as firstValueFrom3,
|
|
1430
|
+
isError as isError3,
|
|
1431
|
+
isSnapshot as isSnapshot3
|
|
1526
1432
|
} from "@sylphx/lens-core";
|
|
1527
1433
|
function createWSHandler(server, options = {}) {
|
|
1528
1434
|
const { logger = {} } = options;
|
|
@@ -1660,17 +1566,20 @@ function createWSHandler(server, options = {}) {
|
|
|
1660
1566
|
}));
|
|
1661
1567
|
return;
|
|
1662
1568
|
}
|
|
1663
|
-
let
|
|
1569
|
+
let resultData;
|
|
1664
1570
|
try {
|
|
1665
|
-
result = await firstValueFrom3(server.execute({ path: operation, input }));
|
|
1666
|
-
if (result
|
|
1571
|
+
const result = await firstValueFrom3(server.execute({ path: operation, input }));
|
|
1572
|
+
if (isError3(result)) {
|
|
1667
1573
|
conn.ws.send(JSON.stringify({
|
|
1668
1574
|
type: "error",
|
|
1669
1575
|
id,
|
|
1670
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1576
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1671
1577
|
}));
|
|
1672
1578
|
return;
|
|
1673
1579
|
}
|
|
1580
|
+
if (isSnapshot3(result)) {
|
|
1581
|
+
resultData = result.data;
|
|
1582
|
+
}
|
|
1674
1583
|
} catch (error) {
|
|
1675
1584
|
conn.ws.send(JSON.stringify({
|
|
1676
1585
|
type: "error",
|
|
@@ -1679,7 +1588,7 @@ function createWSHandler(server, options = {}) {
|
|
|
1679
1588
|
}));
|
|
1680
1589
|
return;
|
|
1681
1590
|
}
|
|
1682
|
-
const entities =
|
|
1591
|
+
const entities = resultData ? extractEntities(resultData) : [];
|
|
1683
1592
|
const existingSub = conn.subscriptions.get(id);
|
|
1684
1593
|
if (existingSub) {
|
|
1685
1594
|
for (const cleanup of existingSub.cleanups) {
|
|
@@ -1704,7 +1613,7 @@ function createWSHandler(server, options = {}) {
|
|
|
1704
1613
|
fields,
|
|
1705
1614
|
entityKeys: new Set(entities.map(({ entity, entityId }) => `${entity}:${entityId}`)),
|
|
1706
1615
|
cleanups: [],
|
|
1707
|
-
lastData:
|
|
1616
|
+
lastData: resultData
|
|
1708
1617
|
};
|
|
1709
1618
|
for (const { entity, entityId, entityData } of entities) {
|
|
1710
1619
|
const allowed = await server.subscribe({
|
|
@@ -1792,20 +1701,22 @@ function createWSHandler(server, options = {}) {
|
|
|
1792
1701
|
path: message.operation,
|
|
1793
1702
|
input: message.input
|
|
1794
1703
|
}));
|
|
1795
|
-
if (result
|
|
1704
|
+
if (isError3(result)) {
|
|
1796
1705
|
conn.ws.send(JSON.stringify({
|
|
1797
1706
|
type: "error",
|
|
1798
1707
|
id: message.id,
|
|
1799
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1708
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1800
1709
|
}));
|
|
1801
1710
|
return;
|
|
1802
1711
|
}
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1806
|
-
|
|
1807
|
-
|
|
1808
|
-
|
|
1712
|
+
if (isSnapshot3(result)) {
|
|
1713
|
+
const selected = message.fields ? applySelection2(result.data, message.fields) : result.data;
|
|
1714
|
+
conn.ws.send(JSON.stringify({
|
|
1715
|
+
type: "result",
|
|
1716
|
+
id: message.id,
|
|
1717
|
+
data: selected
|
|
1718
|
+
}));
|
|
1719
|
+
}
|
|
1809
1720
|
} catch (error) {
|
|
1810
1721
|
conn.ws.send(JSON.stringify({
|
|
1811
1722
|
type: "error",
|
|
@@ -1820,25 +1731,25 @@ function createWSHandler(server, options = {}) {
|
|
|
1820
1731
|
path: message.operation,
|
|
1821
1732
|
input: message.input
|
|
1822
1733
|
}));
|
|
1823
|
-
if (result
|
|
1734
|
+
if (isError3(result)) {
|
|
1824
1735
|
conn.ws.send(JSON.stringify({
|
|
1825
1736
|
type: "error",
|
|
1826
1737
|
id: message.id,
|
|
1827
|
-
error: { code: "EXECUTION_ERROR", message: result.error
|
|
1738
|
+
error: { code: "EXECUTION_ERROR", message: result.error }
|
|
1828
1739
|
}));
|
|
1829
1740
|
return;
|
|
1830
1741
|
}
|
|
1831
|
-
if (result
|
|
1742
|
+
if (isSnapshot3(result)) {
|
|
1832
1743
|
const entities = extractEntities(result.data);
|
|
1833
1744
|
for (const { entity, entityId, entityData } of entities) {
|
|
1834
1745
|
await server.broadcast(entity, entityId, entityData);
|
|
1835
1746
|
}
|
|
1747
|
+
conn.ws.send(JSON.stringify({
|
|
1748
|
+
type: "result",
|
|
1749
|
+
id: message.id,
|
|
1750
|
+
data: result.data
|
|
1751
|
+
}));
|
|
1836
1752
|
}
|
|
1837
|
-
conn.ws.send(JSON.stringify({
|
|
1838
|
-
type: "result",
|
|
1839
|
-
id: message.id,
|
|
1840
|
-
data: result.data
|
|
1841
|
-
}));
|
|
1842
1753
|
} catch (error) {
|
|
1843
1754
|
conn.ws.send(JSON.stringify({
|
|
1844
1755
|
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.1",
|
|
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.1"
|
|
34
34
|
},
|
|
35
35
|
"devDependencies": {
|
|
36
36
|
"typescript": "^5.9.3",
|