@thoughtbot/superglue 1.0.3 → 2.0.0-alpha.10

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.
@@ -100,6 +100,9 @@ function getIn(node, path) {
100
100
  return void 0;
101
101
  }
102
102
  }
103
+ function clone(node) {
104
+ return Array.isArray(node) ? [].slice.call(node) : { ...node };
105
+ }
103
106
  function getKey(node, key) {
104
107
  if (Array.isArray(node) && Number.isNaN(Number(key))) {
105
108
  const key_parts = Array.from(key.split("="));
@@ -148,6 +151,34 @@ function normalizeKeyPath(path) {
148
151
  return path;
149
152
  }
150
153
  }
154
+ function setIn(object, path, value) {
155
+ const keypath = normalizeKeyPath(path);
156
+ const results = { 0: object };
157
+ const parents = { 0: object };
158
+ let i;
159
+ for (i = 0; i < keypath.length; i++) {
160
+ const parent = parents[i];
161
+ if (!(typeof parent === "object" && parent !== null)) {
162
+ throw new KeyPathError(
163
+ `Expected to traverse an Array or Obj, got ${JSON.stringify(parent)}`
164
+ );
165
+ }
166
+ const child = atKey(parent, keypath[i]);
167
+ parents[i + 1] = child;
168
+ }
169
+ results[keypath.length] = value;
170
+ for (i = keypath.length - 1; i >= 0; i--) {
171
+ const target = clone(parents[i]);
172
+ results[i] = target;
173
+ const key = getKey(results[i], keypath[i]);
174
+ if (Array.isArray(target)) {
175
+ target[key] = results[i + 1];
176
+ } else {
177
+ target[key] = results[i + 1];
178
+ }
179
+ }
180
+ return results[0];
181
+ }
151
182
 
152
183
  // lib/config.ts
153
184
  var config = {
@@ -155,7 +186,26 @@ var config = {
155
186
  maxPages: 20
156
187
  };
157
188
 
189
+ // lib/utils/limited_set.ts
190
+ var LimitedSet = class extends Set {
191
+ constructor(maxSize) {
192
+ super();
193
+ this.maxSize = maxSize;
194
+ }
195
+ add(value) {
196
+ if (this.size >= this.maxSize) {
197
+ const iterator = this.values();
198
+ const oldestValue = iterator.next().value;
199
+ this.delete(oldestValue);
200
+ }
201
+ super.add(value);
202
+ return this;
203
+ }
204
+ };
205
+
158
206
  // lib/utils/request.ts
207
+ var import_uuid = require("uuid");
208
+ var lastRequestIds = new LimitedSet(20);
159
209
  function isValidResponse(xhr) {
160
210
  return isValidContent(xhr) && !downloadingFile(xhr);
161
211
  }
@@ -211,6 +261,9 @@ function argsForFetch(getState, pathQuery2, {
211
261
  nextHeaders["x-requested-with"] = "XMLHttpRequest";
212
262
  nextHeaders["accept"] = "application/json";
213
263
  nextHeaders["x-superglue-request"] = "true";
264
+ const requestId = (0, import_uuid.v4)();
265
+ lastRequestIds.add(requestId);
266
+ nextHeaders["X-Superglue-Request-Id"] = requestId;
214
267
  if (method != "GET" && method != "HEAD") {
215
268
  nextHeaders["content-type"] = "application/json";
216
269
  }
@@ -250,7 +303,7 @@ function argsForFetch(getState, pathQuery2, {
250
303
  return [fetchPath.toString(), { ...options, ...rest }];
251
304
  }
252
305
  function extractJSON(rsp) {
253
- return rsp.clone().json().then((json) => {
306
+ return rsp.json().then((json) => {
254
307
  return { rsp, json };
255
308
  }).catch((e) => {
256
309
  e.response = rsp;
@@ -302,7 +355,6 @@ var handleGraft = (0, import_toolkit.createAction)(
302
355
  var superglueError = (0, import_toolkit.createAction)(
303
356
  "@@superglue/ERROR"
304
357
  );
305
- var updateFragments = (0, import_toolkit.createAction)("@@superglue/UPDATE_FRAGMENTS");
306
358
  var copyPage = (0, import_toolkit.createAction)(
307
359
  "@@superglue/COPY_PAGE"
308
360
  );
@@ -317,24 +369,285 @@ var beforeRemote = (0, import_toolkit.createAction)("@@superglue/BEFORE_REMOTE")
317
369
  var setCSRFToken = (0, import_toolkit.createAction)("@@superglue/SET_CSRF_TOKEN");
318
370
  var historyChange = (0, import_toolkit.createAction)("@@superglue/HISTORY_CHANGE");
319
371
  var setActivePage = (0, import_toolkit.createAction)("@@superglue/SET_ACTIVE_PAGE");
372
+ var handleFragmentGraft = (0, import_toolkit.createAction)(
373
+ "@@superglue/HANDLE_FRAGMENT_GRAFT",
374
+ ({
375
+ fragmentId,
376
+ response
377
+ }) => {
378
+ return {
379
+ payload: {
380
+ response,
381
+ fragmentId
382
+ }
383
+ };
384
+ }
385
+ );
386
+ var saveFragment = (0, import_toolkit.createAction)(
387
+ "@@superglue/SAVE_FRAGMENT",
388
+ ({ fragmentId, data }) => {
389
+ return {
390
+ payload: {
391
+ fragmentId,
392
+ data
393
+ }
394
+ };
395
+ }
396
+ );
397
+ var receiveResponse = (0, import_toolkit.createAction)(
398
+ "@@superglue/RECEIVE_RESPONSE",
399
+ ({ pageKey, response }) => {
400
+ pageKey = urlToPageKey(pageKey);
401
+ return {
402
+ payload: {
403
+ pageKey,
404
+ response
405
+ }
406
+ };
407
+ }
408
+ );
409
+ var appendToFragment = (0, import_toolkit.createAction)(
410
+ "@@superglue/APPEND_TO_FRAGMENT",
411
+ ({ data, fragmentId }) => {
412
+ return {
413
+ payload: {
414
+ data,
415
+ fragmentId
416
+ }
417
+ };
418
+ }
419
+ );
420
+ var prependToFragment = (0, import_toolkit.createAction)(
421
+ "@@superglue/PREPEND_TO_FRAGMENT",
422
+ ({ data, fragmentId }) => {
423
+ return {
424
+ payload: {
425
+ data,
426
+ fragmentId
427
+ }
428
+ };
429
+ }
430
+ );
431
+
432
+ // lib/utils/proxy.ts
433
+ var ORIGINAL_TARGET = Symbol("@@originalTarget");
434
+ var ARRAY_GETTER_METHODS = /* @__PURE__ */ new Set([
435
+ Symbol.iterator,
436
+ "at",
437
+ "concat",
438
+ "entries",
439
+ "every",
440
+ "filter",
441
+ "find",
442
+ "findIndex",
443
+ "flat",
444
+ "flatMap",
445
+ "forEach",
446
+ "includes",
447
+ "indexOf",
448
+ "join",
449
+ "keys",
450
+ "lastIndexOf",
451
+ "map",
452
+ "reduce",
453
+ "reduceRight",
454
+ "slice",
455
+ "some",
456
+ "toString",
457
+ "values"
458
+ ]);
459
+ var ARRAY_SETTER_METHODS = /* @__PURE__ */ new Set([
460
+ "copyWithin",
461
+ "fill",
462
+ "pop",
463
+ "push",
464
+ "reverse",
465
+ "shift",
466
+ "sort",
467
+ "splice",
468
+ "unshift"
469
+ ]);
470
+ function isArraySetter(prop) {
471
+ return ARRAY_SETTER_METHODS.has(prop);
472
+ }
473
+ function isArrayGetter(prop) {
474
+ return ARRAY_GETTER_METHODS.has(prop);
475
+ }
476
+ function convertToInt(prop) {
477
+ if (typeof prop === "symbol") return null;
478
+ const num = Number(prop);
479
+ return Number.isInteger(num) ? num : null;
480
+ }
481
+ function isFragmentReference(value) {
482
+ return !!value && typeof value === "object" && "__id" in value && typeof value.__id === "string";
483
+ }
484
+ function createArrayProxy(arrayData, fragments, dependencies, proxyCache) {
485
+ if (proxyCache && proxyCache.has(arrayData)) {
486
+ return proxyCache.get(arrayData);
487
+ }
488
+ const proxy = new Proxy(arrayData, {
489
+ get(target, prop) {
490
+ if (prop === ORIGINAL_TARGET) {
491
+ return target;
492
+ }
493
+ if (isArrayGetter(prop)) {
494
+ const method = target[prop];
495
+ if (typeof method === "function") {
496
+ return function(...args) {
497
+ return Reflect.apply(method, proxy, args);
498
+ };
499
+ }
500
+ return method;
501
+ }
502
+ if (isArraySetter(prop)) {
503
+ throw new Error(
504
+ `Cannot mutate proxy array. Use useSetFragment to update state.`
505
+ );
506
+ }
507
+ const index = convertToInt(prop);
508
+ if (index !== null && index >= 0 && index < target.length) {
509
+ const item = target[index];
510
+ if (isFragmentReference(item)) {
511
+ dependencies.add(item.__id);
512
+ const fragmentData = fragments.current[item.__id];
513
+ if (!fragmentData) {
514
+ return void 0;
515
+ }
516
+ return createProxy(fragmentData, fragments, dependencies, proxyCache);
517
+ }
518
+ if (typeof item === "object" && item !== null) {
519
+ if ("$$typeof" in item) {
520
+ return item;
521
+ } else {
522
+ return createProxy(
523
+ item,
524
+ fragments,
525
+ dependencies,
526
+ proxyCache
527
+ );
528
+ }
529
+ }
530
+ return item;
531
+ }
532
+ return Reflect.get(target, prop);
533
+ },
534
+ has(target, prop) {
535
+ if (prop === ORIGINAL_TARGET) {
536
+ return true;
537
+ }
538
+ return Reflect.has(target, prop);
539
+ },
540
+ set() {
541
+ throw new Error(
542
+ "Cannot mutate proxy array. Use useSetFragment to update state."
543
+ );
544
+ },
545
+ deleteProperty() {
546
+ throw new Error(
547
+ "Cannot delete properties on proxy array. Use useSetFragment to update state."
548
+ );
549
+ },
550
+ defineProperty() {
551
+ throw new Error(
552
+ "Cannot define properties on proxy array. Use useSetFragment to update state."
553
+ );
554
+ }
555
+ });
556
+ if (proxyCache) {
557
+ proxyCache.set(arrayData, proxy);
558
+ }
559
+ return proxy;
560
+ }
561
+ function createObjectProxy(objectData, fragments, dependencies, proxyCache) {
562
+ if (proxyCache && proxyCache.has(objectData)) {
563
+ return proxyCache.get(objectData);
564
+ }
565
+ const proxy = new Proxy(objectData, {
566
+ get(target, prop) {
567
+ if (prop === ORIGINAL_TARGET) {
568
+ return target;
569
+ }
570
+ const value = target[prop];
571
+ if (isFragmentReference(value)) {
572
+ dependencies.add(value.__id);
573
+ const fragmentData = fragments.current[value.__id];
574
+ if (!fragmentData) {
575
+ return void 0;
576
+ }
577
+ return createProxy(fragmentData, fragments, dependencies, proxyCache);
578
+ }
579
+ if (typeof value === "object" && value !== null) {
580
+ if ("$$typeof" in value) {
581
+ return value;
582
+ } else if (Array.isArray(value)) {
583
+ return createArrayProxy(value, fragments, dependencies, proxyCache);
584
+ } else {
585
+ return createObjectProxy(value, fragments, dependencies, proxyCache);
586
+ }
587
+ }
588
+ return value;
589
+ },
590
+ has(target, prop) {
591
+ if (prop === ORIGINAL_TARGET) {
592
+ return true;
593
+ }
594
+ return Reflect.has(target, prop);
595
+ },
596
+ set() {
597
+ throw new Error(
598
+ "Cannot mutate proxy object. Use useSetFragment to update state."
599
+ );
600
+ },
601
+ deleteProperty() {
602
+ throw new Error(
603
+ "Cannot delete properties on proxy object. Use useSetFragment to update state."
604
+ );
605
+ },
606
+ defineProperty() {
607
+ throw new Error(
608
+ "Cannot define properties on proxy object. Use useSetFragment to update state."
609
+ );
610
+ }
611
+ });
612
+ if (proxyCache) {
613
+ proxyCache.set(objectData, proxy);
614
+ }
615
+ return proxy;
616
+ }
617
+ function createProxy(content, fragments, dependencies, proxyCache) {
618
+ if (!content || typeof content !== "object") {
619
+ return content;
620
+ }
621
+ if ("$$typeof" in content) {
622
+ return content;
623
+ }
624
+ if (Array.isArray(content)) {
625
+ return createArrayProxy(content, fragments, dependencies, proxyCache);
626
+ }
627
+ return createObjectProxy(content, fragments, dependencies, proxyCache);
628
+ }
320
629
 
321
630
  // lib/action_creators/requests.ts
322
631
  function handleFetchErr(err, fetchArgs, dispatch) {
323
632
  dispatch(superglueError({ message: err.message }));
633
+ console.error(err);
324
634
  throw err;
325
635
  }
326
636
  function buildMeta(pageKey, page, state, rsp, fetchArgs) {
327
637
  const { assets: prevAssets } = state;
328
638
  const { assets: nextAssets } = page;
329
- return {
639
+ const meta = {
330
640
  pageKey,
331
641
  page,
332
642
  redirected: rsp.redirected,
333
643
  rsp,
334
644
  fetchArgs,
335
- componentIdentifier: page.componentIdentifier,
336
645
  needsRefresh: needsRefresh(prevAssets, nextAssets)
337
646
  };
647
+ if (page.action !== "handleStreamResponse") {
648
+ meta.componentIdentifier = page.componentIdentifier;
649
+ }
650
+ return meta;
338
651
  }
339
652
  var MismatchedComponentError = class extends Error {
340
653
  constructor(message) {
@@ -342,10 +655,11 @@ var MismatchedComponentError = class extends Error {
342
655
  this.name = "MismatchedComponentError";
343
656
  }
344
657
  };
658
+ var defaultBeforeSave = (prevPage, receivedPage) => receivedPage;
345
659
  var remote = (path, {
346
660
  pageKey: targetPageKey,
347
661
  force = false,
348
- beforeSave = (prevPage, receivedPage) => receivedPage,
662
+ beforeSave = defaultBeforeSave,
349
663
  ...rest
350
664
  } = {}) => {
351
665
  targetPageKey = targetPageKey && urlToPageKey(targetPageKey);
@@ -355,7 +669,7 @@ var remote = (path, {
355
669
  dispatch(beforeRemote({ currentPageKey, fetchArgs }));
356
670
  dispatch(beforeFetch({ fetchArgs }));
357
671
  return fetch(...fetchArgs).then(parseResponse).then(({ rsp, json }) => {
358
- const { superglue, pages = {} } = getState();
672
+ const { superglue, pages = {}, fragments } = getState();
359
673
  let pageKey;
360
674
  if (targetPageKey === void 0) {
361
675
  const isGet = fetchArgs[1].method === "GET";
@@ -364,10 +678,11 @@ var remote = (path, {
364
678
  pageKey = targetPageKey;
365
679
  }
366
680
  const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
367
- const existingId = pages[pageKey]?.componentIdentifier;
368
- const receivedId = json.componentIdentifier;
369
- if (!!existingId && existingId != receivedId && !force) {
370
- const message = `You cannot replace or update an existing page
681
+ if (json.action !== "handleStreamResponse") {
682
+ const existingId = pages[pageKey]?.componentIdentifier;
683
+ const receivedId = json.componentIdentifier;
684
+ if (!!existingId && existingId != receivedId && !force) {
685
+ const message = `You cannot replace or update an existing page
371
686
  located at pages["${currentPageKey}"] that has a componentIdentifier
372
687
  of "${existingId}" with the contents of a page response that has a
373
688
  componentIdentifier of "${receivedId}".
@@ -383,9 +698,27 @@ compatible with the page component associated with "${existingId}".
383
698
  Consider using data-sg-visit, the visit function, or redirect_back to
384
699
  the same page. Or if you're sure you want to proceed, use force: true.
385
700
  `;
386
- throw new MismatchedComponentError(message);
701
+ throw new MismatchedComponentError(message);
702
+ }
703
+ }
704
+ dispatch(
705
+ receiveResponse({
706
+ pageKey,
707
+ response: JSON.parse(JSON.stringify(json))
708
+ })
709
+ );
710
+ const existingPage = createProxy(
711
+ pages[pageKey],
712
+ { current: fragments },
713
+ /* @__PURE__ */ new Set(),
714
+ /* @__PURE__ */ new WeakMap()
715
+ );
716
+ let page = json;
717
+ if (json.action === "savePage" || json.action === "graft") {
718
+ page = JSON.parse(
719
+ JSON.stringify(beforeSave(existingPage, json))
720
+ );
387
721
  }
388
- const page = beforeSave(pages[pageKey], json);
389
722
  return dispatch(saveAndProcessPage(pageKey, page)).then(() => meta);
390
723
  }).catch((e) => handleFetchErr(e, fetchArgs, dispatch));
391
724
  };
@@ -424,14 +757,16 @@ var visit = (path, {
424
757
  );
425
758
  lastVisitController = controller;
426
759
  return fetch(...fetchArgs).then(parseResponse).then(({ rsp, json }) => {
427
- const { superglue, pages = {} } = getState();
760
+ const { superglue, pages = {}, fragments } = getState();
428
761
  const isGet = fetchArgs[1].method === "GET";
429
762
  const pageKey = calculatePageKey(rsp, isGet, currentPageKey);
430
- if (placeholderKey && hasPropsAt(path) && hasPlaceholder) {
431
- const existingId = pages[placeholderKey]?.componentIdentifier;
432
- const receivedId = json.componentIdentifier;
433
- if (!!existingId && existingId != receivedId) {
434
- const message = `You received a page response with a
763
+ const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
764
+ if (json.action !== "handleStreamResponse") {
765
+ if (placeholderKey && hasPropsAt(path) && hasPlaceholder) {
766
+ const existingId = pages[placeholderKey]?.componentIdentifier;
767
+ const receivedId = json.componentIdentifier;
768
+ if (!!existingId && existingId != receivedId) {
769
+ const message = `You received a page response with a
435
770
  componentIdentifier "${receivedId}" that is different than the
436
771
  componentIdentifier "${existingId}" located at ${placeholderKey}.
437
772
 
@@ -447,29 +782,50 @@ Check that you're rendering a page with a matching
447
782
  componentIdentifier, or consider using redirect_back_with_props_at
448
783
  to the same page.
449
784
  `;
450
- throw new MismatchedComponentError(message);
785
+ throw new MismatchedComponentError(message);
786
+ }
787
+ dispatch(copyPage({ from: placeholderKey, to: pageKey }));
451
788
  }
452
- dispatch(copyPage({ from: placeholderKey, to: pageKey }));
453
789
  }
454
- const meta = buildMeta(pageKey, json, superglue, rsp, fetchArgs);
455
790
  const visitMeta = {
456
791
  ...meta,
457
792
  navigationAction: calculateNavAction(
458
793
  meta,
459
794
  rsp,
795
+ json,
460
796
  isGet,
461
797
  pageKey,
462
798
  currentPageKey,
463
799
  revisit
464
800
  )
465
801
  };
466
- const page = beforeSave(pages[pageKey], json);
802
+ dispatch(
803
+ receiveResponse({
804
+ pageKey,
805
+ response: JSON.parse(JSON.stringify(json))
806
+ })
807
+ );
808
+ const existingPage = createProxy(
809
+ pages[pageKey],
810
+ { current: fragments },
811
+ /* @__PURE__ */ new Set(),
812
+ /* @__PURE__ */ new WeakMap()
813
+ );
814
+ let page = json;
815
+ if (json.action === "savePage" || json.action === "graft") {
816
+ page = JSON.parse(
817
+ JSON.stringify(beforeSave(existingPage, json))
818
+ );
819
+ }
467
820
  return dispatch(saveAndProcessPage(pageKey, page)).then(() => visitMeta);
468
821
  }).catch((e) => handleFetchErr(e, fetchArgs, dispatch));
469
822
  };
470
823
  };
471
- function calculateNavAction(meta, rsp, isGet, pageKey, currentPageKey, revisit) {
824
+ function calculateNavAction(meta, rsp, json, isGet, pageKey, currentPageKey, revisit) {
472
825
  let navigationAction = "push";
826
+ if (json.action === "handleStreamResponse") {
827
+ return "none";
828
+ }
473
829
  if (!rsp.redirected && !isGet) {
474
830
  navigationAction = "replace";
475
831
  }
@@ -498,6 +854,113 @@ function calculatePageKey(rsp, isGet, currentPageKey) {
498
854
  return pageKey;
499
855
  }
500
856
 
857
+ // lib/action_creators/stream.ts
858
+ var streamPrepend = (fragments, data, options = {}) => {
859
+ return (dispatch) => {
860
+ if (options.saveAs) {
861
+ const { saveAs } = options;
862
+ dispatch(
863
+ saveFragment({
864
+ fragmentId: saveAs,
865
+ data
866
+ })
867
+ );
868
+ fragments.forEach((fragmentId) => {
869
+ dispatch(
870
+ prependToFragment({
871
+ fragmentId,
872
+ data: {
873
+ __id: saveAs
874
+ }
875
+ })
876
+ );
877
+ });
878
+ } else {
879
+ fragments.forEach((fragmentId) => {
880
+ dispatch(
881
+ prependToFragment({
882
+ fragmentId,
883
+ data
884
+ })
885
+ );
886
+ });
887
+ }
888
+ };
889
+ };
890
+ var streamAppend = (fragments, data, options = {}) => {
891
+ return (dispatch) => {
892
+ if (options.saveAs) {
893
+ const { saveAs } = options;
894
+ dispatch(
895
+ saveFragment({
896
+ fragmentId: saveAs,
897
+ data
898
+ })
899
+ );
900
+ fragments.forEach((fragmentId) => {
901
+ dispatch(
902
+ appendToFragment({
903
+ fragmentId,
904
+ data: {
905
+ __id: saveAs
906
+ }
907
+ })
908
+ );
909
+ });
910
+ } else {
911
+ fragments.forEach((fragmentId) => {
912
+ dispatch(
913
+ appendToFragment({
914
+ fragmentId,
915
+ data
916
+ })
917
+ );
918
+ });
919
+ }
920
+ };
921
+ };
922
+ var streamSave = (fragment, data) => {
923
+ return (dispatch) => {
924
+ dispatch(
925
+ saveFragment({
926
+ fragmentId: fragment,
927
+ data
928
+ })
929
+ );
930
+ };
931
+ };
932
+ var handleStreamResponse = (response) => {
933
+ return (dispatch) => {
934
+ let nextResponse = response;
935
+ nextResponse.fragments.reverse().forEach((fragment) => {
936
+ const { id, path } = fragment;
937
+ const node = getIn(nextResponse, path);
938
+ nextResponse = setIn(nextResponse, path, { __id: id });
939
+ dispatch(
940
+ saveFragment({
941
+ fragmentId: id,
942
+ data: node
943
+ })
944
+ );
945
+ });
946
+ nextResponse.data.forEach((message) => {
947
+ if (message.handler === "append") {
948
+ dispatch(
949
+ streamAppend(message.fragmentIds, message.data, message.options)
950
+ );
951
+ }
952
+ if (message.handler === "prepend") {
953
+ dispatch(
954
+ streamPrepend(message.fragmentIds, message.data, message.options)
955
+ );
956
+ }
957
+ if (message.handler === "save") {
958
+ dispatch(streamSave(message.fragmentIds[0], message.data));
959
+ }
960
+ });
961
+ };
962
+ };
963
+
501
964
  // lib/action_creators/index.ts
502
965
  function fetchDeferments(pageKey, defers = []) {
503
966
  pageKey = urlToPageKey(pageKey);
@@ -531,58 +994,64 @@ function fetchDeferments(pageKey, defers = []) {
531
994
  return Promise.all(fetches);
532
995
  };
533
996
  }
997
+ function addPlaceholdersToDeferredNodes(existingPage, page) {
998
+ const { defers = [] } = existingPage;
999
+ const prevDefers = defers.map(({ path }) => {
1000
+ const node = getIn(existingPage, path);
1001
+ const copy = JSON.stringify(node);
1002
+ return [path, JSON.parse(copy)];
1003
+ });
1004
+ return prevDefers.reduce((memo, [path, node]) => {
1005
+ return setIn(page, path, node);
1006
+ }, page);
1007
+ }
534
1008
  function saveAndProcessPage(pageKey, page) {
535
1009
  return (dispatch, getState) => {
536
1010
  pageKey = urlToPageKey(pageKey);
537
- const { defers = [] } = page;
538
- if ("action" in page) {
539
- const prevPage = getState().pages[pageKey];
540
- dispatch(handleGraft({ pageKey, page }));
541
- const currentPage = getState().pages[pageKey];
542
- currentPage.fragments.forEach((fragment) => {
543
- const { type, path } = fragment;
544
- const currentFragment = getIn(currentPage, path);
545
- const prevFragment = getIn(prevPage, path);
546
- if (!prevFragment) {
547
- dispatch(
548
- updateFragments({
549
- name: type,
550
- pageKey,
551
- value: currentFragment,
552
- path
553
- })
554
- );
555
- } else if (currentFragment !== prevFragment) {
556
- dispatch(
557
- updateFragments({
558
- name: type,
559
- pageKey,
560
- value: currentFragment,
561
- previousValue: prevFragment,
562
- path
563
- })
564
- );
565
- }
566
- });
567
- } else {
568
- dispatch(saveResponse({ pageKey, page }));
569
- const currentPage = getState().pages[pageKey];
570
- currentPage.fragments.forEach((fragment) => {
571
- const { type, path } = fragment;
572
- const currentFragment = getIn(currentPage, path);
1011
+ let nextPage = page;
1012
+ const state = getState();
1013
+ if (page.action === "savePage" && state.pages[pageKey]) {
1014
+ const existingPage = createProxy(
1015
+ state.pages[pageKey],
1016
+ { current: state.fragments },
1017
+ /* @__PURE__ */ new Set(),
1018
+ /* @__PURE__ */ new WeakMap()
1019
+ );
1020
+ nextPage = JSON.parse(
1021
+ JSON.stringify(addPlaceholdersToDeferredNodes(existingPage, nextPage))
1022
+ );
1023
+ }
1024
+ page.fragments.slice().reverse().forEach((fragment) => {
1025
+ const { id, path } = fragment;
1026
+ const node = getIn(nextPage, path);
1027
+ nextPage = setIn(nextPage, path, { __id: id });
1028
+ dispatch(
1029
+ saveFragment({
1030
+ fragmentId: id,
1031
+ data: node
1032
+ })
1033
+ );
1034
+ });
1035
+ if (nextPage.action === "graft") {
1036
+ if (typeof nextPage.fragmentContext === "string") {
573
1037
  dispatch(
574
- updateFragments({
575
- name: type,
576
- pageKey,
577
- value: currentFragment,
578
- path
1038
+ handleFragmentGraft({
1039
+ fragmentId: nextPage.fragmentContext,
1040
+ response: nextPage
579
1041
  })
580
1042
  );
581
- });
1043
+ } else {
1044
+ dispatch(handleGraft({ pageKey, page: nextPage }));
1045
+ }
1046
+ } else if (nextPage.action === "handleStreamResponse") {
1047
+ dispatch(handleStreamResponse(nextPage));
1048
+ return Promise.resolve();
1049
+ } else {
1050
+ dispatch(saveResponse({ pageKey, page: nextPage }));
582
1051
  }
583
1052
  const hasFetch = typeof fetch != "undefined";
584
1053
  if (hasFetch) {
585
- return dispatch(fetchDeferments(pageKey, defers)).then(
1054
+ return dispatch(fetchDeferments(pageKey, nextPage.defers)).then(
586
1055
  () => Promise.resolve()
587
1056
  );
588
1057
  } else {