@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 CHANGED
@@ -536,11 +536,11 @@ interface LensOperation {
536
536
  path: string;
537
537
  input?: unknown;
538
538
  }
539
- /** Result from operation execution */
540
- interface LensResult<T = unknown> {
541
- data?: T;
542
- error?: Error;
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?.({ error: new Error(`Operation not found: ${path}`) });
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?.({ error: new Error(`Operation not found: ${path}`) });
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
- error: new Error(`Invalid input: ${JSON.stringify(result.error)}`)
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?.({ error: new Error(`Operation ${path} has no resolver`) });
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
- emitQueue.enqueue(command);
607
- processEmitQueue().catch((err) => {
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(() => currentState, (state) => {
624
- currentState = state;
625
- }, emitIfChanged, select, context, onCleanup) : undefined;
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: err instanceof Error ? err : new Error(String(err))
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
- observer.next?.({ error: error instanceof Error ? error : new Error(String(error)) });
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
- applyEmitCommand(command, state) {
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(currentFieldValue)) {
648
+ if (Array.isArray(resolvedValue)) {
757
649
  outputType = "array";
758
- } else if (currentFieldValue === null || typeof currentFieldValue === "string" || typeof currentFieldValue === "number" || typeof currentFieldValue === "boolean") {
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 fullState = getCurrentState();
764
- if (!fullState || typeof fullState !== "object")
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
- getFieldByPath(obj, path) {
787
- if (!obj || typeof obj !== "object")
788
- return;
789
- const parts = path.split(".");
790
- let current = obj;
791
- for (const part of parts) {
792
- if (!current || typeof current !== "object")
793
- return;
794
- current = current[part];
795
- }
796
- return current;
797
- }
798
- setFieldByPath(obj, path, value) {
799
- const parts = path.split(".");
800
- if (parts.length === 1) {
801
- return { ...obj, [path]: value };
802
- }
803
- const [first, ...rest] = parts;
804
- const nested = obj[first];
805
- if (nested && typeof nested === "object") {
806
- return {
807
- ...obj,
808
- [first]: this.setFieldByPath(nested, rest.join("."), value)
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.error) {
1344
- return new Response(JSON.stringify({ error: sanitize(result2.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
- return new Response(JSON.stringify({ data: result2.data }), {
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.error) {
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 result.data;
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.error) {
1441
- return Response.json({ error: result.error.message }, { status: 400 });
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({ data: result.data });
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.error) {
1454
- return Response.json({ error: result.error.message }, { status: 400 });
1354
+ if (isError2(result)) {
1355
+ return Response.json({ error: result.error }, { status: 400 });
1455
1356
  }
1456
- return Response.json({ data: result.data });
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 result;
1569
+ let resultData;
1664
1570
  try {
1665
- result = await firstValueFrom3(server.execute({ path: operation, input }));
1666
- if (result.error) {
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.message }
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 = result.data ? extractEntities(result.data) : [];
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: result.data
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.error) {
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.message }
1708
+ error: { code: "EXECUTION_ERROR", message: result.error }
1800
1709
  }));
1801
1710
  return;
1802
1711
  }
1803
- const selected = message.fields ? applySelection2(result.data, message.fields) : result.data;
1804
- conn.ws.send(JSON.stringify({
1805
- type: "result",
1806
- id: message.id,
1807
- data: selected
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.error) {
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.message }
1738
+ error: { code: "EXECUTION_ERROR", message: result.error }
1828
1739
  }));
1829
1740
  return;
1830
1741
  }
1831
- if (result.data) {
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.13.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.11.2"
33
+ "@sylphx/lens-core": "^2.12.1"
34
34
  },
35
35
  "devDependencies": {
36
36
  "typescript": "^5.9.3",