@syncular/server-hono 0.0.1-100

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 (77) 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 +80 -0
  22. package/dist/create-server.d.ts.map +1 -0
  23. package/dist/create-server.js +100 -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 +884 -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 +572 -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__/sync-rate-limit-routing.test.ts +181 -0
  63. package/src/__tests__/ws-connection-manager.test.ts +176 -0
  64. package/src/api-key-auth.ts +179 -0
  65. package/src/blobs.ts +534 -0
  66. package/src/console/index.ts +17 -0
  67. package/src/console/routes.ts +2155 -0
  68. package/src/console/schemas.ts +299 -0
  69. package/src/create-server.ts +186 -0
  70. package/src/index.ts +42 -0
  71. package/src/openapi.ts +74 -0
  72. package/src/proxy/connection-manager.ts +340 -0
  73. package/src/proxy/index.ts +8 -0
  74. package/src/proxy/routes.ts +223 -0
  75. package/src/rate-limit.ts +321 -0
  76. package/src/routes.ts +1305 -0
  77. package/src/ws.ts +789 -0
package/package.json ADDED
@@ -0,0 +1,73 @@
1
+ {
2
+ "name": "@syncular/server-hono",
3
+ "version": "0.0.1-100",
4
+ "description": "Hono adapter for the Syncular server with OpenAPI support",
5
+ "license": "MIT",
6
+ "author": "Benjamin Kniffler",
7
+ "homepage": "https://syncular.dev",
8
+ "repository": {
9
+ "type": "git",
10
+ "url": "https://github.com/syncular/syncular.git",
11
+ "directory": "packages/server-hono"
12
+ },
13
+ "bugs": {
14
+ "url": "https://github.com/syncular/syncular/issues"
15
+ },
16
+ "keywords": [
17
+ "sync",
18
+ "offline-first",
19
+ "realtime",
20
+ "database",
21
+ "typescript",
22
+ "hono",
23
+ "openapi"
24
+ ],
25
+ "private": false,
26
+ "publishConfig": {
27
+ "access": "public"
28
+ },
29
+ "type": "module",
30
+ "exports": {
31
+ ".": {
32
+ "bun": "./src/index.ts",
33
+ "import": {
34
+ "types": "./dist/index.d.ts",
35
+ "default": "./dist/index.js"
36
+ }
37
+ }
38
+ },
39
+ "scripts": {
40
+ "test": "bun test src/__tests__",
41
+ "tsgo": "tsgo --noEmit",
42
+ "generate": "bun generate:openapi",
43
+ "generate:openapi": "bun scripts/generate-openapi.ts",
44
+ "build": "tsgo",
45
+ "release": "bunx syncular-publish"
46
+ },
47
+ "dependencies": {
48
+ "@hono/standard-validator": "^0.2.2",
49
+ "@standard-community/standard-json": "^0.3.5",
50
+ "@standard-community/standard-openapi": "^0.2.9",
51
+ "@syncular/core": "0.0.1",
52
+ "@syncular/server": "0.0.1",
53
+ "@types/json-schema": "^7.0.15",
54
+ "hono-openapi": "^1.2.0",
55
+ "openapi-types": "^12.1.3"
56
+ },
57
+ "devDependencies": {
58
+ "@syncular/config": "0.0.0",
59
+ "@syncular/dialect-pglite": "0.0.1",
60
+ "@syncular/server-dialect-postgres": "0.0.1",
61
+ "kysely": "*",
62
+ "zod": "*"
63
+ },
64
+ "peerDependencies": {
65
+ "hono": "^4.0.0",
66
+ "kysely": "^0.28.0",
67
+ "zod": "^4.0.0"
68
+ },
69
+ "files": [
70
+ "dist",
71
+ "src"
72
+ ]
73
+ }
@@ -0,0 +1,187 @@
1
+ import { afterEach, beforeEach, describe, expect, it } from 'bun:test';
2
+ import { createPgliteDb } from '@syncular/dialect-pglite';
3
+ import {
4
+ createServerHandler,
5
+ ensureSyncSchema,
6
+ type SyncCoreDb,
7
+ } from '@syncular/server';
8
+ import { createPostgresServerDialect } from '@syncular/server-dialect-postgres';
9
+ import { Hono } from 'hono';
10
+ import { defineWebSocketHelper } from 'hono/ws';
11
+ import type { Kysely } from 'kysely';
12
+ import { createSyncServer } from '../create-server';
13
+ import { getSyncWebSocketConnectionManager } from '../routes';
14
+ import type { WebSocketConnection } from '../ws';
15
+
16
+ interface TasksTable {
17
+ id: string;
18
+ user_id: string;
19
+ title: string;
20
+ server_version: number;
21
+ }
22
+
23
+ interface ServerDb extends SyncCoreDb {
24
+ tasks: TasksTable;
25
+ }
26
+
27
+ interface ClientDb {
28
+ tasks: TasksTable;
29
+ }
30
+
31
+ describe('createSyncServer console configuration', () => {
32
+ let db: Kysely<ServerDb>;
33
+ let previousConsoleToken: string | undefined;
34
+
35
+ beforeEach(async () => {
36
+ db = createPgliteDb<ServerDb>();
37
+ await ensureSyncSchema(db, createPostgresServerDialect());
38
+ previousConsoleToken = process.env.SYNC_CONSOLE_TOKEN;
39
+ delete process.env.SYNC_CONSOLE_TOKEN;
40
+ });
41
+
42
+ afterEach(async () => {
43
+ if (previousConsoleToken === undefined) {
44
+ delete process.env.SYNC_CONSOLE_TOKEN;
45
+ } else {
46
+ process.env.SYNC_CONSOLE_TOKEN = previousConsoleToken;
47
+ }
48
+ await db.destroy();
49
+ });
50
+
51
+ function createTestHandler() {
52
+ return createServerHandler<ServerDb, ClientDb, 'tasks'>({
53
+ table: 'tasks',
54
+ scopes: ['user:{user_id}'],
55
+ resolveScopes: async (ctx) => ({ user_id: [ctx.actorId] }),
56
+ });
57
+ }
58
+
59
+ function createOptions() {
60
+ return {
61
+ db,
62
+ dialect: createPostgresServerDialect(),
63
+ handlers: [createTestHandler()],
64
+ authenticate: async () => ({ actorId: 'u1' }),
65
+ };
66
+ }
67
+
68
+ function createConn(args: {
69
+ actorId: string;
70
+ clientId: string;
71
+ }): WebSocketConnection {
72
+ return {
73
+ actorId: args.actorId,
74
+ clientId: args.clientId,
75
+ transportPath: 'direct',
76
+ get isOpen() {
77
+ return true;
78
+ },
79
+ sendSync() {},
80
+ sendHeartbeat() {},
81
+ sendPresence() {},
82
+ sendError() {},
83
+ close() {},
84
+ };
85
+ }
86
+
87
+ it('keeps console routes disabled when console config is omitted', () => {
88
+ const server = createSyncServer(createOptions());
89
+ expect(server.consoleRoutes).toBeUndefined();
90
+ });
91
+
92
+ it('throws when console is enabled without a token', () => {
93
+ const options = createOptions();
94
+ expect(() =>
95
+ createSyncServer({
96
+ ...options,
97
+ console: {},
98
+ })
99
+ ).toThrow(
100
+ 'Console is enabled but no token is configured. Set `console.token` or SYNC_CONSOLE_TOKEN.'
101
+ );
102
+ });
103
+
104
+ it('accepts SYNC_CONSOLE_TOKEN when console token is omitted', () => {
105
+ process.env.SYNC_CONSOLE_TOKEN = 'env-token';
106
+ const options = createOptions();
107
+ const server = createSyncServer({
108
+ ...options,
109
+ console: {},
110
+ });
111
+ expect(server.consoleRoutes).toBeDefined();
112
+ });
113
+
114
+ it('accepts an explicit console token', () => {
115
+ process.env.SYNC_CONSOLE_TOKEN = 'env-token';
116
+ const options = createOptions();
117
+ const server = createSyncServer({
118
+ ...options,
119
+ console: { token: 'explicit-token' },
120
+ });
121
+ expect(server.consoleRoutes).toBeDefined();
122
+ });
123
+
124
+ it('forwards maxConnectionsPerClient from factory to realtime route', async () => {
125
+ const options = createOptions();
126
+ const upgradeWebSocket = defineWebSocketHelper(async () => {});
127
+
128
+ const server = createSyncServer({
129
+ ...options,
130
+ upgradeWebSocket,
131
+ sync: {
132
+ websocket: {
133
+ maxConnectionsPerClient: 1,
134
+ },
135
+ },
136
+ });
137
+
138
+ const manager = getSyncWebSocketConnectionManager(server.syncRoutes);
139
+ if (!manager) {
140
+ throw new Error('Expected websocket manager to be enabled.');
141
+ }
142
+ manager.register(createConn({ actorId: 'u1', clientId: 'client-1' }), []);
143
+
144
+ const app = new Hono();
145
+ app.route('/sync', server.syncRoutes);
146
+
147
+ const response = await app.request(
148
+ 'http://localhost/sync/realtime?clientId=client-1'
149
+ );
150
+ expect(response.status).toBe(429);
151
+ expect(await response.json()).toEqual({
152
+ error: 'WEBSOCKET_CONNECTION_LIMIT_CLIENT',
153
+ });
154
+ });
155
+
156
+ it('forwards maxConnectionsTotal from factory to realtime route', async () => {
157
+ const options = createOptions();
158
+ const upgradeWebSocket = defineWebSocketHelper(async () => {});
159
+
160
+ const server = createSyncServer({
161
+ ...options,
162
+ upgradeWebSocket,
163
+ sync: {
164
+ websocket: {
165
+ maxConnectionsTotal: 1,
166
+ },
167
+ },
168
+ });
169
+
170
+ const manager = getSyncWebSocketConnectionManager(server.syncRoutes);
171
+ if (!manager) {
172
+ throw new Error('Expected websocket manager to be enabled.');
173
+ }
174
+ manager.register(createConn({ actorId: 'u1', clientId: 'client-1' }), []);
175
+
176
+ const app = new Hono();
177
+ app.route('/sync', server.syncRoutes);
178
+
179
+ const response = await app.request(
180
+ 'http://localhost/sync/realtime?clientId=client-2'
181
+ );
182
+ expect(response.status).toBe(429);
183
+ expect(await response.json()).toEqual({
184
+ error: 'WEBSOCKET_CONNECTION_LIMIT_TOTAL',
185
+ });
186
+ });
187
+ });