@syncular/server-hono 0.0.1-60

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.
Files changed (76) hide show
  1. package/dist/api-key-auth.d.ts +49 -0
  2. package/dist/api-key-auth.d.ts.map +1 -0
  3. package/dist/api-key-auth.js +110 -0
  4. package/dist/api-key-auth.js.map +1 -0
  5. package/dist/blobs.d.ts +69 -0
  6. package/dist/blobs.d.ts.map +1 -0
  7. package/dist/blobs.js +383 -0
  8. package/dist/blobs.js.map +1 -0
  9. package/dist/console/index.d.ts +8 -0
  10. package/dist/console/index.d.ts.map +1 -0
  11. package/dist/console/index.js +7 -0
  12. package/dist/console/index.js.map +1 -0
  13. package/dist/console/routes.d.ts +106 -0
  14. package/dist/console/routes.d.ts.map +1 -0
  15. package/dist/console/routes.js +1612 -0
  16. package/dist/console/routes.js.map +1 -0
  17. package/dist/console/schemas.d.ts +308 -0
  18. package/dist/console/schemas.d.ts.map +1 -0
  19. package/dist/console/schemas.js +201 -0
  20. package/dist/console/schemas.js.map +1 -0
  21. package/dist/create-server.d.ts +78 -0
  22. package/dist/create-server.d.ts.map +1 -0
  23. package/dist/create-server.js +99 -0
  24. package/dist/create-server.js.map +1 -0
  25. package/dist/index.d.ts +16 -0
  26. package/dist/index.d.ts.map +1 -0
  27. package/dist/index.js +25 -0
  28. package/dist/index.js.map +1 -0
  29. package/dist/openapi.d.ts +45 -0
  30. package/dist/openapi.d.ts.map +1 -0
  31. package/dist/openapi.js +59 -0
  32. package/dist/openapi.js.map +1 -0
  33. package/dist/proxy/connection-manager.d.ts +78 -0
  34. package/dist/proxy/connection-manager.d.ts.map +1 -0
  35. package/dist/proxy/connection-manager.js +251 -0
  36. package/dist/proxy/connection-manager.js.map +1 -0
  37. package/dist/proxy/index.d.ts +8 -0
  38. package/dist/proxy/index.d.ts.map +1 -0
  39. package/dist/proxy/index.js +8 -0
  40. package/dist/proxy/index.js.map +1 -0
  41. package/dist/proxy/routes.d.ts +74 -0
  42. package/dist/proxy/routes.d.ts.map +1 -0
  43. package/dist/proxy/routes.js +147 -0
  44. package/dist/proxy/routes.js.map +1 -0
  45. package/dist/rate-limit.d.ts +101 -0
  46. package/dist/rate-limit.d.ts.map +1 -0
  47. package/dist/rate-limit.js +186 -0
  48. package/dist/rate-limit.js.map +1 -0
  49. package/dist/routes.d.ts +126 -0
  50. package/dist/routes.d.ts.map +1 -0
  51. package/dist/routes.js +788 -0
  52. package/dist/routes.js.map +1 -0
  53. package/dist/ws.d.ts +230 -0
  54. package/dist/ws.d.ts.map +1 -0
  55. package/dist/ws.js +601 -0
  56. package/dist/ws.js.map +1 -0
  57. package/package.json +73 -0
  58. package/src/__tests__/create-server.test.ts +187 -0
  59. package/src/__tests__/pull-chunk-storage.test.ts +189 -0
  60. package/src/__tests__/rate-limit.test.ts +78 -0
  61. package/src/__tests__/realtime-bridge.test.ts +131 -0
  62. package/src/__tests__/ws-connection-manager.test.ts +176 -0
  63. package/src/api-key-auth.ts +179 -0
  64. package/src/blobs.ts +534 -0
  65. package/src/console/index.ts +17 -0
  66. package/src/console/routes.ts +2155 -0
  67. package/src/console/schemas.ts +299 -0
  68. package/src/create-server.ts +180 -0
  69. package/src/index.ts +42 -0
  70. package/src/openapi.ts +74 -0
  71. package/src/proxy/connection-manager.ts +340 -0
  72. package/src/proxy/index.ts +8 -0
  73. package/src/proxy/routes.ts +223 -0
  74. package/src/rate-limit.ts +321 -0
  75. package/src/routes.ts +1186 -0
  76. package/src/ws.ts +789 -0
