@regular-software/sync-hono 0.3.0
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/README.md +5 -0
- package/dist/index.d.ts +24 -0
- package/dist/index.js +115 -0
- package/package.json +27 -0
package/README.md
ADDED
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { Hono, type Context } from "hono";
|
|
2
|
+
import type { SyncRequest, SyncResult } from "@regular-software/sync-protocol";
|
|
3
|
+
export type SyncHonoEngine<TContext = undefined> = {
|
|
4
|
+
syncSince(request: SyncRequest, context: TContext): SyncResult | Promise<SyncResult>;
|
|
5
|
+
getVersion?(): number;
|
|
6
|
+
};
|
|
7
|
+
export type SyncContextResolver<TContext> = (context: Context) => TContext | Promise<TContext>;
|
|
8
|
+
export type SyncEventListener = (version: number) => void;
|
|
9
|
+
export declare class SyncEventHub {
|
|
10
|
+
private readonly listeners;
|
|
11
|
+
private lastVersion;
|
|
12
|
+
subscribe(listener: SyncEventListener): () => void;
|
|
13
|
+
publish(version: number): void;
|
|
14
|
+
}
|
|
15
|
+
export declare function createSyncHono<TContext = undefined>(options: {
|
|
16
|
+
engine: SyncHonoEngine<TContext>;
|
|
17
|
+
getContext?: SyncContextResolver<TContext>;
|
|
18
|
+
events?: SyncEventHub;
|
|
19
|
+
pollIntervalMs?: number;
|
|
20
|
+
}): {
|
|
21
|
+
app: Hono;
|
|
22
|
+
events: SyncEventHub;
|
|
23
|
+
stop(): void;
|
|
24
|
+
};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
// packages/hono/src/index.ts
|
|
2
|
+
import { Hono } from "hono";
|
|
3
|
+
var SyncEventHub = class {
|
|
4
|
+
listeners = /* @__PURE__ */ new Set();
|
|
5
|
+
lastVersion = 0;
|
|
6
|
+
subscribe(listener) {
|
|
7
|
+
this.listeners.add(listener);
|
|
8
|
+
return () => this.listeners.delete(listener);
|
|
9
|
+
}
|
|
10
|
+
publish(version) {
|
|
11
|
+
if (version <= this.lastVersion) {
|
|
12
|
+
return;
|
|
13
|
+
}
|
|
14
|
+
this.lastVersion = version;
|
|
15
|
+
for (const listener of this.listeners) {
|
|
16
|
+
listener(version);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
};
|
|
20
|
+
function createSyncHono(options) {
|
|
21
|
+
const events = options.events ?? new SyncEventHub();
|
|
22
|
+
const app = new Hono();
|
|
23
|
+
app.get("/sync", async (context) => {
|
|
24
|
+
const version = Number(context.req.query("version"));
|
|
25
|
+
const replicaId = context.req.query("replicaId");
|
|
26
|
+
const schemaVersion = Number(context.req.query("schemaVersion"));
|
|
27
|
+
const schemaFingerprint = context.req.query("schemaFingerprint");
|
|
28
|
+
if (!Number.isSafeInteger(version) || version < 0 || !Number.isSafeInteger(schemaVersion) || schemaVersion <= 0 || schemaFingerprint === void 0) {
|
|
29
|
+
return context.json({ error: "Invalid synchronization request" }, 400);
|
|
30
|
+
}
|
|
31
|
+
try {
|
|
32
|
+
const resolvedContext = options.getContext ? await options.getContext(context) : void 0;
|
|
33
|
+
return context.json(
|
|
34
|
+
await options.engine.syncSince(
|
|
35
|
+
{
|
|
36
|
+
version,
|
|
37
|
+
...replicaId ? { replicaId } : {},
|
|
38
|
+
schemaVersion,
|
|
39
|
+
schemaFingerprint
|
|
40
|
+
},
|
|
41
|
+
resolvedContext
|
|
42
|
+
)
|
|
43
|
+
);
|
|
44
|
+
} catch (error) {
|
|
45
|
+
if (error instanceof Error && error.name === "SyncSchemaMismatchError") {
|
|
46
|
+
const expected = error.expected;
|
|
47
|
+
return context.json({ error: error.message, expected }, 409);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
});
|
|
52
|
+
app.get("/sync-events", (context) => {
|
|
53
|
+
const encoder = new TextEncoder();
|
|
54
|
+
let closed = false;
|
|
55
|
+
let unsubscribe = () => {
|
|
56
|
+
};
|
|
57
|
+
let heartbeat;
|
|
58
|
+
let controller;
|
|
59
|
+
const close = () => {
|
|
60
|
+
if (closed) {
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
closed = true;
|
|
64
|
+
unsubscribe();
|
|
65
|
+
if (heartbeat) {
|
|
66
|
+
clearInterval(heartbeat);
|
|
67
|
+
}
|
|
68
|
+
controller?.close();
|
|
69
|
+
};
|
|
70
|
+
const stream = new ReadableStream({
|
|
71
|
+
start(nextController) {
|
|
72
|
+
controller = nextController;
|
|
73
|
+
unsubscribe = events.subscribe((version) => {
|
|
74
|
+
if (!closed) {
|
|
75
|
+
controller?.enqueue(encoder.encode(`data: ${version}
|
|
76
|
+
|
|
77
|
+
`));
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
heartbeat = setInterval(() => {
|
|
81
|
+
if (!closed) {
|
|
82
|
+
controller?.enqueue(encoder.encode(": heartbeat\n\n"));
|
|
83
|
+
}
|
|
84
|
+
}, 3e4);
|
|
85
|
+
context.req.raw.signal.addEventListener("abort", close, { once: true });
|
|
86
|
+
},
|
|
87
|
+
cancel: close
|
|
88
|
+
});
|
|
89
|
+
return new Response(stream, {
|
|
90
|
+
headers: {
|
|
91
|
+
"Cache-Control": "no-cache",
|
|
92
|
+
Connection: "keep-alive",
|
|
93
|
+
"Content-Type": "text/event-stream"
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
});
|
|
97
|
+
const pollIntervalMs = options.pollIntervalMs ?? 1e3;
|
|
98
|
+
const poll = options.engine.getVersion && pollIntervalMs > 0 ? setInterval(() => {
|
|
99
|
+
events.publish(options.engine.getVersion?.() ?? 0);
|
|
100
|
+
}, pollIntervalMs) : void 0;
|
|
101
|
+
poll?.unref?.();
|
|
102
|
+
return {
|
|
103
|
+
app,
|
|
104
|
+
events,
|
|
105
|
+
stop() {
|
|
106
|
+
if (poll) {
|
|
107
|
+
clearInterval(poll);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export {
|
|
113
|
+
SyncEventHub,
|
|
114
|
+
createSyncHono
|
|
115
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@regular-software/sync-hono",
|
|
3
|
+
"version": "0.3.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"description": "Hono adapter for regular software sync servers.",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"main": "./dist/index.js",
|
|
8
|
+
"module": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js",
|
|
14
|
+
"default": "./dist/index.js"
|
|
15
|
+
}
|
|
16
|
+
},
|
|
17
|
+
"files": ["dist", "README.md"],
|
|
18
|
+
"repository": {"type": "git", "url": "https://github.com/regular-software/sync.git", "directory": "packages/hono"},
|
|
19
|
+
"bugs": {"url": "https://github.com/regular-software/sync/issues"},
|
|
20
|
+
"homepage": "https://github.com/regular-software/sync#readme",
|
|
21
|
+
"engines": {"node": ">=20"},
|
|
22
|
+
"publishConfig": {"access": "public"},
|
|
23
|
+
"dependencies": {
|
|
24
|
+
"@regular-software/sync-protocol": "0.3.0",
|
|
25
|
+
"hono": "^4.13.5"
|
|
26
|
+
}
|
|
27
|
+
}
|