@syncular/server-hono 0.0.6-93 → 0.0.6-96
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/console/routes.d.ts.map +1 -1
- package/dist/console/routes.js +212 -40
- package/dist/console/routes.js.map +1 -1
- package/dist/console/types.d.ts +38 -0
- package/dist/console/types.d.ts.map +1 -1
- package/dist/create-server.d.ts.map +1 -1
- package/dist/create-server.js +7 -1
- package/dist/create-server.js.map +1 -1
- package/dist/routes.d.ts +21 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +92 -56
- package/dist/routes.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/console-routes.test.ts +155 -5
- package/src/__tests__/create-server.test.ts +147 -4
- package/src/console/routes.ts +280 -48
- package/src/console/types.ts +39 -0
- package/src/create-server.ts +8 -1
- package/src/routes.ts +134 -63
package/src/console/routes.ts
CHANGED
|
@@ -391,6 +391,25 @@ const handlersResponseSchema = z.object({
|
|
|
391
391
|
items: z.array(ConsoleHandlerSchema),
|
|
392
392
|
});
|
|
393
393
|
|
|
394
|
+
const DEFAULT_REQUEST_EVENTS_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
395
|
+
const DEFAULT_REQUEST_EVENTS_MAX_ROWS = 10_000;
|
|
396
|
+
const DEFAULT_OPERATION_EVENTS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
397
|
+
const DEFAULT_OPERATION_EVENTS_MAX_ROWS = 5_000;
|
|
398
|
+
const DEFAULT_AUTO_EVENTS_PRUNE_INTERVAL_MS = 5 * 60 * 1000;
|
|
399
|
+
|
|
400
|
+
function readNonNegativeInteger(
|
|
401
|
+
value: number | undefined,
|
|
402
|
+
fallback: number
|
|
403
|
+
): number {
|
|
404
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
405
|
+
return fallback;
|
|
406
|
+
}
|
|
407
|
+
if (value < 0) {
|
|
408
|
+
return fallback;
|
|
409
|
+
}
|
|
410
|
+
return Math.floor(value);
|
|
411
|
+
}
|
|
412
|
+
|
|
394
413
|
export function createConsoleRoutes<
|
|
395
414
|
DB extends SyncCoreDb,
|
|
396
415
|
Auth extends SyncServerAuth,
|
|
@@ -489,11 +508,36 @@ export function createConsoleRoutes<
|
|
|
489
508
|
1,
|
|
490
509
|
options.metrics?.rawFallbackMaxEvents ?? 5000
|
|
491
510
|
);
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
511
|
+
const requestEventsMaxAgeMs = readNonNegativeInteger(
|
|
512
|
+
options.maintenance?.requestEventsMaxAgeMs,
|
|
513
|
+
DEFAULT_REQUEST_EVENTS_MAX_AGE_MS
|
|
514
|
+
);
|
|
515
|
+
const requestEventsMaxRows = readNonNegativeInteger(
|
|
516
|
+
options.maintenance?.requestEventsMaxRows,
|
|
517
|
+
DEFAULT_REQUEST_EVENTS_MAX_ROWS
|
|
518
|
+
);
|
|
519
|
+
const operationEventsMaxAgeMs = readNonNegativeInteger(
|
|
520
|
+
options.maintenance?.operationEventsMaxAgeMs,
|
|
521
|
+
DEFAULT_OPERATION_EVENTS_MAX_AGE_MS
|
|
522
|
+
);
|
|
523
|
+
const operationEventsMaxRows = readNonNegativeInteger(
|
|
524
|
+
options.maintenance?.operationEventsMaxRows,
|
|
525
|
+
DEFAULT_OPERATION_EVENTS_MAX_ROWS
|
|
526
|
+
);
|
|
527
|
+
const autoEventsPruneIntervalMs = readNonNegativeInteger(
|
|
528
|
+
options.maintenance?.autoPruneIntervalMs,
|
|
529
|
+
DEFAULT_AUTO_EVENTS_PRUNE_INTERVAL_MS
|
|
530
|
+
);
|
|
531
|
+
let lastEventsPruneRunAt = 0;
|
|
532
|
+
|
|
533
|
+
// Ensure console schema exists before handlers query console tables.
|
|
534
|
+
const consoleSchemaReadyPromise = (
|
|
535
|
+
options.consoleSchemaReady ??
|
|
536
|
+
options.dialect.ensureConsoleSchema?.(options.db) ??
|
|
537
|
+
Promise.resolve()
|
|
538
|
+
).catch((err) => {
|
|
496
539
|
console.error('[console] Failed to ensure console schema:', err);
|
|
540
|
+
throw err;
|
|
497
541
|
});
|
|
498
542
|
|
|
499
543
|
// CORS configuration
|
|
@@ -501,11 +545,12 @@ export function createConsoleRoutes<
|
|
|
501
545
|
'http://localhost:5173',
|
|
502
546
|
'https://console.sync.dev',
|
|
503
547
|
];
|
|
548
|
+
const allowWildcardCors = corsOrigins === '*';
|
|
504
549
|
|
|
505
550
|
routes.use(
|
|
506
551
|
'*',
|
|
507
552
|
cors({
|
|
508
|
-
origin:
|
|
553
|
+
origin: allowWildcardCors ? '*' : corsOrigins,
|
|
509
554
|
allowMethods: ['GET', 'POST', 'DELETE', 'OPTIONS'],
|
|
510
555
|
allowHeaders: [
|
|
511
556
|
'Content-Type',
|
|
@@ -517,10 +562,36 @@ export function createConsoleRoutes<
|
|
|
517
562
|
'Tracestate',
|
|
518
563
|
],
|
|
519
564
|
exposeHeaders: ['X-Total-Count'],
|
|
520
|
-
credentials:
|
|
565
|
+
credentials: !allowWildcardCors,
|
|
521
566
|
})
|
|
522
567
|
);
|
|
523
568
|
|
|
569
|
+
const ensureConsoleSchemaReady = async (
|
|
570
|
+
c: Context
|
|
571
|
+
): Promise<Response | null> => {
|
|
572
|
+
try {
|
|
573
|
+
await consoleSchemaReadyPromise;
|
|
574
|
+
return null;
|
|
575
|
+
} catch {
|
|
576
|
+
return c.json({ error: 'CONSOLE_SCHEMA_UNAVAILABLE' }, 503);
|
|
577
|
+
}
|
|
578
|
+
};
|
|
579
|
+
|
|
580
|
+
routes.use('*', async (c, next) => {
|
|
581
|
+
const readyError = await ensureConsoleSchemaReady(c);
|
|
582
|
+
if (readyError) {
|
|
583
|
+
return readyError;
|
|
584
|
+
}
|
|
585
|
+
await next();
|
|
586
|
+
});
|
|
587
|
+
|
|
588
|
+
routes.use('*', async (c, next) => {
|
|
589
|
+
if (c.req.method !== 'OPTIONS') {
|
|
590
|
+
triggerAutomaticEventsPrune();
|
|
591
|
+
}
|
|
592
|
+
await next();
|
|
593
|
+
});
|
|
594
|
+
|
|
524
595
|
// Auth middleware
|
|
525
596
|
const requireAuth = async (c: Context): Promise<ConsoleAuthResult | null> => {
|
|
526
597
|
const auth = await options.authenticate(c);
|
|
@@ -616,6 +687,202 @@ export function createConsoleRoutes<
|
|
|
616
687
|
createdAt: row.created_at ?? '',
|
|
617
688
|
});
|
|
618
689
|
|
|
690
|
+
type PruneEventsRunResult = {
|
|
691
|
+
requestEventsDeleted: number;
|
|
692
|
+
operationEventsDeleted: number;
|
|
693
|
+
payloadSnapshotsDeleted: number;
|
|
694
|
+
totalDeleted: number;
|
|
695
|
+
};
|
|
696
|
+
|
|
697
|
+
const deleteUnreferencedPayloadSnapshots = async (): Promise<number> => {
|
|
698
|
+
const result = await db
|
|
699
|
+
.deleteFrom('sync_request_payloads')
|
|
700
|
+
.where(
|
|
701
|
+
'payload_ref',
|
|
702
|
+
'not in',
|
|
703
|
+
db
|
|
704
|
+
.selectFrom('sync_request_events')
|
|
705
|
+
.select('payload_ref')
|
|
706
|
+
.where('payload_ref', 'is not', null)
|
|
707
|
+
)
|
|
708
|
+
.executeTakeFirst();
|
|
709
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
710
|
+
};
|
|
711
|
+
|
|
712
|
+
const pruneRequestEventsByAge = async (): Promise<number> => {
|
|
713
|
+
if (requestEventsMaxAgeMs <= 0) {
|
|
714
|
+
return 0;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const cutoffDate = new Date(Date.now() - requestEventsMaxAgeMs);
|
|
718
|
+
const result = await db
|
|
719
|
+
.deleteFrom('sync_request_events')
|
|
720
|
+
.where('created_at', '<', cutoffDate.toISOString())
|
|
721
|
+
.executeTakeFirst();
|
|
722
|
+
|
|
723
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
724
|
+
};
|
|
725
|
+
|
|
726
|
+
const pruneRequestEventsByCount = async (): Promise<number> => {
|
|
727
|
+
if (requestEventsMaxRows <= 0) {
|
|
728
|
+
return 0;
|
|
729
|
+
}
|
|
730
|
+
|
|
731
|
+
const countRow = await db
|
|
732
|
+
.selectFrom('sync_request_events')
|
|
733
|
+
.select(({ fn }) => fn.countAll().as('total'))
|
|
734
|
+
.executeTakeFirst();
|
|
735
|
+
|
|
736
|
+
const total = coerceNumber(countRow?.total) ?? 0;
|
|
737
|
+
if (total <= requestEventsMaxRows) {
|
|
738
|
+
return 0;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
const cutoffRow = await db
|
|
742
|
+
.selectFrom('sync_request_events')
|
|
743
|
+
.select(['event_id'])
|
|
744
|
+
.orderBy('event_id', 'desc')
|
|
745
|
+
.offset(requestEventsMaxRows)
|
|
746
|
+
.limit(1)
|
|
747
|
+
.executeTakeFirst();
|
|
748
|
+
|
|
749
|
+
const cutoffEventId = coerceNumber(cutoffRow?.event_id);
|
|
750
|
+
if (cutoffEventId === null) {
|
|
751
|
+
return 0;
|
|
752
|
+
}
|
|
753
|
+
|
|
754
|
+
const result = await db
|
|
755
|
+
.deleteFrom('sync_request_events')
|
|
756
|
+
.where('event_id', '<=', cutoffEventId)
|
|
757
|
+
.executeTakeFirst();
|
|
758
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
759
|
+
};
|
|
760
|
+
|
|
761
|
+
const pruneOperationEventsByAge = async (): Promise<number> => {
|
|
762
|
+
if (operationEventsMaxAgeMs <= 0) {
|
|
763
|
+
return 0;
|
|
764
|
+
}
|
|
765
|
+
|
|
766
|
+
const cutoffDate = new Date(Date.now() - operationEventsMaxAgeMs);
|
|
767
|
+
const result = await db
|
|
768
|
+
.deleteFrom('sync_operation_events')
|
|
769
|
+
.where('created_at', '<', cutoffDate.toISOString())
|
|
770
|
+
.executeTakeFirst();
|
|
771
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
772
|
+
};
|
|
773
|
+
|
|
774
|
+
const pruneOperationEventsByCount = async (): Promise<number> => {
|
|
775
|
+
if (operationEventsMaxRows <= 0) {
|
|
776
|
+
return 0;
|
|
777
|
+
}
|
|
778
|
+
|
|
779
|
+
const countRow = await db
|
|
780
|
+
.selectFrom('sync_operation_events')
|
|
781
|
+
.select(({ fn }) => fn.countAll().as('total'))
|
|
782
|
+
.executeTakeFirst();
|
|
783
|
+
const total = coerceNumber(countRow?.total) ?? 0;
|
|
784
|
+
if (total <= operationEventsMaxRows) {
|
|
785
|
+
return 0;
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
const cutoffRow = await db
|
|
789
|
+
.selectFrom('sync_operation_events')
|
|
790
|
+
.select(['operation_id'])
|
|
791
|
+
.orderBy('operation_id', 'desc')
|
|
792
|
+
.offset(operationEventsMaxRows)
|
|
793
|
+
.limit(1)
|
|
794
|
+
.executeTakeFirst();
|
|
795
|
+
|
|
796
|
+
const cutoffOperationId = coerceNumber(cutoffRow?.operation_id);
|
|
797
|
+
if (cutoffOperationId === null) {
|
|
798
|
+
return 0;
|
|
799
|
+
}
|
|
800
|
+
|
|
801
|
+
const result = await db
|
|
802
|
+
.deleteFrom('sync_operation_events')
|
|
803
|
+
.where('operation_id', '<=', cutoffOperationId)
|
|
804
|
+
.executeTakeFirst();
|
|
805
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
806
|
+
};
|
|
807
|
+
|
|
808
|
+
const pruneConsoleEvents = async (): Promise<PruneEventsRunResult> => {
|
|
809
|
+
const requestEventsDeletedByAge = await pruneRequestEventsByAge();
|
|
810
|
+
const requestEventsDeletedByCount = await pruneRequestEventsByCount();
|
|
811
|
+
const requestEventsDeleted =
|
|
812
|
+
requestEventsDeletedByAge + requestEventsDeletedByCount;
|
|
813
|
+
|
|
814
|
+
const operationEventsDeletedByAge = await pruneOperationEventsByAge();
|
|
815
|
+
const operationEventsDeletedByCount = await pruneOperationEventsByCount();
|
|
816
|
+
const operationEventsDeleted =
|
|
817
|
+
operationEventsDeletedByAge + operationEventsDeletedByCount;
|
|
818
|
+
|
|
819
|
+
const payloadSnapshotsDeleted = await deleteUnreferencedPayloadSnapshots();
|
|
820
|
+
const totalDeleted = requestEventsDeleted + operationEventsDeleted;
|
|
821
|
+
|
|
822
|
+
return {
|
|
823
|
+
requestEventsDeleted,
|
|
824
|
+
operationEventsDeleted,
|
|
825
|
+
payloadSnapshotsDeleted,
|
|
826
|
+
totalDeleted,
|
|
827
|
+
};
|
|
828
|
+
};
|
|
829
|
+
|
|
830
|
+
let eventsPrunePromise: Promise<PruneEventsRunResult> | null = null;
|
|
831
|
+
|
|
832
|
+
const runEventsPrune = async (): Promise<PruneEventsRunResult> => {
|
|
833
|
+
if (eventsPrunePromise) {
|
|
834
|
+
return eventsPrunePromise;
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
let pending: Promise<PruneEventsRunResult>;
|
|
838
|
+
pending = pruneConsoleEvents()
|
|
839
|
+
.then((result) => {
|
|
840
|
+
lastEventsPruneRunAt = Date.now();
|
|
841
|
+
return result;
|
|
842
|
+
})
|
|
843
|
+
.finally(() => {
|
|
844
|
+
if (eventsPrunePromise === pending) {
|
|
845
|
+
eventsPrunePromise = null;
|
|
846
|
+
}
|
|
847
|
+
});
|
|
848
|
+
|
|
849
|
+
eventsPrunePromise = pending;
|
|
850
|
+
return pending;
|
|
851
|
+
};
|
|
852
|
+
|
|
853
|
+
const triggerAutomaticEventsPrune = (): void => {
|
|
854
|
+
if (autoEventsPruneIntervalMs <= 0) {
|
|
855
|
+
return;
|
|
856
|
+
}
|
|
857
|
+
if (eventsPrunePromise) {
|
|
858
|
+
return;
|
|
859
|
+
}
|
|
860
|
+
if (Date.now() - lastEventsPruneRunAt < autoEventsPruneIntervalMs) {
|
|
861
|
+
return;
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
void runEventsPrune()
|
|
865
|
+
.then((result) => {
|
|
866
|
+
if (result.totalDeleted <= 0 && result.payloadSnapshotsDeleted <= 0) {
|
|
867
|
+
return;
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
logSyncEvent({
|
|
871
|
+
event: 'console.prune_events_auto',
|
|
872
|
+
deletedCount: result.totalDeleted,
|
|
873
|
+
requestEventsDeleted: result.requestEventsDeleted,
|
|
874
|
+
operationEventsDeleted: result.operationEventsDeleted,
|
|
875
|
+
payloadDeletedCount: result.payloadSnapshotsDeleted,
|
|
876
|
+
});
|
|
877
|
+
})
|
|
878
|
+
.catch((error) => {
|
|
879
|
+
logSyncEvent({
|
|
880
|
+
event: 'console.prune_events_auto_failed',
|
|
881
|
+
error: error instanceof Error ? error.message : String(error),
|
|
882
|
+
});
|
|
883
|
+
});
|
|
884
|
+
};
|
|
885
|
+
|
|
619
886
|
const recordOperationEvent = async (event: {
|
|
620
887
|
operationType: ConsoleOperationType;
|
|
621
888
|
consoleUserId?: string;
|
|
@@ -2696,11 +2963,13 @@ export function createConsoleRoutes<
|
|
|
2696
2963
|
const res = await db.deleteFrom('sync_request_events').executeTakeFirst();
|
|
2697
2964
|
|
|
2698
2965
|
const deletedCount = Number(res?.numDeletedRows ?? 0);
|
|
2966
|
+
const payloadDeletedCount = await deleteUnreferencedPayloadSnapshots();
|
|
2699
2967
|
|
|
2700
2968
|
logSyncEvent({
|
|
2701
2969
|
event: 'console.clear_events',
|
|
2702
2970
|
consoleUserId: auth.consoleUserId,
|
|
2703
2971
|
deletedCount,
|
|
2972
|
+
payloadDeletedCount,
|
|
2704
2973
|
});
|
|
2705
2974
|
|
|
2706
2975
|
const result: ConsoleClearEventsResult = { deletedCount };
|
|
@@ -2738,53 +3007,16 @@ export function createConsoleRoutes<
|
|
|
2738
3007
|
const auth = await requireAuth(c);
|
|
2739
3008
|
if (!auth) return c.json({ error: 'UNAUTHENTICATED' }, 401);
|
|
2740
3009
|
|
|
2741
|
-
|
|
2742
|
-
const
|
|
2743
|
-
|
|
2744
|
-
// Delete by date first
|
|
2745
|
-
const resByDate = await db
|
|
2746
|
-
.deleteFrom('sync_request_events')
|
|
2747
|
-
.where('created_at', '<', cutoffDate.toISOString())
|
|
2748
|
-
.executeTakeFirst();
|
|
2749
|
-
|
|
2750
|
-
let deletedCount = Number(resByDate?.numDeletedRows ?? 0);
|
|
2751
|
-
|
|
2752
|
-
// Then delete oldest if we still have more than 10000 events
|
|
2753
|
-
const countRow = await db
|
|
2754
|
-
.selectFrom('sync_request_events')
|
|
2755
|
-
.select(({ fn }) => fn.countAll().as('total'))
|
|
2756
|
-
.executeTakeFirst();
|
|
2757
|
-
|
|
2758
|
-
const total = coerceNumber(countRow?.total) ?? 0;
|
|
2759
|
-
const maxEvents = 10000;
|
|
2760
|
-
|
|
2761
|
-
if (total > maxEvents) {
|
|
2762
|
-
// Find event_id cutoff to keep only newest maxEvents
|
|
2763
|
-
const cutoffRow = await db
|
|
2764
|
-
.selectFrom('sync_request_events')
|
|
2765
|
-
.select(['event_id'])
|
|
2766
|
-
.orderBy('event_id', 'desc')
|
|
2767
|
-
.offset(maxEvents)
|
|
2768
|
-
.limit(1)
|
|
2769
|
-
.executeTakeFirst();
|
|
2770
|
-
|
|
2771
|
-
if (cutoffRow) {
|
|
2772
|
-
const cutoffEventId = coerceNumber(cutoffRow.event_id);
|
|
2773
|
-
if (cutoffEventId !== null) {
|
|
2774
|
-
const resByCount = await db
|
|
2775
|
-
.deleteFrom('sync_request_events')
|
|
2776
|
-
.where('event_id', '<=', cutoffEventId)
|
|
2777
|
-
.executeTakeFirst();
|
|
2778
|
-
|
|
2779
|
-
deletedCount += Number(resByCount?.numDeletedRows ?? 0);
|
|
2780
|
-
}
|
|
2781
|
-
}
|
|
2782
|
-
}
|
|
3010
|
+
const pruneResult = await runEventsPrune();
|
|
3011
|
+
const deletedCount = pruneResult.totalDeleted;
|
|
2783
3012
|
|
|
2784
3013
|
logSyncEvent({
|
|
2785
3014
|
event: 'console.prune_events',
|
|
2786
3015
|
consoleUserId: auth.consoleUserId,
|
|
2787
3016
|
deletedCount,
|
|
3017
|
+
requestEventsDeleted: pruneResult.requestEventsDeleted,
|
|
3018
|
+
operationEventsDeleted: pruneResult.operationEventsDeleted,
|
|
3019
|
+
payloadDeletedCount: pruneResult.payloadSnapshotsDeleted,
|
|
2788
3020
|
});
|
|
2789
3021
|
|
|
2790
3022
|
const result: ConsolePruneEventsResult = { deletedCount };
|
package/src/console/types.ts
CHANGED
|
@@ -53,6 +53,39 @@ export interface ConsoleMetricsOptions {
|
|
|
53
53
|
rawFallbackMaxEvents?: number;
|
|
54
54
|
}
|
|
55
55
|
|
|
56
|
+
export interface ConsoleMaintenanceOptions {
|
|
57
|
+
/**
|
|
58
|
+
* Minimum interval between automatic event-prune runs.
|
|
59
|
+
* Set to 0 to disable automatic pruning.
|
|
60
|
+
* Default: 5 minutes.
|
|
61
|
+
*/
|
|
62
|
+
autoPruneIntervalMs?: number;
|
|
63
|
+
/**
|
|
64
|
+
* Max age for request events before pruning.
|
|
65
|
+
* Set to 0 to disable age-based pruning.
|
|
66
|
+
* Default: 7 days.
|
|
67
|
+
*/
|
|
68
|
+
requestEventsMaxAgeMs?: number;
|
|
69
|
+
/**
|
|
70
|
+
* Max number of request events to retain.
|
|
71
|
+
* Set to 0 to disable count-based pruning.
|
|
72
|
+
* Default: 10000.
|
|
73
|
+
*/
|
|
74
|
+
requestEventsMaxRows?: number;
|
|
75
|
+
/**
|
|
76
|
+
* Max age for operation audit events before pruning.
|
|
77
|
+
* Set to 0 to disable age-based pruning.
|
|
78
|
+
* Default: 30 days.
|
|
79
|
+
*/
|
|
80
|
+
operationEventsMaxAgeMs?: number;
|
|
81
|
+
/**
|
|
82
|
+
* Max number of operation audit events to retain.
|
|
83
|
+
* Set to 0 to disable count-based pruning.
|
|
84
|
+
* Default: 5000.
|
|
85
|
+
*/
|
|
86
|
+
operationEventsMaxRows?: number;
|
|
87
|
+
}
|
|
88
|
+
|
|
56
89
|
export interface ConsoleBlobObject {
|
|
57
90
|
key: string;
|
|
58
91
|
size: number;
|
|
@@ -89,6 +122,7 @@ export interface ConsoleSharedOptions {
|
|
|
89
122
|
*/
|
|
90
123
|
corsOrigins?: string[] | '*';
|
|
91
124
|
metrics?: ConsoleMetricsOptions;
|
|
125
|
+
maintenance?: ConsoleMaintenanceOptions;
|
|
92
126
|
blobBucket?: ConsoleBlobBucket;
|
|
93
127
|
}
|
|
94
128
|
|
|
@@ -143,4 +177,9 @@ export interface CreateConsoleRoutesOptions<
|
|
|
143
177
|
*/
|
|
144
178
|
heartbeatIntervalMs?: number;
|
|
145
179
|
};
|
|
180
|
+
/**
|
|
181
|
+
* Optional console schema readiness promise.
|
|
182
|
+
* When provided, routes wait for this promise before querying console tables.
|
|
183
|
+
*/
|
|
184
|
+
consoleSchemaReady?: Promise<void>;
|
|
146
185
|
}
|
package/src/create-server.ts
CHANGED
|
@@ -138,6 +138,10 @@ export function createSyncServer<
|
|
|
138
138
|
const consoleEventEmitter = isConsoleEnabled
|
|
139
139
|
? createConsoleEventEmitter()
|
|
140
140
|
: undefined;
|
|
141
|
+
const consoleSchemaReady =
|
|
142
|
+
isConsoleEnabled && dialect.ensureConsoleSchema
|
|
143
|
+
? dialect.ensureConsoleSchema(db)
|
|
144
|
+
: undefined;
|
|
141
145
|
|
|
142
146
|
// Create sync routes
|
|
143
147
|
const syncRoutes = createSyncRoutes({
|
|
@@ -149,6 +153,7 @@ export function createSyncServer<
|
|
|
149
153
|
chunkStorage,
|
|
150
154
|
scopeCache,
|
|
151
155
|
consoleLiveEmitter: consoleEventEmitter,
|
|
156
|
+
consoleSchemaReady,
|
|
152
157
|
sync: {
|
|
153
158
|
...routes,
|
|
154
159
|
websocket: upgradeWebSocket
|
|
@@ -179,10 +184,12 @@ export function createSyncServer<
|
|
|
179
184
|
dialect,
|
|
180
185
|
handlers: sync.handlers,
|
|
181
186
|
authenticate: createTokenAuthenticator(consoleToken),
|
|
182
|
-
corsOrigins: resolvedConsoleConfig.corsOrigins
|
|
187
|
+
corsOrigins: resolvedConsoleConfig.corsOrigins,
|
|
183
188
|
eventEmitter: consoleEventEmitter,
|
|
189
|
+
consoleSchemaReady,
|
|
184
190
|
wsConnectionManager: getSyncWebSocketConnectionManager(syncRoutes),
|
|
185
191
|
metrics: resolvedConsoleConfig.metrics,
|
|
192
|
+
maintenance: resolvedConsoleConfig.maintenance,
|
|
186
193
|
blobBucket: resolvedConsoleConfig.blobBucket,
|
|
187
194
|
...(upgradeWebSocket && {
|
|
188
195
|
websocket: {
|