@@ -0,0 +1,299 @@
1
+ /**
2
+ * @syncular/server-hono - Console API Zod schemas
3
+ */
4
+
5
+ import { z } from 'zod';
6
+
7
+ // ============================================================================
8
+ // Stats Schema
9
+ // ============================================================================
10
+
11
+ export const SyncStatsSchema = z.object({
12
+ commitCount: z.number().int(),
13
+ changeCount: z.number().int(),
14
+ minCommitSeq: z.number().int(),
15
+ maxCommitSeq: z.number().int(),
16
+ clientCount: z.number().int(),
17
+ activeClientCount: z.number().int(),
18
+ minActiveClientCursor: z.number().int().nullable(),
19
+ maxActiveClientCursor: z.number().int().nullable(),
20
+ });
21
+
22
+ export type SyncStats = z.infer<typeof SyncStatsSchema>;
23
+
24
+ // ============================================================================
25
+ // Commit Schemas
26
+ // ============================================================================
27
+
28
+ export const ConsoleCommitListItemSchema = z.object({
29
+ commitSeq: z.number().int(),
30
+ actorId: z.string(),
31
+ clientId: z.string(),
32
+ clientCommitId: z.string(),
33
+ createdAt: z.string(),
34
+ changeCount: z.number().int(),
35
+ affectedTables: z.array(z.string()),
36
+ });
37
+
38
+ export type ConsoleCommitListItem = z.infer<typeof ConsoleCommitListItemSchema>;
39
+
40
+ export const ConsoleChangeSchema = z.object({
41
+ changeId: z.number().int(),
42
+ table: z.string(),
43
+ rowId: z.string(),
44
+ op: z.enum(['upsert', 'delete']),
45
+ rowJson: z.unknown().nullable(),
46
+ rowVersion: z.number().int().nullable(),
47
+ scopes: z.record(z.string(), z.unknown()),
48
+ });
49
+
50
+ export type ConsoleChange = z.infer<typeof ConsoleChangeSchema>;
51
+
52
+ export const ConsoleCommitDetailSchema = ConsoleCommitListItemSchema.extend({
53
+ changes: z.array(ConsoleChangeSchema),
54
+ });
55
+
56
+ export type ConsoleCommitDetail = z.infer<typeof ConsoleCommitDetailSchema>;
57
+
58
+ // ============================================================================
59
+ // Client Schemas
60
+ // ============================================================================
61
+
62
+ export const ConsoleClientSchema = z.object({
63
+ clientId: z.string(),
64
+ actorId: z.string(),
65
+ cursor: z.number().int(),
66
+ lagCommitCount: z.number().int().nonnegative(),
67
+ connectionPath: z.enum(['direct', 'relay']),
68
+ connectionMode: z.enum(['polling', 'realtime']),
69
+ realtimeConnectionCount: z.number().int().nonnegative(),
70
+ isRealtimeConnected: z.boolean(),
71
+ activityState: z.enum(['active', 'idle', 'stale']),
72
+ lastRequestAt: z.string().nullable(),
73
+ lastRequestType: z.enum(['push', 'pull']).nullable(),
74
+ lastRequestOutcome: z.string().nullable(),
75
+ effectiveScopes: z.record(z.string(), z.unknown()),
76
+ updatedAt: z.string(),
77
+ });
78
+
79
+ export type ConsoleClient = z.infer<typeof ConsoleClientSchema>;
80
+
81
+ // ============================================================================
82
+ // Handler Schemas
83
+ // ============================================================================
84
+
85
+ export const ConsoleHandlerSchema = z.object({
86
+ table: z.string(),
87
+ dependsOn: z.array(z.string()).optional(),
88
+ snapshotChunkTtlMs: z.number().int().optional(),
89
+ });
90
+
91
+ export type ConsoleHandler = z.infer<typeof ConsoleHandlerSchema>;
92
+
93
+ // ============================================================================
94
+ // Prune & Compact Schemas
95
+ // ============================================================================
96
+
97
+ export const ConsolePrunePreviewSchema = z.object({
98
+ watermarkCommitSeq: z.number().int(),
99
+ commitsToDelete: z.number().int(),
100
+ });
101
+
102
+ export type ConsolePrunePreview = z.infer<typeof ConsolePrunePreviewSchema>;
103
+
104
+ export const ConsolePruneResultSchema = z.object({
105
+ deletedCommits: z.number().int(),
106
+ });
107
+
108
+ export type ConsolePruneResult = z.infer<typeof ConsolePruneResultSchema>;
109
+
110
+ export const ConsoleCompactResultSchema = z.object({
111
+ deletedChanges: z.number().int(),
112
+ });
113
+
114
+ export type ConsoleCompactResult = z.infer<typeof ConsoleCompactResultSchema>;
115
+
116
+ // ============================================================================
117
+ // Evict Schema
118
+ // ============================================================================
119
+
120
+ export const ConsoleEvictResultSchema = z.object({
121
+ evicted: z.boolean(),
122
+ });
123
+
124
+ export type ConsoleEvictResult = z.infer<typeof ConsoleEvictResultSchema>;
125
+
126
+ // ============================================================================
127
+ // Request Event Schemas
128
+ // ============================================================================
129
+
130
+ export const ConsoleRequestEventSchema = z.object({
131
+ eventId: z.number().int(),
132
+ eventType: z.enum(['push', 'pull']),
133
+ transportPath: z.enum(['direct', 'relay']),
134
+ actorId: z.string(),
135
+ clientId: z.string(),
136
+ statusCode: z.number().int(),
137
+ outcome: z.string(),
138
+ durationMs: z.number().int(),
139
+ commitSeq: z.number().int().nullable(),
140
+ operationCount: z.number().int().nullable(),
141
+ rowCount: z.number().int().nullable(),
142
+ tables: z.array(z.string()),
143
+ errorMessage: z.string().nullable(),
144
+ createdAt: z.string(),
145
+ });
146
+
147
+ export type ConsoleRequestEvent = z.infer<typeof ConsoleRequestEventSchema>;
148
+
149
+ export const ConsoleClearEventsResultSchema = z.object({
150
+ deletedCount: z.number().int(),
151
+ });
152
+
153
+ export type ConsoleClearEventsResult = z.infer<
154
+ typeof ConsoleClearEventsResultSchema
155
+ >;
156
+
157
+ export const ConsolePruneEventsResultSchema = z.object({
158
+ deletedCount: z.number().int(),
159
+ });
160
+
161
+ export type ConsolePruneEventsResult = z.infer<
162
+ typeof ConsolePruneEventsResultSchema
163
+ >;
164
+
165
+ // ============================================================================
166
+ // API Key Schemas
167
+ // ============================================================================
168
+
169
+ export const ApiKeyTypeSchema = z.enum(['relay', 'proxy', 'admin']);
170
+ export type ApiKeyType = z.infer<typeof ApiKeyTypeSchema>;
171
+
172
+ export const ConsoleApiKeySchema = z.object({
173
+ keyId: z.string(),
174
+ keyPrefix: z.string(),
175
+ name: z.string(),
176
+ keyType: ApiKeyTypeSchema,
177
+ scopeKeys: z.array(z.string()),
178
+ actorId: z.string().nullable(),
179
+ createdAt: z.string(),
180
+ expiresAt: z.string().nullable(),
181
+ lastUsedAt: z.string().nullable(),
182
+ revokedAt: z.string().nullable(),
183
+ });
184
+
185
+ export type ConsoleApiKey = z.infer<typeof ConsoleApiKeySchema>;
186
+
187
+ export const ConsoleApiKeyCreateRequestSchema = z.object({
188
+ name: z.string().min(1),
189
+ keyType: ApiKeyTypeSchema,
190
+ scopeKeys: z.array(z.string()).optional(),
191
+ actorId: z.string().optional(),
192
+ expiresInDays: z.number().int().positive().optional(),
193
+ });
194
+
195
+ export const ConsoleApiKeyCreateResponseSchema = z.object({
196
+ key: ConsoleApiKeySchema,
197
+ secretKey: z.string(),
198
+ });
199
+
200
+ export type ConsoleApiKeyCreateResponse = z.infer<
201
+ typeof ConsoleApiKeyCreateResponseSchema
202
+ >;
203
+
204
+ export const ConsoleApiKeyRevokeResponseSchema = z.object({
205
+ revoked: z.boolean(),
206
+ });
207
+
208
+ // ============================================================================
209
+ // Pagination Schemas (Console-specific)
210
+ // ============================================================================
211
+
212
+ export const ConsolePaginationQuerySchema = z.object({
213
+ limit: z.coerce.number().int().min(1).max(100).default(50),
214
+ offset: z.coerce.number().int().min(0).default(0),
215
+ });
216
+
217
+ export const ConsolePaginatedResponseSchema = <T extends z.ZodTypeAny>(
218
+ itemSchema: T
219
+ ) =>
220
+ z.object({
221
+ items: z.array(itemSchema),
222
+ total: z.number().int(),
223
+ offset: z.number().int(),
224
+ limit: z.number().int(),
225
+ });
226
+
227
+ export type ConsolePaginatedResponse<T> = {
228
+ items: T[];
229
+ total: number;
230
+ offset: number;
231
+ limit: number;
232
+ };
233
+
234
+ // ============================================================================
235
+ // Time-Series Stats Schemas
236
+ // ============================================================================
237
+
238
+ const TimeseriesIntervalSchema = z.enum(['minute', 'hour', 'day']);
239
+ const TimeseriesRangeSchema = z.enum(['1h', '6h', '24h', '7d', '30d']);
240
+ export const TimeseriesQuerySchema = z.object({
241
+ interval: TimeseriesIntervalSchema.default('hour'),
242
+ range: TimeseriesRangeSchema.default('24h'),
243
+ });
244
+
245
+ export const TimeseriesBucketSchema = z.object({
246
+ timestamp: z.string(),
247
+ pushCount: z.number().int(),
248
+ pullCount: z.number().int(),
249
+ errorCount: z.number().int(),
250
+ avgLatencyMs: z.number(),
251
+ });
252
+
253
+ export type TimeseriesBucket = z.infer<typeof TimeseriesBucketSchema>;
254
+
255
+ export const TimeseriesStatsResponseSchema = z.object({
256
+ buckets: z.array(TimeseriesBucketSchema),
257
+ interval: TimeseriesIntervalSchema,
258
+ range: TimeseriesRangeSchema,
259
+ });
260
+
261
+ export type TimeseriesStatsResponse = z.infer<
262
+ typeof TimeseriesStatsResponseSchema
263
+ >;
264
+
265
+ // ============================================================================
266
+ // Latency Percentiles Schemas
267
+ // ============================================================================
268
+
269
+ export const LatencyPercentilesSchema = z.object({
270
+ p50: z.number(),
271
+ p90: z.number(),
272
+ p99: z.number(),
273
+ });
274
+
275
+ export type LatencyPercentiles = z.infer<typeof LatencyPercentilesSchema>;
276
+
277
+ export const LatencyStatsResponseSchema = z.object({
278
+ push: LatencyPercentilesSchema,
279
+ pull: LatencyPercentilesSchema,
280
+ range: TimeseriesRangeSchema,
281
+ });
282
+
283
+ export type LatencyStatsResponse = z.infer<typeof LatencyStatsResponseSchema>;
284
+
285
+ export const LatencyQuerySchema = z.object({
286
+ range: TimeseriesRangeSchema.default('24h'),
287
+ });
288
+
289
+ // ============================================================================
290
+ // Live Events Schemas (for WebSocket)
291
+ // ============================================================================
292
+
293
+ export const LiveEventSchema = z.object({
294
+ type: z.enum(['push', 'pull', 'commit', 'client_update']),
295
+ timestamp: z.string(),
296
+ data: z.record(z.string(), z.unknown()),
297
+ });
298
+
299
+ export type LiveEvent = z.infer<typeof LiveEventSchema>;
@@ -0,0 +1,180 @@
1
+ /**
2
+ * Simplified server factory for Hono
3
+ *
4
+ * Breaking changes from legacy createSyncRoutes:
5
+ * - handlers: array instead of TableRegistry
6
+ * - Combined sync + console routes in one call
7
+ */
8
+
9
+ import type {
10
+ ServerSyncDialect,
11
+ ServerTableHandler,
12
+ SyncCoreDb,
13
+ } from '@syncular/server';
14
+ import type { Context } from 'hono';
15
+ import type { UpgradeWebSocket } from 'hono/ws';
16
+ import type { Kysely } from 'kysely';
17
+ import {
18
+ type ConsoleEventEmitter,
19
+ createConsoleEventEmitter,
20
+ createConsoleRoutes,
21
+ createTokenAuthenticator,
22
+ } from './console';
23
+ import {
24
+ createSyncRoutes,
25
+ getSyncWebSocketConnectionManager,
26
+ type SyncAuthResult,
27
+ type SyncRoutesConfigWithRateLimit,
28
+ } from './routes';
29
+
30
+ export interface SyncServerOptions<DB extends SyncCoreDb = SyncCoreDb> {
31
+ /** Kysely database instance */
32
+ db: Kysely<DB>;
33
+
34
+ /** Server sync dialect */
35
+ dialect: ServerSyncDialect;
36
+
37
+ /**
38
+ * Table handlers for sync operations.
39
+ */
40
+ handlers: ServerTableHandler<DB>[];
41
+
42
+ /** Authentication function - returns actorId or null for unauthenticated */
43
+ authenticate: (c: Context) => Promise<SyncAuthResult | null>;
44
+
45
+ /** Sync route configuration */
46
+ sync?: SyncRoutesConfigWithRateLimit;
47
+
48
+ /** WebSocket upgrader for realtime */
49
+ upgradeWebSocket?: UpgradeWebSocket;
50
+
51
+ /**
52
+ * Console configuration for dashboard/monitoring.
53
+ * Omit or set to false to disable console routes.
54
+ */
55
+ console?:
56
+ | false
57
+ | {
58
+ /** Console bearer token for authentication (required unless SYNC_CONSOLE_TOKEN is set) */
59
+ token?: string;
60
+ /** CORS origins (defaults to '*') */
61
+ corsOrigins?: '*' | string[];
62
+ };
63
+ }
64
+
65
+ export interface SyncServerResult {
66
+ /** Sync routes for Hono */
67
+ syncRoutes: ReturnType<typeof createSyncRoutes>;
68
+ /** Console routes for Hono (if enabled) */
69
+ consoleRoutes?: ReturnType<typeof createConsoleRoutes>;
70
+ /** Console event emitter (if console enabled) */
71
+ consoleEventEmitter?: ConsoleEventEmitter;
72
+ }
73
+
74
+ /**
75
+ * Create a simplified sync server with sync and optional console routes.
76
+ *
77
+ * @example
78
+ * ```typescript
79
+ * // With handlers
80
+ * const { syncRoutes } = createSyncServer({
81
+ * db,
82
+ * dialect,
83
+ * handlers: [tasksHandler, notesHandler],
84
+ * authenticate: async (c) => {
85
+ * const userId = c.req.header('x-user-id');
86
+ * return userId ? { actorId: userId } : null;
87
+ * },
88
+ * });
89
+ *
90
+ * // With custom handlers
91
+ * const { syncRoutes, consoleRoutes } = createSyncServer({
92
+ * db,
93
+ * dialect,
94
+ * handlers: [tasksHandler, notesHandler],
95
+ * authenticate: async (c) => {
96
+ * const userId = c.req.header('x-user-id');
97
+ * return userId ? { actorId: userId } : null;
98
+ * },
99
+ * console: { token: process.env.CONSOLE_TOKEN },
100
+ * });
101
+ * ```
102
+ */
103
+ export function createSyncServer<DB extends SyncCoreDb = SyncCoreDb>(
104
+ options: SyncServerOptions<DB>
105
+ ): SyncServerResult {
106
+ const {
107
+ db,
108
+ dialect,
109
+ handlers,
110
+ authenticate,
111
+ sync,
112
+ upgradeWebSocket,
113
+ console: consoleConfig,
114
+ } = options;
115
+
116
+ if (handlers.length === 0) {
117
+ throw new Error('At least one handler must be provided');
118
+ }
119
+
120
+ // Create sync routes
121
+ const syncRoutes = createSyncRoutes({
122
+ db,
123
+ dialect,
124
+ handlers,
125
+ authenticate,
126
+ sync: {
127
+ ...sync,
128
+ websocket: upgradeWebSocket
129
+ ? {
130
+ enabled: true,
131
+ upgradeWebSocket,
132
+ ...(sync?.websocket?.heartbeatIntervalMs !== undefined && {
133
+ heartbeatIntervalMs: sync.websocket.heartbeatIntervalMs,
134
+ }),
135
+ ...(sync?.websocket?.maxConnectionsTotal !== undefined && {
136
+ maxConnectionsTotal: sync.websocket.maxConnectionsTotal,
137
+ }),
138
+ ...(sync?.websocket?.maxConnectionsPerClient !== undefined && {
139
+ maxConnectionsPerClient: sync.websocket.maxConnectionsPerClient,
140
+ }),
141
+ }
142
+ : { enabled: false },
143
+ },
144
+ });
145
+
146
+ // Console is opt-in; disable unless explicitly configured.
147
+ if (!consoleConfig) {
148
+ return { syncRoutes };
149
+ }
150
+
151
+ const consoleToken = consoleConfig.token ?? process.env.SYNC_CONSOLE_TOKEN;
152
+ if (!consoleToken) {
153
+ throw new Error(
154
+ 'Console is enabled but no token is configured. Set `console.token` or SYNC_CONSOLE_TOKEN.'
155
+ );
156
+ }
157
+ const consoleEventEmitter = createConsoleEventEmitter();
158
+
159
+ const consoleRoutes = createConsoleRoutes({
160
+ db,
161
+ dialect,
162
+ handlers,
163
+ authenticate: createTokenAuthenticator(consoleToken),
164
+ corsOrigins: consoleConfig.corsOrigins ?? '*',
165
+ eventEmitter: consoleEventEmitter,
166
+ wsConnectionManager: getSyncWebSocketConnectionManager(syncRoutes),
167
+ ...(upgradeWebSocket && {
168
+ websocket: {
169
+ enabled: true,
170
+ upgradeWebSocket,
171
+ },
172
+ }),
173
+ });
174
+
175
+ return {
176
+ syncRoutes,
177
+ consoleRoutes,
178
+ consoleEventEmitter,
179
+ };
180
+ }
package/src/index.ts ADDED
@@ -0,0 +1,42 @@
1
+ /**
2
+ * @syncular/server-hono - Hono adapter for sync infrastructure
3
+ *
4
+ * This package provides Hono-specific routes for @syncular/server.
5
+ * Keeps @syncular/server framework-agnostic.
6
+ */
7
+
8
+ // API Key Auth
9
+ export * from './api-key-auth';
10
+
11
+ // Blob routes
12
+ export { type CreateBlobRoutesOptions, createBlobRoutes } from './blobs';
13
+
14
+ // Console
15
+ export * from './console';
16
+
17
+ // Simplified server factory
18
+ export {
19
+ createSyncServer,
20
+ type SyncServerOptions,
21
+ type SyncServerResult,
22
+ } from './create-server';
23
+
24
+ // OpenAPI utilities
25
+ export * from './openapi';
26
+
27
+ // Proxy
28
+ export * from './proxy';
29
+
30
+ // Rate limiting
31
+ export * from './rate-limit';
32
+
33
+ // Route types and factory
34
+ export {
35
+ type CreateSyncRoutesOptions,
36
+ createSyncRoutes,
37
+ getSyncRealtimeUnsubscribe,
38
+ getSyncWebSocketConnectionManager,
39
+ } from './routes';
40
+
41
+ // WebSocket helpers for realtime sync
42
+ export * from './ws';
package/src/openapi.ts ADDED
@@ -0,0 +1,74 @@
1
+ /**
2
+ * @syncular/server-hono - OpenAPI Spec Export
3
+ *
4
+ * Provides utilities for generating and serving OpenAPI specifications.
5
+ */
6
+
7
+ import type { Hono } from 'hono';
8
+ import { generateSpecs, openAPIRouteHandler } from 'hono-openapi';
9
+
10
+ interface OpenAPIConfig {
11
+ title?: string;
12
+ version?: string;
13
+ description?: string;
14
+ servers?: Array<{ url: string; description?: string }>;
15
+ }
16
+
17
+ /**
18
+ * Create an OpenAPI spec handler that can be used with Hono routes.
19
+ *
20
+ * @example
21
+ * ```typescript
22
+ * import { Hono } from 'hono';
23
+ * import { createSyncRoutes, createConsoleRoutes, createOpenAPIHandler } from '@syncular/server-hono';
24
+ *
25
+ * const app = new Hono();
26
+ * const syncRoutes = createSyncRoutes({ ... });
27
+ * const consoleRoutes = createConsoleRoutes({ ... });
28
+ *
29
+ * app.route('/sync', syncRoutes);
30
+ * app.route('/console', consoleRoutes);
31
+ *
32
+ * // Add OpenAPI spec endpoint
33
+ * app.get('/openapi.json', createOpenAPIHandler(app, {
34
+ * title: 'Syncular API',
35
+ * version: '1.0.0',
36
+ * }));
37
+ * ```
38
+ */
39
+ export function createOpenAPIHandler(app: Hono, config: OpenAPIConfig = {}) {
40
+ return openAPIRouteHandler(app, {
41
+ documentation: {
42
+ info: {
43
+ title: config.title ?? 'Syncular API',
44
+ version: config.version ?? '1.0.0',
45
+ description:
46
+ config.description ??
47
+ 'Sync infrastructure API for real-time data synchronization',
48
+ },
49
+ servers: config.servers,
50
+ },
51
+ });
52
+ }
53
+
54
+ /**
55
+ * Generate OpenAPI document from a Hono app instance.
56
+ * This is useful for build-time spec generation.
57
+ */
58
+ export async function generateOpenAPIDocument(
59
+ app: Hono,
60
+ config: OpenAPIConfig = {}
61
+ ): Promise<unknown> {
62
+ return generateSpecs(app, {
63
+ documentation: {
64
+ info: {
65
+ title: config.title ?? 'Syncular API',
66
+ version: config.version ?? '1.0.0',
67
+ description:
68
+ config.description ??
69
+ 'Sync infrastructure API for real-time data synchronization',
70
+ },
71
+ servers: config.servers,
72
+ },
73
+ });
74
+ }