@syncular/server-hono 0.15.45 → 0.15.47
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/admin.d.ts +2 -2
- package/dist/admin.js +28 -2
- package/dist/index.d.ts +4 -3
- package/dist/index.js +20 -3
- package/package.json +4 -4
- package/src/admin.ts +29 -2
- package/src/index.ts +30 -2
package/dist/admin.d.ts
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* refuses to mount without a host-provided `authorize` guard (it throws at
|
|
8
8
|
* construction). Every request runs the guard first; a falsy result is a
|
|
9
9
|
* 401. Admin is a privileged surface (it reads every partition's clients,
|
|
10
|
-
* commit metadata,
|
|
11
|
-
*
|
|
10
|
+
* commit metadata, scope activity, and reactions) and authorization is
|
|
11
|
+
* entirely the host's and mandatory.
|
|
12
12
|
*/
|
|
13
13
|
import { type SyncularAdmin } from '@syncular/server';
|
|
14
14
|
import { Hono } from 'hono';
|
package/dist/admin.js
CHANGED
|
@@ -7,8 +7,8 @@
|
|
|
7
7
|
* refuses to mount without a host-provided `authorize` guard (it throws at
|
|
8
8
|
* construction). Every request runs the guard first; a falsy result is a
|
|
9
9
|
* 401. Admin is a privileged surface (it reads every partition's clients,
|
|
10
|
-
* commit metadata,
|
|
11
|
-
*
|
|
10
|
+
* commit metadata, scope activity, and reactions) and authorization is
|
|
11
|
+
* entirely the host's and mandatory.
|
|
12
12
|
*/
|
|
13
13
|
import { errorBody, matchesRingQuery, SyncError, } from '@syncular/server';
|
|
14
14
|
import { Hono } from 'hono';
|
|
@@ -113,6 +113,32 @@ export function createSyncularAdminRoutes(admin, options) {
|
|
|
113
113
|
return jsonError(error);
|
|
114
114
|
}
|
|
115
115
|
});
|
|
116
|
+
app.get('/reactions', async (c) => {
|
|
117
|
+
try {
|
|
118
|
+
const status = c.req.query('status');
|
|
119
|
+
const type = c.req.query('type');
|
|
120
|
+
const allowed = new Set([
|
|
121
|
+
'pending',
|
|
122
|
+
'leased',
|
|
123
|
+
'completed',
|
|
124
|
+
'dead-letter',
|
|
125
|
+
]);
|
|
126
|
+
if (status !== undefined && !allowed.has(status)) {
|
|
127
|
+
throw new SyncError('sync.invalid_request', 'invalid reaction status');
|
|
128
|
+
}
|
|
129
|
+
const reactions = await admin.listReactions(partitionOf(c), {
|
|
130
|
+
...(status !== undefined
|
|
131
|
+
? { statuses: [status] }
|
|
132
|
+
: {}),
|
|
133
|
+
...(type !== undefined ? { types: [type] } : {}),
|
|
134
|
+
limit: intParam(c.req.query('limit'), 100),
|
|
135
|
+
});
|
|
136
|
+
return Response.json({ reactions });
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
return jsonError(error);
|
|
140
|
+
}
|
|
141
|
+
});
|
|
116
142
|
app.get('/rows/:table/:rowId', async (c) => {
|
|
117
143
|
try {
|
|
118
144
|
const row = await admin.inspectRow(partitionOf(c), c.req.param('table'), c.req.param('rowId'));
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,15 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hono adapter: a thin wrapper proving the embed boundary.
|
|
3
3
|
* Hono is a dependency of this adapter only, never of the server core.
|
|
4
|
-
* Mounts the §1.1 routes
|
|
5
|
-
*
|
|
4
|
+
* Mounts the §1.1 routes including sync, registered operations, segments, and
|
|
5
|
+
* blobs. Realtime upgrades are runtime-specific and stay with the host.
|
|
6
6
|
*/
|
|
7
|
-
import { type SyncServerConfig } from '@syncular/server';
|
|
7
|
+
import { type RemoteOperationRegistry, type SyncServerConfig } from '@syncular/server';
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
export * from './admin.js';
|
|
10
10
|
export interface SyncularHonoOptions {
|
|
11
11
|
readonly config: SyncServerConfig;
|
|
12
|
+
readonly operations?: RemoteOperationRegistry;
|
|
12
13
|
/** Host authentication (§1.1); `null` ⇒ 401 `sync.auth_required`. */
|
|
13
14
|
readonly authenticate: (request: Request) => Promise<{
|
|
14
15
|
actorId: string;
|
package/dist/index.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hono adapter: a thin wrapper proving the embed boundary.
|
|
3
3
|
* Hono is a dependency of this adapter only, never of the server core.
|
|
4
|
-
* Mounts the §1.1 routes
|
|
5
|
-
*
|
|
4
|
+
* Mounts the §1.1 routes including sync, registered operations, segments, and
|
|
5
|
+
* blobs. Realtime upgrades are runtime-specific and stay with the host.
|
|
6
6
|
*/
|
|
7
|
-
import { encodeSegmentBody, errorBody, handleBlobDownload, handleBlobUpload, handleBlobUploadGrant, handleSegmentDownload, handleSyncRequest, SSP2_CONTENT_TYPE, SyncError, } from '@syncular/server';
|
|
7
|
+
import { encodeSegmentBody, errorBody, handleBlobDownload, handleBlobUpload, handleBlobUploadGrant, handleSegmentDownload, handleRemoteOperation, handleSyncRequest, SSP2_CONTENT_TYPE, SyncError, } from '@syncular/server';
|
|
8
8
|
import { Hono } from 'hono';
|
|
9
9
|
export * from './admin.js';
|
|
10
10
|
function errorResponse(error) {
|
|
@@ -38,6 +38,23 @@ export function createSyncularHono(options) {
|
|
|
38
38
|
return errorResponse(error);
|
|
39
39
|
}
|
|
40
40
|
});
|
|
41
|
+
app.post('/operations', async (c) => {
|
|
42
|
+
const contentType = c.req.header('content-type')?.split(';')[0]?.trim();
|
|
43
|
+
if (contentType !== 'application/vnd.syncular.operations.v1+json') {
|
|
44
|
+
return Response.json(errorBody(new SyncError('operation.invalid_request')), { status: 415 });
|
|
45
|
+
}
|
|
46
|
+
if (options.operations === undefined) {
|
|
47
|
+
return errorResponse(new SyncError('operation.unknown'));
|
|
48
|
+
}
|
|
49
|
+
const auth = await options.authenticate(c.req.raw);
|
|
50
|
+
if (auth === null)
|
|
51
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
52
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
53
|
+
const out = await handleRemoteOperation(bytes, { ...options.config, ...auth }, options.operations);
|
|
54
|
+
return c.body(out.slice().buffer, 200, {
|
|
55
|
+
'Content-Type': 'application/vnd.syncular.operations.v1+json',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
41
58
|
app.get('/segments/:segmentId', async (c) => {
|
|
42
59
|
const auth = await options.authenticate(c.req.raw);
|
|
43
60
|
if (auth === null)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@syncular/server-hono",
|
|
3
|
-
"version": "0.15.
|
|
3
|
+
"version": "0.15.47",
|
|
4
4
|
"description": "Hono adapter for the Syncular sync server",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "Benjamin Kniffler",
|
|
@@ -45,10 +45,10 @@
|
|
|
45
45
|
"!dist/**/*.test.d.ts"
|
|
46
46
|
],
|
|
47
47
|
"dependencies": {
|
|
48
|
-
"@syncular/server": "0.15.
|
|
49
|
-
"hono": "^4.
|
|
48
|
+
"@syncular/server": "0.15.47",
|
|
49
|
+
"hono": "^4.12.34"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@syncular/core": "0.15.
|
|
52
|
+
"@syncular/core": "0.15.47"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/admin.ts
CHANGED
|
@@ -7,13 +7,14 @@
|
|
|
7
7
|
* refuses to mount without a host-provided `authorize` guard (it throws at
|
|
8
8
|
* construction). Every request runs the guard first; a falsy result is a
|
|
9
9
|
* 401. Admin is a privileged surface (it reads every partition's clients,
|
|
10
|
-
* commit metadata,
|
|
11
|
-
*
|
|
10
|
+
* commit metadata, scope activity, and reactions) and authorization is
|
|
11
|
+
* entirely the host's and mandatory.
|
|
12
12
|
*/
|
|
13
13
|
import {
|
|
14
14
|
errorBody,
|
|
15
15
|
matchesRingQuery,
|
|
16
16
|
type RingEventQuery,
|
|
17
|
+
type ReactionStatus,
|
|
17
18
|
SyncError,
|
|
18
19
|
type SyncularAdmin,
|
|
19
20
|
type SyncularServerEvent,
|
|
@@ -159,6 +160,32 @@ export function createSyncularAdminRoutes(
|
|
|
159
160
|
}
|
|
160
161
|
});
|
|
161
162
|
|
|
163
|
+
app.get('/reactions', async (c) => {
|
|
164
|
+
try {
|
|
165
|
+
const status = c.req.query('status');
|
|
166
|
+
const type = c.req.query('type');
|
|
167
|
+
const allowed = new Set<ReactionStatus>([
|
|
168
|
+
'pending',
|
|
169
|
+
'leased',
|
|
170
|
+
'completed',
|
|
171
|
+
'dead-letter',
|
|
172
|
+
]);
|
|
173
|
+
if (status !== undefined && !allowed.has(status as ReactionStatus)) {
|
|
174
|
+
throw new SyncError('sync.invalid_request', 'invalid reaction status');
|
|
175
|
+
}
|
|
176
|
+
const reactions = await admin.listReactions(partitionOf(c), {
|
|
177
|
+
...(status !== undefined
|
|
178
|
+
? { statuses: [status as ReactionStatus] }
|
|
179
|
+
: {}),
|
|
180
|
+
...(type !== undefined ? { types: [type] } : {}),
|
|
181
|
+
limit: intParam(c.req.query('limit'), 100),
|
|
182
|
+
});
|
|
183
|
+
return Response.json({ reactions });
|
|
184
|
+
} catch (error) {
|
|
185
|
+
return jsonError(error);
|
|
186
|
+
}
|
|
187
|
+
});
|
|
188
|
+
|
|
162
189
|
app.get('/rows/:table/:rowId', async (c) => {
|
|
163
190
|
try {
|
|
164
191
|
const row = await admin.inspectRow(
|
package/src/index.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Hono adapter: a thin wrapper proving the embed boundary.
|
|
3
3
|
* Hono is a dependency of this adapter only, never of the server core.
|
|
4
|
-
* Mounts the §1.1 routes
|
|
5
|
-
*
|
|
4
|
+
* Mounts the §1.1 routes including sync, registered operations, segments, and
|
|
5
|
+
* blobs. Realtime upgrades are runtime-specific and stay with the host.
|
|
6
6
|
*/
|
|
7
7
|
import {
|
|
8
8
|
encodeSegmentBody,
|
|
@@ -11,6 +11,8 @@ import {
|
|
|
11
11
|
handleBlobUpload,
|
|
12
12
|
handleBlobUploadGrant,
|
|
13
13
|
handleSegmentDownload,
|
|
14
|
+
handleRemoteOperation,
|
|
15
|
+
type RemoteOperationRegistry,
|
|
14
16
|
handleSyncRequest,
|
|
15
17
|
SSP2_CONTENT_TYPE,
|
|
16
18
|
SyncError,
|
|
@@ -22,6 +24,7 @@ export * from './admin';
|
|
|
22
24
|
|
|
23
25
|
export interface SyncularHonoOptions {
|
|
24
26
|
readonly config: SyncServerConfig;
|
|
27
|
+
readonly operations?: RemoteOperationRegistry;
|
|
25
28
|
/** Host authentication (§1.1); `null` ⇒ 401 `sync.auth_required`. */
|
|
26
29
|
readonly authenticate: (
|
|
27
30
|
request: Request,
|
|
@@ -67,6 +70,31 @@ export function createSyncularHono(options: SyncularHonoOptions): Hono {
|
|
|
67
70
|
}
|
|
68
71
|
});
|
|
69
72
|
|
|
73
|
+
app.post('/operations', async (c) => {
|
|
74
|
+
const contentType = c.req.header('content-type')?.split(';')[0]?.trim();
|
|
75
|
+
if (contentType !== 'application/vnd.syncular.operations.v1+json') {
|
|
76
|
+
return Response.json(
|
|
77
|
+
errorBody(new SyncError('operation.invalid_request')),
|
|
78
|
+
{ status: 415 },
|
|
79
|
+
);
|
|
80
|
+
}
|
|
81
|
+
if (options.operations === undefined) {
|
|
82
|
+
return errorResponse(new SyncError('operation.unknown'));
|
|
83
|
+
}
|
|
84
|
+
const auth = await options.authenticate(c.req.raw);
|
|
85
|
+
if (auth === null)
|
|
86
|
+
return errorResponse(new SyncError('sync.auth_required'));
|
|
87
|
+
const bytes = new Uint8Array(await c.req.arrayBuffer());
|
|
88
|
+
const out = await handleRemoteOperation(
|
|
89
|
+
bytes,
|
|
90
|
+
{ ...options.config, ...auth },
|
|
91
|
+
options.operations,
|
|
92
|
+
);
|
|
93
|
+
return c.body(out.slice().buffer as ArrayBuffer, 200, {
|
|
94
|
+
'Content-Type': 'application/vnd.syncular.operations.v1+json',
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
|
|
70
98
|
app.get('/segments/:segmentId', async (c) => {
|
|
71
99
|
const auth = await options.authenticate(c.req.raw);
|
|
72
100
|
if (auth === null)
|