@syncular/server-hono 0.0.6-95 → 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 +176 -37
- package/dist/console/routes.js.map +1 -1
- package/dist/console/types.d.ts +33 -0
- package/dist/console/types.d.ts.map +1 -1
- package/dist/create-server.d.ts.map +1 -1
- package/dist/create-server.js +1 -0
- package/dist/create-server.js.map +1 -1
- package/dist/routes.d.ts +16 -0
- package/dist/routes.d.ts.map +1 -1
- package/dist/routes.js +77 -55
- package/dist/routes.js.map +1 -1
- package/package.json +6 -6
- package/src/__tests__/console-routes.test.ts +92 -9
- package/src/__tests__/create-server.test.ts +147 -4
- package/src/console/routes.ts +233 -45
- package/src/console/types.ts +34 -0
- package/src/create-server.ts +1 -0
- package/src/routes.ts +107 -55
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/console/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAQ9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAqE5B,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EAEnB,0BAA0B,EAC3B,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CA0DtB;
|
|
1
|
+
{"version":3,"file":"routes.d.ts","sourceRoot":"","sources":["../../src/console/routes.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAGH,OAAO,KAAK,EAAE,SAAS,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAQ9E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AACpC,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAqE5B,OAAO,KAAK,EACV,iBAAiB,EACjB,mBAAmB,EAEnB,0BAA0B,EAC3B,MAAM,SAAS,CAAC;AAEjB;;GAEG;AACH,wBAAgB,yBAAyB,CAAC,OAAO,CAAC,EAAE;IAClD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,mBAAmB,CA0DtB;AAsPD,wBAAgB,mBAAmB,CACjC,EAAE,SAAS,UAAU,EACrB,IAAI,SAAS,cAAc,EAC3B,CAAC,SAAS,SAAS,GAAG,SAAS,EAC/B,OAAO,EAAE,0BAA0B,CAAC,EAAE,EAAE,IAAI,EAAE,CAAC,CAAC,GAAG,IAAI,CA44GxD;AA6BD;;;GAGG;AACH,wBAAgB,wBAAwB,CACtC,KAAK,CAAC,EAAE,MAAM,GACb,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,iBAAiB,GAAG,IAAI,CAAC,CAgBnD"}
|
package/dist/console/routes.js
CHANGED
|
@@ -262,6 +262,20 @@ const apiKeysQuerySchema = ConsolePaginationQuerySchema.extend({
|
|
|
262
262
|
const handlersResponseSchema = z.object({
|
|
263
263
|
items: z.array(ConsoleHandlerSchema),
|
|
264
264
|
});
|
|
265
|
+
const DEFAULT_REQUEST_EVENTS_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1000;
|
|
266
|
+
const DEFAULT_REQUEST_EVENTS_MAX_ROWS = 10_000;
|
|
267
|
+
const DEFAULT_OPERATION_EVENTS_MAX_AGE_MS = 30 * 24 * 60 * 60 * 1000;
|
|
268
|
+
const DEFAULT_OPERATION_EVENTS_MAX_ROWS = 5_000;
|
|
269
|
+
const DEFAULT_AUTO_EVENTS_PRUNE_INTERVAL_MS = 5 * 60 * 1000;
|
|
270
|
+
function readNonNegativeInteger(value, fallback) {
|
|
271
|
+
if (typeof value !== 'number' || !Number.isFinite(value)) {
|
|
272
|
+
return fallback;
|
|
273
|
+
}
|
|
274
|
+
if (value < 0) {
|
|
275
|
+
return fallback;
|
|
276
|
+
}
|
|
277
|
+
return Math.floor(value);
|
|
278
|
+
}
|
|
265
279
|
export function createConsoleRoutes(options) {
|
|
266
280
|
const routes = new Hono();
|
|
267
281
|
routes.onError((error, context) => {
|
|
@@ -275,6 +289,12 @@ export function createConsoleRoutes(options) {
|
|
|
275
289
|
const db = options.db;
|
|
276
290
|
const metricsAggregationMode = options.metrics?.aggregationMode ?? 'auto';
|
|
277
291
|
const rawFallbackMaxEvents = Math.max(1, options.metrics?.rawFallbackMaxEvents ?? 5000);
|
|
292
|
+
const requestEventsMaxAgeMs = readNonNegativeInteger(options.maintenance?.requestEventsMaxAgeMs, DEFAULT_REQUEST_EVENTS_MAX_AGE_MS);
|
|
293
|
+
const requestEventsMaxRows = readNonNegativeInteger(options.maintenance?.requestEventsMaxRows, DEFAULT_REQUEST_EVENTS_MAX_ROWS);
|
|
294
|
+
const operationEventsMaxAgeMs = readNonNegativeInteger(options.maintenance?.operationEventsMaxAgeMs, DEFAULT_OPERATION_EVENTS_MAX_AGE_MS);
|
|
295
|
+
const operationEventsMaxRows = readNonNegativeInteger(options.maintenance?.operationEventsMaxRows, DEFAULT_OPERATION_EVENTS_MAX_ROWS);
|
|
296
|
+
const autoEventsPruneIntervalMs = readNonNegativeInteger(options.maintenance?.autoPruneIntervalMs, DEFAULT_AUTO_EVENTS_PRUNE_INTERVAL_MS);
|
|
297
|
+
let lastEventsPruneRunAt = 0;
|
|
278
298
|
// Ensure console schema exists before handlers query console tables.
|
|
279
299
|
const consoleSchemaReadyPromise = (options.consoleSchemaReady ??
|
|
280
300
|
options.dialect.ensureConsoleSchema?.(options.db) ??
|
|
@@ -319,6 +339,12 @@ export function createConsoleRoutes(options) {
|
|
|
319
339
|
}
|
|
320
340
|
await next();
|
|
321
341
|
});
|
|
342
|
+
routes.use('*', async (c, next) => {
|
|
343
|
+
if (c.req.method !== 'OPTIONS') {
|
|
344
|
+
triggerAutomaticEventsPrune();
|
|
345
|
+
}
|
|
346
|
+
await next();
|
|
347
|
+
});
|
|
322
348
|
// Auth middleware
|
|
323
349
|
const requireAuth = async (c) => {
|
|
324
350
|
const auth = await options.authenticate(c);
|
|
@@ -414,6 +440,151 @@ export function createConsoleRoutes(options) {
|
|
|
414
440
|
.executeTakeFirst();
|
|
415
441
|
return Number(result?.numDeletedRows ?? 0);
|
|
416
442
|
};
|
|
443
|
+
const pruneRequestEventsByAge = async () => {
|
|
444
|
+
if (requestEventsMaxAgeMs <= 0) {
|
|
445
|
+
return 0;
|
|
446
|
+
}
|
|
447
|
+
const cutoffDate = new Date(Date.now() - requestEventsMaxAgeMs);
|
|
448
|
+
const result = await db
|
|
449
|
+
.deleteFrom('sync_request_events')
|
|
450
|
+
.where('created_at', '<', cutoffDate.toISOString())
|
|
451
|
+
.executeTakeFirst();
|
|
452
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
453
|
+
};
|
|
454
|
+
const pruneRequestEventsByCount = async () => {
|
|
455
|
+
if (requestEventsMaxRows <= 0) {
|
|
456
|
+
return 0;
|
|
457
|
+
}
|
|
458
|
+
const countRow = await db
|
|
459
|
+
.selectFrom('sync_request_events')
|
|
460
|
+
.select(({ fn }) => fn.countAll().as('total'))
|
|
461
|
+
.executeTakeFirst();
|
|
462
|
+
const total = coerceNumber(countRow?.total) ?? 0;
|
|
463
|
+
if (total <= requestEventsMaxRows) {
|
|
464
|
+
return 0;
|
|
465
|
+
}
|
|
466
|
+
const cutoffRow = await db
|
|
467
|
+
.selectFrom('sync_request_events')
|
|
468
|
+
.select(['event_id'])
|
|
469
|
+
.orderBy('event_id', 'desc')
|
|
470
|
+
.offset(requestEventsMaxRows)
|
|
471
|
+
.limit(1)
|
|
472
|
+
.executeTakeFirst();
|
|
473
|
+
const cutoffEventId = coerceNumber(cutoffRow?.event_id);
|
|
474
|
+
if (cutoffEventId === null) {
|
|
475
|
+
return 0;
|
|
476
|
+
}
|
|
477
|
+
const result = await db
|
|
478
|
+
.deleteFrom('sync_request_events')
|
|
479
|
+
.where('event_id', '<=', cutoffEventId)
|
|
480
|
+
.executeTakeFirst();
|
|
481
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
482
|
+
};
|
|
483
|
+
const pruneOperationEventsByAge = async () => {
|
|
484
|
+
if (operationEventsMaxAgeMs <= 0) {
|
|
485
|
+
return 0;
|
|
486
|
+
}
|
|
487
|
+
const cutoffDate = new Date(Date.now() - operationEventsMaxAgeMs);
|
|
488
|
+
const result = await db
|
|
489
|
+
.deleteFrom('sync_operation_events')
|
|
490
|
+
.where('created_at', '<', cutoffDate.toISOString())
|
|
491
|
+
.executeTakeFirst();
|
|
492
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
493
|
+
};
|
|
494
|
+
const pruneOperationEventsByCount = async () => {
|
|
495
|
+
if (operationEventsMaxRows <= 0) {
|
|
496
|
+
return 0;
|
|
497
|
+
}
|
|
498
|
+
const countRow = await db
|
|
499
|
+
.selectFrom('sync_operation_events')
|
|
500
|
+
.select(({ fn }) => fn.countAll().as('total'))
|
|
501
|
+
.executeTakeFirst();
|
|
502
|
+
const total = coerceNumber(countRow?.total) ?? 0;
|
|
503
|
+
if (total <= operationEventsMaxRows) {
|
|
504
|
+
return 0;
|
|
505
|
+
}
|
|
506
|
+
const cutoffRow = await db
|
|
507
|
+
.selectFrom('sync_operation_events')
|
|
508
|
+
.select(['operation_id'])
|
|
509
|
+
.orderBy('operation_id', 'desc')
|
|
510
|
+
.offset(operationEventsMaxRows)
|
|
511
|
+
.limit(1)
|
|
512
|
+
.executeTakeFirst();
|
|
513
|
+
const cutoffOperationId = coerceNumber(cutoffRow?.operation_id);
|
|
514
|
+
if (cutoffOperationId === null) {
|
|
515
|
+
return 0;
|
|
516
|
+
}
|
|
517
|
+
const result = await db
|
|
518
|
+
.deleteFrom('sync_operation_events')
|
|
519
|
+
.where('operation_id', '<=', cutoffOperationId)
|
|
520
|
+
.executeTakeFirst();
|
|
521
|
+
return Number(result?.numDeletedRows ?? 0);
|
|
522
|
+
};
|
|
523
|
+
const pruneConsoleEvents = async () => {
|
|
524
|
+
const requestEventsDeletedByAge = await pruneRequestEventsByAge();
|
|
525
|
+
const requestEventsDeletedByCount = await pruneRequestEventsByCount();
|
|
526
|
+
const requestEventsDeleted = requestEventsDeletedByAge + requestEventsDeletedByCount;
|
|
527
|
+
const operationEventsDeletedByAge = await pruneOperationEventsByAge();
|
|
528
|
+
const operationEventsDeletedByCount = await pruneOperationEventsByCount();
|
|
529
|
+
const operationEventsDeleted = operationEventsDeletedByAge + operationEventsDeletedByCount;
|
|
530
|
+
const payloadSnapshotsDeleted = await deleteUnreferencedPayloadSnapshots();
|
|
531
|
+
const totalDeleted = requestEventsDeleted + operationEventsDeleted;
|
|
532
|
+
return {
|
|
533
|
+
requestEventsDeleted,
|
|
534
|
+
operationEventsDeleted,
|
|
535
|
+
payloadSnapshotsDeleted,
|
|
536
|
+
totalDeleted,
|
|
537
|
+
};
|
|
538
|
+
};
|
|
539
|
+
let eventsPrunePromise = null;
|
|
540
|
+
const runEventsPrune = async () => {
|
|
541
|
+
if (eventsPrunePromise) {
|
|
542
|
+
return eventsPrunePromise;
|
|
543
|
+
}
|
|
544
|
+
let pending;
|
|
545
|
+
pending = pruneConsoleEvents()
|
|
546
|
+
.then((result) => {
|
|
547
|
+
lastEventsPruneRunAt = Date.now();
|
|
548
|
+
return result;
|
|
549
|
+
})
|
|
550
|
+
.finally(() => {
|
|
551
|
+
if (eventsPrunePromise === pending) {
|
|
552
|
+
eventsPrunePromise = null;
|
|
553
|
+
}
|
|
554
|
+
});
|
|
555
|
+
eventsPrunePromise = pending;
|
|
556
|
+
return pending;
|
|
557
|
+
};
|
|
558
|
+
const triggerAutomaticEventsPrune = () => {
|
|
559
|
+
if (autoEventsPruneIntervalMs <= 0) {
|
|
560
|
+
return;
|
|
561
|
+
}
|
|
562
|
+
if (eventsPrunePromise) {
|
|
563
|
+
return;
|
|
564
|
+
}
|
|
565
|
+
if (Date.now() - lastEventsPruneRunAt < autoEventsPruneIntervalMs) {
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
void runEventsPrune()
|
|
569
|
+
.then((result) => {
|
|
570
|
+
if (result.totalDeleted <= 0 && result.payloadSnapshotsDeleted <= 0) {
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
logSyncEvent({
|
|
574
|
+
event: 'console.prune_events_auto',
|
|
575
|
+
deletedCount: result.totalDeleted,
|
|
576
|
+
requestEventsDeleted: result.requestEventsDeleted,
|
|
577
|
+
operationEventsDeleted: result.operationEventsDeleted,
|
|
578
|
+
payloadDeletedCount: result.payloadSnapshotsDeleted,
|
|
579
|
+
});
|
|
580
|
+
})
|
|
581
|
+
.catch((error) => {
|
|
582
|
+
logSyncEvent({
|
|
583
|
+
event: 'console.prune_events_auto_failed',
|
|
584
|
+
error: error instanceof Error ? error.message : String(error),
|
|
585
|
+
});
|
|
586
|
+
});
|
|
587
|
+
};
|
|
417
588
|
const recordOperationEvent = async (event) => {
|
|
418
589
|
await db
|
|
419
590
|
.insertInto('sync_operation_events')
|
|
@@ -2095,47 +2266,15 @@ export function createConsoleRoutes(options) {
|
|
|
2095
2266
|
const auth = await requireAuth(c);
|
|
2096
2267
|
if (!auth)
|
|
2097
2268
|
return c.json({ error: 'UNAUTHENTICATED' }, 401);
|
|
2098
|
-
|
|
2099
|
-
const
|
|
2100
|
-
// Delete by date first
|
|
2101
|
-
const resByDate = await db
|
|
2102
|
-
.deleteFrom('sync_request_events')
|
|
2103
|
-
.where('created_at', '<', cutoffDate.toISOString())
|
|
2104
|
-
.executeTakeFirst();
|
|
2105
|
-
let deletedCount = Number(resByDate?.numDeletedRows ?? 0);
|
|
2106
|
-
// Then delete oldest if we still have more than 10000 events
|
|
2107
|
-
const countRow = await db
|
|
2108
|
-
.selectFrom('sync_request_events')
|
|
2109
|
-
.select(({ fn }) => fn.countAll().as('total'))
|
|
2110
|
-
.executeTakeFirst();
|
|
2111
|
-
const total = coerceNumber(countRow?.total) ?? 0;
|
|
2112
|
-
const maxEvents = 10000;
|
|
2113
|
-
if (total > maxEvents) {
|
|
2114
|
-
// Find event_id cutoff to keep only newest maxEvents
|
|
2115
|
-
const cutoffRow = await db
|
|
2116
|
-
.selectFrom('sync_request_events')
|
|
2117
|
-
.select(['event_id'])
|
|
2118
|
-
.orderBy('event_id', 'desc')
|
|
2119
|
-
.offset(maxEvents)
|
|
2120
|
-
.limit(1)
|
|
2121
|
-
.executeTakeFirst();
|
|
2122
|
-
if (cutoffRow) {
|
|
2123
|
-
const cutoffEventId = coerceNumber(cutoffRow.event_id);
|
|
2124
|
-
if (cutoffEventId !== null) {
|
|
2125
|
-
const resByCount = await db
|
|
2126
|
-
.deleteFrom('sync_request_events')
|
|
2127
|
-
.where('event_id', '<=', cutoffEventId)
|
|
2128
|
-
.executeTakeFirst();
|
|
2129
|
-
deletedCount += Number(resByCount?.numDeletedRows ?? 0);
|
|
2130
|
-
}
|
|
2131
|
-
}
|
|
2132
|
-
}
|
|
2133
|
-
const payloadDeletedCount = await deleteUnreferencedPayloadSnapshots();
|
|
2269
|
+
const pruneResult = await runEventsPrune();
|
|
2270
|
+
const deletedCount = pruneResult.totalDeleted;
|
|
2134
2271
|
logSyncEvent({
|
|
2135
2272
|
event: 'console.prune_events',
|
|
2136
2273
|
consoleUserId: auth.consoleUserId,
|
|
2137
2274
|
deletedCount,
|
|
2138
|
-
|
|
2275
|
+
requestEventsDeleted: pruneResult.requestEventsDeleted,
|
|
2276
|
+
operationEventsDeleted: pruneResult.operationEventsDeleted,
|
|
2277
|
+
payloadDeletedCount: pruneResult.payloadSnapshotsDeleted,
|
|
2139
2278
|
});
|
|
2140
2279
|
const result = { deletedCount };
|
|
2141
2280
|
return c.json(result, 200);
|