@syncular/server-hono 0.15.44 → 0.15.46
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-page.d.ts +2 -2
- package/dist/admin-page.js +2 -2
- package/dist/admin.d.ts +3 -3
- package/dist/admin.js +30 -4
- package/dist/index.d.ts +5 -4
- package/dist/index.js +21 -4
- package/package.json +4 -4
- package/src/admin-page.ts +2 -2
- package/src/admin.ts +31 -4
- package/src/index.ts +31 -3
package/dist/admin-page.d.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The single static console page
|
|
2
|
+
* The single static console page. Zero framework, no build
|
|
3
3
|
* step, ~one file: it fetches the sibling JSON endpoints (relative to its
|
|
4
4
|
* own mount path), renders tables, and offers an auto-refresh toggle —
|
|
5
5
|
* 5% of the code a full console app would cost, the 80% operator value.
|
|
6
6
|
*
|
|
7
|
-
* Styling follows docs/
|
|
7
|
+
* Styling follows docs/STYLE.md: everything
|
|
8
8
|
* monospace, pure black, a single amber accent, 1px borders, sharp
|
|
9
9
|
* corners, bracketed labels, inverse-video hover. The page ships as one
|
|
10
10
|
* self-contained string, so it uses the system mono fallback stack.
|
package/dist/admin-page.js
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The single static console page
|
|
2
|
+
* The single static console page. Zero framework, no build
|
|
3
3
|
* step, ~one file: it fetches the sibling JSON endpoints (relative to its
|
|
4
4
|
* own mount path), renders tables, and offers an auto-refresh toggle —
|
|
5
5
|
* 5% of the code a full console app would cost, the 80% operator value.
|
|
6
6
|
*
|
|
7
|
-
* Styling follows docs/
|
|
7
|
+
* Styling follows docs/STYLE.md: everything
|
|
8
8
|
* monospace, pure black, a single amber accent, 1px borders, sharp
|
|
9
9
|
* corners, bracketed labels, inverse-video hover. The page ships as one
|
|
10
10
|
* self-contained string, so it uses the system mono fallback stack.
|
package/dist/admin.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin / console HTTP surface
|
|
2
|
+
* Admin / console HTTP surface: a mountable Hono sub-app over a
|
|
3
3
|
* `SyncularAdmin`. JSON endpoints mirror the read surface; a single static
|
|
4
4
|
* HTML page (zero framework, no build step) at `GET /` renders them.
|
|
5
5
|
*
|
|
@@ -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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin / console HTTP surface
|
|
2
|
+
* Admin / console HTTP surface: a mountable Hono sub-app over a
|
|
3
3
|
* `SyncularAdmin`. JSON endpoints mirror the read surface; a single static
|
|
4
4
|
* HTML page (zero framework, no build step) at `GET /` renders them.
|
|
5
5
|
*
|
|
@@ -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';
|
|
@@ -32,7 +32,7 @@ function intParam(value, fallback) {
|
|
|
32
32
|
*/
|
|
33
33
|
export function createSyncularAdminRoutes(admin, options) {
|
|
34
34
|
if (typeof options.authorize !== 'function') {
|
|
35
|
-
throw new Error('createSyncularAdminRoutes: an `authorize` guard is required — admin never mounts default-open
|
|
35
|
+
throw new Error('createSyncularAdminRoutes: an `authorize` guard is required — admin never mounts default-open');
|
|
36
36
|
}
|
|
37
37
|
const app = new Hono();
|
|
38
38
|
/** Resolve the request's partition (query, else default) — required. */
|
|
@@ -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
|
-
* Hono adapter
|
|
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
|
-
* Hono adapter
|
|
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.46",
|
|
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.46",
|
|
49
|
+
"hono": "^4.12.34"
|
|
50
50
|
},
|
|
51
51
|
"devDependencies": {
|
|
52
|
-
"@syncular/core": "0.15.
|
|
52
|
+
"@syncular/core": "0.15.46"
|
|
53
53
|
}
|
|
54
54
|
}
|
package/src/admin-page.ts
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The single static console page
|
|
2
|
+
* The single static console page. Zero framework, no build
|
|
3
3
|
* step, ~one file: it fetches the sibling JSON endpoints (relative to its
|
|
4
4
|
* own mount path), renders tables, and offers an auto-refresh toggle —
|
|
5
5
|
* 5% of the code a full console app would cost, the 80% operator value.
|
|
6
6
|
*
|
|
7
|
-
* Styling follows docs/
|
|
7
|
+
* Styling follows docs/STYLE.md: everything
|
|
8
8
|
* monospace, pure black, a single amber accent, 1px borders, sharp
|
|
9
9
|
* corners, bracketed labels, inverse-video hover. The page ships as one
|
|
10
10
|
* self-contained string, so it uses the system mono fallback stack.
|
package/src/admin.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Admin / console HTTP surface
|
|
2
|
+
* Admin / console HTTP surface: a mountable Hono sub-app over a
|
|
3
3
|
* `SyncularAdmin`. JSON endpoints mirror the read surface; a single static
|
|
4
4
|
* HTML page (zero framework, no build step) at `GET /` renders them.
|
|
5
5
|
*
|
|
@@ -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,
|
|
@@ -67,7 +68,7 @@ export function createSyncularAdminRoutes(
|
|
|
67
68
|
): Hono {
|
|
68
69
|
if (typeof options.authorize !== 'function') {
|
|
69
70
|
throw new Error(
|
|
70
|
-
'createSyncularAdminRoutes: an `authorize` guard is required — admin never mounts default-open
|
|
71
|
+
'createSyncularAdminRoutes: an `authorize` guard is required — admin never mounts default-open',
|
|
71
72
|
);
|
|
72
73
|
}
|
|
73
74
|
const app = new Hono();
|
|
@@ -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
|
-
* Hono adapter
|
|
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)
|