@syncular/server-hono 0.0.6-184 → 0.0.6-188
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/routes.d.ts.map +1 -1
- package/dist/routes.js +262 -103
- package/dist/routes.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/audit-routes.test.ts +23 -17
- package/src/__tests__/create-server.test.ts +16 -12
- package/src/__tests__/pull-chunk-storage.test.ts +17 -13
- package/src/__tests__/sync-maintenance.test.ts +17 -13
- package/src/__tests__/sync-rate-limit-routing.test.ts +17 -13
- package/src/routes.ts +373 -131
package/dist/routes.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../src/routes.ts"],"names":[],"mappings":"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"}
|
package/dist/routes.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* - GET /realtime (optional WebSocket "wake up" notifications)
|
|
8
8
|
*/
|
|
9
9
|
import { captureSyncException, countSyncMetric, createSyncTimer, distributionSyncMetric, ErrorResponseSchema, logSyncEvent, ScopeValuesSchema, SyncCombinedRequestSchema, SyncCombinedResponseSchema, SyncPushRequestSchema, } from '@syncular/core';
|
|
10
|
-
import { createServerHandlerCollection, InvalidSubscriptionScopeError, maybeCompactChanges, maybePruneSync, parseJsonValue, pull, pushCommit, readSnapshotChunk, recordClientCursor, resolveEffectiveScopesForSubscriptions, scopesToSnapshotChunkScopeKey, } from '@syncular/server';
|
|
10
|
+
import { createServerHandlerCollection, InvalidSubscriptionScopeError, maybeCompactChanges, maybePruneSync, parseJsonValue, pull, pushCommit, pushCommitBatch, readSnapshotChunk, recordClientCursor, resolveEffectiveScopesForSubscriptions, scopesToSnapshotChunkScopeKey, } from '@syncular/server';
|
|
11
11
|
import { Hono } from 'hono';
|
|
12
12
|
import { describeRoute, resolver, validator as zValidator } from 'hono-openapi';
|
|
13
13
|
import { sql } from 'kysely';
|
|
@@ -488,6 +488,206 @@ export function createSyncRoutes(options) {
|
|
|
488
488
|
});
|
|
489
489
|
});
|
|
490
490
|
};
|
|
491
|
+
function notifyRealtimeForAppliedPushes(ctx, pushedCommits) {
|
|
492
|
+
if (!wsConnectionManager && !realtimeBroadcaster) {
|
|
493
|
+
return;
|
|
494
|
+
}
|
|
495
|
+
let latestCommitSeq = 0;
|
|
496
|
+
let latestActorId = ctx.auth.actorId;
|
|
497
|
+
let latestCreatedAt;
|
|
498
|
+
const scopeKeys = new Set();
|
|
499
|
+
const emittedChanges = [];
|
|
500
|
+
for (const pushed of pushedCommits) {
|
|
501
|
+
if (pushed.response.ok !== true ||
|
|
502
|
+
pushed.response.status !== 'applied' ||
|
|
503
|
+
typeof pushed.response.commitSeq !== 'number') {
|
|
504
|
+
continue;
|
|
505
|
+
}
|
|
506
|
+
latestCommitSeq = Math.max(latestCommitSeq, pushed.response.commitSeq);
|
|
507
|
+
latestActorId = pushed.commitActorId ?? latestActorId;
|
|
508
|
+
latestCreatedAt = pushed.commitCreatedAt ?? latestCreatedAt;
|
|
509
|
+
for (const scopeKey of applyPartitionToScopeKeys(ctx.partitionId, pushed.scopeKeys)) {
|
|
510
|
+
scopeKeys.add(scopeKey);
|
|
511
|
+
}
|
|
512
|
+
emittedChanges.push(...pushed.emittedChanges);
|
|
513
|
+
}
|
|
514
|
+
if (latestCommitSeq <= 0 || scopeKeys.size === 0) {
|
|
515
|
+
return;
|
|
516
|
+
}
|
|
517
|
+
const combinedScopeKeys = Array.from(scopeKeys);
|
|
518
|
+
if (wsConnectionManager) {
|
|
519
|
+
wsConnectionManager.notifyScopeKeys(combinedScopeKeys, latestCommitSeq, {
|
|
520
|
+
excludeClientIds: [ctx.clientId],
|
|
521
|
+
changes: emittedChanges,
|
|
522
|
+
actorId: latestActorId,
|
|
523
|
+
createdAt: latestCreatedAt,
|
|
524
|
+
});
|
|
525
|
+
}
|
|
526
|
+
if (realtimeBroadcaster) {
|
|
527
|
+
realtimeBroadcaster
|
|
528
|
+
.publish({
|
|
529
|
+
type: 'commit',
|
|
530
|
+
commitSeq: latestCommitSeq,
|
|
531
|
+
partitionId: ctx.partitionId,
|
|
532
|
+
scopeKeys: combinedScopeKeys,
|
|
533
|
+
sourceInstanceId: instanceId,
|
|
534
|
+
})
|
|
535
|
+
.catch((error) => {
|
|
536
|
+
logAsyncFailureOnce('sync.realtime.broadcast_publish_failed', {
|
|
537
|
+
event: 'sync.realtime.broadcast_publish_failed',
|
|
538
|
+
userId: ctx.auth.actorId,
|
|
539
|
+
clientId: ctx.clientId,
|
|
540
|
+
error: error instanceof Error ? error.message : String(error),
|
|
541
|
+
});
|
|
542
|
+
});
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
function recordPushExecutionSideEffects(ctx, summary) {
|
|
546
|
+
recordRequestEventInBackground(() => ({
|
|
547
|
+
partitionId: ctx.partitionId,
|
|
548
|
+
requestId: ctx.requestId,
|
|
549
|
+
traceId: ctx.traceContext.traceId,
|
|
550
|
+
spanId: ctx.traceContext.spanId,
|
|
551
|
+
eventType: 'push',
|
|
552
|
+
syncPath: ctx.syncPath,
|
|
553
|
+
actorId: ctx.auth.actorId,
|
|
554
|
+
clientId: ctx.clientId,
|
|
555
|
+
transportPath: ctx.transportPath,
|
|
556
|
+
statusCode: 200,
|
|
557
|
+
outcome: summary.outcome,
|
|
558
|
+
responseStatus: normalizeResponseStatus(200, summary.outcome),
|
|
559
|
+
durationMs: summary.durationMs,
|
|
560
|
+
errorCode: firstPushErrorCode(summary.results),
|
|
561
|
+
commitSeq: summary.commitSeq,
|
|
562
|
+
operationCount: summary.operationCount,
|
|
563
|
+
tables: summary.tables,
|
|
564
|
+
payloadSnapshot: summary.payloadSnapshot,
|
|
565
|
+
}));
|
|
566
|
+
emitConsoleLiveEvent(consoleLiveEmitter, 'push', () => ({
|
|
567
|
+
partitionId: ctx.partitionId,
|
|
568
|
+
requestId: ctx.requestId,
|
|
569
|
+
traceId: ctx.traceContext.traceId,
|
|
570
|
+
spanId: ctx.traceContext.spanId,
|
|
571
|
+
actorId: ctx.auth.actorId,
|
|
572
|
+
clientId: ctx.clientId,
|
|
573
|
+
transportPath: ctx.transportPath,
|
|
574
|
+
syncPath: ctx.syncPath,
|
|
575
|
+
outcome: summary.outcome,
|
|
576
|
+
statusCode: 200,
|
|
577
|
+
durationMs: summary.durationMs,
|
|
578
|
+
commitSeq: summary.commitSeq,
|
|
579
|
+
operationCount: summary.operationCount,
|
|
580
|
+
tables: summary.tables,
|
|
581
|
+
}));
|
|
582
|
+
}
|
|
583
|
+
function maybeCountPushConflicts(ctx, results, enabled) {
|
|
584
|
+
if (enabled !== true) {
|
|
585
|
+
return;
|
|
586
|
+
}
|
|
587
|
+
const detectedConflicts = results.reduce((count, result) => count + (result.status === 'conflict' ? 1 : 0), 0);
|
|
588
|
+
if (detectedConflicts <= 0) {
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
countSyncMetric('sync.conflicts.detected', detectedConflicts, {
|
|
592
|
+
attributes: {
|
|
593
|
+
syncPath: ctx.syncPath,
|
|
594
|
+
transportPath: ctx.transportPath,
|
|
595
|
+
},
|
|
596
|
+
});
|
|
597
|
+
}
|
|
598
|
+
function emitCommitLiveEvents(ctx, pushedCommits) {
|
|
599
|
+
for (const pushed of pushedCommits) {
|
|
600
|
+
if (pushed.response.ok !== true ||
|
|
601
|
+
pushed.response.status !== 'applied' ||
|
|
602
|
+
typeof pushed.response.commitSeq !== 'number') {
|
|
603
|
+
continue;
|
|
604
|
+
}
|
|
605
|
+
emitConsoleLiveEvent(consoleLiveEmitter, 'commit', () => ({
|
|
606
|
+
partitionId: ctx.partitionId,
|
|
607
|
+
commitSeq: pushed.response.commitSeq,
|
|
608
|
+
actorId: ctx.auth.actorId,
|
|
609
|
+
clientId: ctx.clientId,
|
|
610
|
+
affectedTables: pushed.affectedTables,
|
|
611
|
+
}));
|
|
612
|
+
}
|
|
613
|
+
}
|
|
614
|
+
async function executePushCommitBatchWithSideEffects(ctx, pushBodies, execOptions = {}) {
|
|
615
|
+
const timer = createSyncTimer();
|
|
616
|
+
const totalOperationCount = pushBodies.reduce((count, pushBody) => count + (pushBody.operations?.length ?? 0), 0);
|
|
617
|
+
const executedPushes = await pushCommitBatch({
|
|
618
|
+
db: options.db,
|
|
619
|
+
dialect: options.dialect,
|
|
620
|
+
handlers: handlerRegistry,
|
|
621
|
+
plugins: options.plugins,
|
|
622
|
+
auth: ctx.auth,
|
|
623
|
+
suppressTelemetry: true,
|
|
624
|
+
requests: pushBodies.map((pushBody) => ({
|
|
625
|
+
clientId: ctx.clientId,
|
|
626
|
+
clientCommitId: pushBody.clientCommitId,
|
|
627
|
+
operations: pushBody.operations,
|
|
628
|
+
schemaVersion: pushBody.schemaVersion,
|
|
629
|
+
})),
|
|
630
|
+
});
|
|
631
|
+
const affectedTables = new Set();
|
|
632
|
+
for (const pushed of executedPushes) {
|
|
633
|
+
for (const table of pushed.affectedTables) {
|
|
634
|
+
affectedTables.add(table);
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
const pushDurationMs = timer();
|
|
638
|
+
const latestCommitSeq = executedPushes.reduce((latest, pushed) => {
|
|
639
|
+
if (typeof pushed.response.commitSeq === 'number') {
|
|
640
|
+
return Math.max(latest, pushed.response.commitSeq);
|
|
641
|
+
}
|
|
642
|
+
return latest;
|
|
643
|
+
}, 0);
|
|
644
|
+
const aggregateStatus = executedPushes.every((pushed) => pushed.response.status === 'cached')
|
|
645
|
+
? 'cached'
|
|
646
|
+
: executedPushes.every((pushed) => pushed.response.status === 'applied' ||
|
|
647
|
+
pushed.response.status === 'cached')
|
|
648
|
+
? 'applied'
|
|
649
|
+
: 'rejected';
|
|
650
|
+
const aggregatedResults = executedPushes.flatMap((pushed) => pushed.response.results);
|
|
651
|
+
logSyncEvent({
|
|
652
|
+
event: 'sync.push',
|
|
653
|
+
userId: ctx.auth.actorId,
|
|
654
|
+
durationMs: pushDurationMs,
|
|
655
|
+
operationCount: totalOperationCount,
|
|
656
|
+
status: aggregateStatus,
|
|
657
|
+
commitSeq: latestCommitSeq > 0 ? latestCommitSeq : undefined,
|
|
658
|
+
});
|
|
659
|
+
recordPushExecutionSideEffects(ctx, {
|
|
660
|
+
durationMs: pushDurationMs,
|
|
661
|
+
outcome: aggregateStatus,
|
|
662
|
+
commitSeq: latestCommitSeq > 0 ? latestCommitSeq : null,
|
|
663
|
+
operationCount: totalOperationCount,
|
|
664
|
+
tables: Array.from(affectedTables),
|
|
665
|
+
results: aggregatedResults,
|
|
666
|
+
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
667
|
+
? {
|
|
668
|
+
request: {
|
|
669
|
+
clientId: ctx.clientId,
|
|
670
|
+
commits: pushBodies.map((pushBody) => ({
|
|
671
|
+
clientCommitId: pushBody.clientCommitId,
|
|
672
|
+
schemaVersion: pushBody.schemaVersion,
|
|
673
|
+
operations: pushBody.operations,
|
|
674
|
+
})),
|
|
675
|
+
},
|
|
676
|
+
response: {
|
|
677
|
+
ok: true,
|
|
678
|
+
commits: executedPushes.map((pushed, index) => ({
|
|
679
|
+
clientCommitId: pushBodies[index]?.clientCommitId ?? '',
|
|
680
|
+
...pushed.response,
|
|
681
|
+
})),
|
|
682
|
+
},
|
|
683
|
+
}
|
|
684
|
+
: null,
|
|
685
|
+
});
|
|
686
|
+
maybeCountPushConflicts(ctx, aggregatedResults, execOptions.countConflictsMetric);
|
|
687
|
+
notifyRealtimeForAppliedPushes(ctx, executedPushes);
|
|
688
|
+
emitCommitLiveEvents(ctx, executedPushes);
|
|
689
|
+
return executedPushes;
|
|
690
|
+
}
|
|
491
691
|
async function executePushCommitWithSideEffects(ctx, pushBody, execOptions = {}) {
|
|
492
692
|
const timer = createSyncTimer();
|
|
493
693
|
const pushOps = pushBody.operations ?? [];
|
|
@@ -513,24 +713,13 @@ export function createSyncRoutes(options) {
|
|
|
513
713
|
status: pushed.response.status,
|
|
514
714
|
commitSeq: pushed.response.commitSeq,
|
|
515
715
|
});
|
|
516
|
-
|
|
517
|
-
partitionId: ctx.partitionId,
|
|
518
|
-
requestId: ctx.requestId,
|
|
519
|
-
traceId: ctx.traceContext.traceId,
|
|
520
|
-
spanId: ctx.traceContext.spanId,
|
|
521
|
-
eventType: 'push',
|
|
522
|
-
syncPath: ctx.syncPath,
|
|
523
|
-
actorId: ctx.auth.actorId,
|
|
524
|
-
clientId: ctx.clientId,
|
|
525
|
-
transportPath: ctx.transportPath,
|
|
526
|
-
statusCode: 200,
|
|
527
|
-
outcome: pushed.response.status,
|
|
528
|
-
responseStatus: normalizeResponseStatus(200, pushed.response.status),
|
|
716
|
+
recordPushExecutionSideEffects(ctx, {
|
|
529
717
|
durationMs: pushDurationMs,
|
|
530
|
-
|
|
531
|
-
commitSeq: pushed.response.commitSeq,
|
|
718
|
+
outcome: pushed.response.status,
|
|
719
|
+
commitSeq: pushed.response.commitSeq ?? null,
|
|
532
720
|
operationCount: pushOps.length,
|
|
533
721
|
tables: pushed.affectedTables,
|
|
722
|
+
results: pushed.response.results,
|
|
534
723
|
payloadSnapshot: shouldCaptureRequestPayloadSnapshots
|
|
535
724
|
? {
|
|
536
725
|
request: {
|
|
@@ -542,77 +731,12 @@ export function createSyncRoutes(options) {
|
|
|
542
731
|
response: pushed.response,
|
|
543
732
|
}
|
|
544
733
|
: null,
|
|
545
|
-
})
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
traceId: ctx.traceContext.traceId,
|
|
550
|
-
spanId: ctx.traceContext.spanId,
|
|
551
|
-
actorId: ctx.auth.actorId,
|
|
552
|
-
clientId: ctx.clientId,
|
|
553
|
-
transportPath: ctx.transportPath,
|
|
554
|
-
syncPath: ctx.syncPath,
|
|
555
|
-
outcome: pushed.response.status,
|
|
556
|
-
statusCode: 200,
|
|
557
|
-
durationMs: pushDurationMs,
|
|
558
|
-
commitSeq: pushed.response.commitSeq ?? null,
|
|
559
|
-
operationCount: pushOps.length,
|
|
560
|
-
tables: pushed.affectedTables,
|
|
561
|
-
}));
|
|
562
|
-
if (execOptions.countConflictsMetric === true) {
|
|
563
|
-
const detectedConflicts = pushed.response.results.reduce((count, result) => count + (result.status === 'conflict' ? 1 : 0), 0);
|
|
564
|
-
if (detectedConflicts > 0) {
|
|
565
|
-
countSyncMetric('sync.conflicts.detected', detectedConflicts, {
|
|
566
|
-
attributes: {
|
|
567
|
-
syncPath: ctx.syncPath,
|
|
568
|
-
transportPath: ctx.transportPath,
|
|
569
|
-
},
|
|
570
|
-
});
|
|
571
|
-
}
|
|
572
|
-
}
|
|
573
|
-
if (wsConnectionManager &&
|
|
574
|
-
pushed.response.ok === true &&
|
|
575
|
-
pushed.response.status === 'applied' &&
|
|
576
|
-
typeof pushed.response.commitSeq === 'number') {
|
|
577
|
-
const scopeKeys = applyPartitionToScopeKeys(ctx.partitionId, pushed.scopeKeys);
|
|
578
|
-
if (scopeKeys.length > 0) {
|
|
579
|
-
wsConnectionManager.notifyScopeKeys(scopeKeys, pushed.response.commitSeq, {
|
|
580
|
-
excludeClientIds: [ctx.clientId],
|
|
581
|
-
changes: pushed.emittedChanges,
|
|
582
|
-
actorId: pushed.commitActorId ?? ctx.auth.actorId,
|
|
583
|
-
createdAt: pushed.commitCreatedAt ?? new Date().toISOString(),
|
|
584
|
-
});
|
|
585
|
-
if (realtimeBroadcaster) {
|
|
586
|
-
realtimeBroadcaster
|
|
587
|
-
.publish({
|
|
588
|
-
type: 'commit',
|
|
589
|
-
commitSeq: pushed.response.commitSeq,
|
|
590
|
-
partitionId: ctx.partitionId,
|
|
591
|
-
scopeKeys,
|
|
592
|
-
sourceInstanceId: instanceId,
|
|
593
|
-
})
|
|
594
|
-
.catch((error) => {
|
|
595
|
-
logAsyncFailureOnce('sync.realtime.broadcast_publish_failed', {
|
|
596
|
-
event: 'sync.realtime.broadcast_publish_failed',
|
|
597
|
-
userId: ctx.auth.actorId,
|
|
598
|
-
clientId: ctx.clientId,
|
|
599
|
-
error: error instanceof Error ? error.message : String(error),
|
|
600
|
-
});
|
|
601
|
-
});
|
|
602
|
-
}
|
|
603
|
-
}
|
|
604
|
-
}
|
|
605
|
-
if (pushed.response.ok === true &&
|
|
606
|
-
pushed.response.status === 'applied' &&
|
|
607
|
-
typeof pushed.response.commitSeq === 'number') {
|
|
608
|
-
emitConsoleLiveEvent(consoleLiveEmitter, 'commit', () => ({
|
|
609
|
-
partitionId: ctx.partitionId,
|
|
610
|
-
commitSeq: pushed.response.commitSeq,
|
|
611
|
-
actorId: ctx.auth.actorId,
|
|
612
|
-
clientId: ctx.clientId,
|
|
613
|
-
affectedTables: pushed.affectedTables,
|
|
614
|
-
}));
|
|
734
|
+
});
|
|
735
|
+
maybeCountPushConflicts(ctx, pushed.response.results, execOptions.countConflictsMetric);
|
|
736
|
+
if (execOptions.deferRealtimeNotifications !== true) {
|
|
737
|
+
notifyRealtimeForAppliedPushes(ctx, [pushed]);
|
|
615
738
|
}
|
|
739
|
+
emitCommitLiveEvents(ctx, [pushed]);
|
|
616
740
|
return pushed;
|
|
617
741
|
}
|
|
618
742
|
const authCache = new WeakMap();
|
|
@@ -939,24 +1063,59 @@ export function createSyncRoutes(options) {
|
|
|
939
1063
|
let pullResponse;
|
|
940
1064
|
// --- Push phase ---
|
|
941
1065
|
if (body.push) {
|
|
942
|
-
const
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1066
|
+
const pushBodies = body.push.commits ?? [];
|
|
1067
|
+
const pushedCommits = [];
|
|
1068
|
+
for (const pushBody of pushBodies) {
|
|
1069
|
+
const pushOps = pushBody.operations ?? [];
|
|
1070
|
+
if (pushOps.length > maxOperationsPerPush) {
|
|
1071
|
+
return c.json({
|
|
1072
|
+
error: 'TOO_MANY_OPERATIONS',
|
|
1073
|
+
message: `Maximum ${maxOperationsPerPush} operations per push`,
|
|
1074
|
+
}, 400);
|
|
1075
|
+
}
|
|
949
1076
|
}
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1077
|
+
const executedPushes = pushBodies.length > 1
|
|
1078
|
+
? await executePushCommitBatchWithSideEffects({
|
|
1079
|
+
auth,
|
|
1080
|
+
clientId,
|
|
1081
|
+
partitionId,
|
|
1082
|
+
requestId,
|
|
1083
|
+
traceContext,
|
|
1084
|
+
transportPath,
|
|
1085
|
+
syncPath: 'http-combined',
|
|
1086
|
+
}, pushBodies, {
|
|
1087
|
+
countConflictsMetric: true,
|
|
1088
|
+
})
|
|
1089
|
+
: [];
|
|
1090
|
+
for (let index = 0; index < pushBodies.length; index += 1) {
|
|
1091
|
+
const pushBody = pushBodies[index];
|
|
1092
|
+
if (!pushBody)
|
|
1093
|
+
continue;
|
|
1094
|
+
const pushed = pushBodies.length > 1
|
|
1095
|
+
? executedPushes[index]
|
|
1096
|
+
: await executePushCommitWithSideEffects({
|
|
1097
|
+
auth,
|
|
1098
|
+
clientId,
|
|
1099
|
+
partitionId,
|
|
1100
|
+
requestId,
|
|
1101
|
+
traceContext,
|
|
1102
|
+
transportPath,
|
|
1103
|
+
syncPath: 'http-combined',
|
|
1104
|
+
}, pushBody, {
|
|
1105
|
+
countConflictsMetric: true,
|
|
1106
|
+
});
|
|
1107
|
+
if (!pushed) {
|
|
1108
|
+
throw new Error('Server returned incomplete batched push result');
|
|
1109
|
+
}
|
|
1110
|
+
pushedCommits.push({
|
|
1111
|
+
clientCommitId: pushBody.clientCommitId,
|
|
1112
|
+
...pushed.response,
|
|
1113
|
+
});
|
|
1114
|
+
}
|
|
1115
|
+
pushResponse = {
|
|
1116
|
+
ok: true,
|
|
1117
|
+
commits: pushedCommits,
|
|
1118
|
+
};
|
|
960
1119
|
}
|
|
961
1120
|
// --- Pull phase ---
|
|
962
1121
|
if (body.pull) {
|