@syncular/server-hono 0.0.6-185 → 0.0.6-201
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 +266 -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 +382 -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();
|
|
@@ -937,26 +1061,62 @@ export function createSyncRoutes(options) {
|
|
|
937
1061
|
}
|
|
938
1062
|
let pushResponse;
|
|
939
1063
|
let pullResponse;
|
|
1064
|
+
const exposeBenchPullTimings = c.req.header('x-syncular-bench-timings') === '1';
|
|
940
1065
|
// --- Push phase ---
|
|
941
1066
|
if (body.push) {
|
|
942
|
-
const
|
|
943
|
-
const
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
1067
|
+
const pushBodies = body.push.commits ?? [];
|
|
1068
|
+
const pushedCommits = [];
|
|
1069
|
+
for (const pushBody of pushBodies) {
|
|
1070
|
+
const pushOps = pushBody.operations ?? [];
|
|
1071
|
+
if (pushOps.length > maxOperationsPerPush) {
|
|
1072
|
+
return c.json({
|
|
1073
|
+
error: 'TOO_MANY_OPERATIONS',
|
|
1074
|
+
message: `Maximum ${maxOperationsPerPush} operations per push`,
|
|
1075
|
+
}, 400);
|
|
1076
|
+
}
|
|
949
1077
|
}
|
|
950
|
-
const
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
1078
|
+
const executedPushes = pushBodies.length > 1
|
|
1079
|
+
? await executePushCommitBatchWithSideEffects({
|
|
1080
|
+
auth,
|
|
1081
|
+
clientId,
|
|
1082
|
+
partitionId,
|
|
1083
|
+
requestId,
|
|
1084
|
+
traceContext,
|
|
1085
|
+
transportPath,
|
|
1086
|
+
syncPath: 'http-combined',
|
|
1087
|
+
}, pushBodies, {
|
|
1088
|
+
countConflictsMetric: true,
|
|
1089
|
+
})
|
|
1090
|
+
: [];
|
|
1091
|
+
for (let index = 0; index < pushBodies.length; index += 1) {
|
|
1092
|
+
const pushBody = pushBodies[index];
|
|
1093
|
+
if (!pushBody)
|
|
1094
|
+
continue;
|
|
1095
|
+
const pushed = pushBodies.length > 1
|
|
1096
|
+
? executedPushes[index]
|
|
1097
|
+
: await executePushCommitWithSideEffects({
|
|
1098
|
+
auth,
|
|
1099
|
+
clientId,
|
|
1100
|
+
partitionId,
|
|
1101
|
+
requestId,
|
|
1102
|
+
traceContext,
|
|
1103
|
+
transportPath,
|
|
1104
|
+
syncPath: 'http-combined',
|
|
1105
|
+
}, pushBody, {
|
|
1106
|
+
countConflictsMetric: true,
|
|
1107
|
+
});
|
|
1108
|
+
if (!pushed) {
|
|
1109
|
+
throw new Error('Server returned incomplete batched push result');
|
|
1110
|
+
}
|
|
1111
|
+
pushedCommits.push({
|
|
1112
|
+
clientCommitId: pushBody.clientCommitId,
|
|
1113
|
+
...pushed.response,
|
|
1114
|
+
});
|
|
1115
|
+
}
|
|
1116
|
+
pushResponse = {
|
|
1117
|
+
ok: true,
|
|
1118
|
+
commits: pushedCommits,
|
|
1119
|
+
};
|
|
960
1120
|
}
|
|
961
1121
|
// --- Pull phase ---
|
|
962
1122
|
if (body.pull) {
|
|
@@ -1111,6 +1271,9 @@ export function createSyncRoutes(options) {
|
|
|
1111
1271
|
clientCursor: pullResult.clientCursor,
|
|
1112
1272
|
};
|
|
1113
1273
|
});
|
|
1274
|
+
if (exposeBenchPullTimings && pullResult.bootstrapTimings) {
|
|
1275
|
+
c.header('x-syncular-bench-pull-timings', JSON.stringify(pullResult.bootstrapTimings));
|
|
1276
|
+
}
|
|
1114
1277
|
pullResponse = pullResult.response;
|
|
1115
1278
|
}
|
|
1116
1279
|
triggerAutoMaintenance({
|