@cat-factory/node-server 0.123.1 → 0.125.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/dist/container.d.ts +9 -1
- package/dist/container.d.ts.map +1 -1
- package/dist/container.js.map +1 -1
- package/dist/db/schema.d.ts +45 -0
- package/dist/db/schema.d.ts.map +1 -1
- package/dist/db/schema.js +20 -4
- package/dist/db/schema.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/realtime.d.ts +65 -3
- package/dist/realtime.d.ts.map +1 -1
- package/dist/realtime.js +99 -15
- package/dist/realtime.js.map +1 -1
- package/dist/repositories/drizzle/reviews.d.ts +38 -23
- package/dist/repositories/drizzle/reviews.d.ts.map +1 -1
- package/dist/repositories/drizzle/reviews.js +261 -65
- package/dist/repositories/drizzle/reviews.js.map +1 -1
- package/dist/server.d.ts +8 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +48 -42
- package/dist/server.js.map +1 -1
- package/drizzle/20260728090659_review_rev/migration.sql +3 -0
- package/drizzle/20260728090659_review_rev/snapshot.json +17502 -0
- package/drizzle/20260728095931_review_block_unique/migration.sql +32 -0
- package/drizzle/20260728095931_review_block_unique/snapshot.json +17502 -0
- package/package.json +20 -20
package/dist/realtime.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { authorizeWsUpgrade } from '@cat-factory/server';
|
|
1
|
+
import { MACHINE_EVENTS_SUBSCRIBE_PATTERN, authorizeMachineSubscribe, authorizeWsUpgrade, } from '@cat-factory/server';
|
|
2
2
|
import { WebSocket, WebSocketServer } from 'ws';
|
|
3
3
|
const WS_EVENTS_PATH = /^\/workspaces\/([^/]+)\/events$/;
|
|
4
4
|
/**
|
|
@@ -16,6 +16,36 @@ export class NodeRealtimeHub {
|
|
|
16
16
|
// The `?cid=` each socket connected with — used to skip echoing a board mutation back to
|
|
17
17
|
// the connection that caused it (the Node analogue of the DO's serialized attachment).
|
|
18
18
|
connectionIds = new WeakMap();
|
|
19
|
+
roomListener = null;
|
|
20
|
+
/**
|
|
21
|
+
* Observe room open/close transitions (see {@link RealtimeRoomWatcher}). Best-effort in the one
|
|
22
|
+
* direction that matters: a listener that throws must never break a subscribe/unsubscribe, since
|
|
23
|
+
* the socket lifecycle is the transport's core job and the listener is an optional
|
|
24
|
+
* mothership-mode add-on.
|
|
25
|
+
*
|
|
26
|
+
* At most ONE listener, and a second registration THROWS rather than replacing the first: silent
|
|
27
|
+
* replacement would leave the displaced observer believing it is still watching (a mothership
|
|
28
|
+
* node that never learns a room opened holds no upstream stream and the board just stays frozen),
|
|
29
|
+
* which is precisely the class of quiet breakage this seam exists to serve.
|
|
30
|
+
*/
|
|
31
|
+
watchRooms(listener) {
|
|
32
|
+
if (this.roomListener) {
|
|
33
|
+
throw new Error('NodeRealtimeHub already has a room listener; detach before re-registering');
|
|
34
|
+
}
|
|
35
|
+
this.roomListener = listener;
|
|
36
|
+
return () => {
|
|
37
|
+
if (this.roomListener === listener)
|
|
38
|
+
this.roomListener = null;
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
notifyRoom(event, workspaceId) {
|
|
42
|
+
try {
|
|
43
|
+
this.roomListener?.[event](workspaceId);
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
// See watchRooms: the listener is an add-on, the socket lifecycle is not.
|
|
47
|
+
}
|
|
48
|
+
}
|
|
19
49
|
/** Add a socket to a workspace's room; it is reaped on close/error. */
|
|
20
50
|
subscribe(workspaceId, socket, connectionId) {
|
|
21
51
|
let room = this.rooms.get(workspaceId);
|
|
@@ -23,7 +53,10 @@ export class NodeRealtimeHub {
|
|
|
23
53
|
room = new Set();
|
|
24
54
|
this.rooms.set(workspaceId, room);
|
|
25
55
|
}
|
|
56
|
+
const wasEmpty = room.size === 0;
|
|
26
57
|
room.add(socket);
|
|
58
|
+
if (wasEmpty)
|
|
59
|
+
this.notifyRoom('roomOpened', workspaceId);
|
|
27
60
|
if (connectionId)
|
|
28
61
|
this.connectionIds.set(socket, connectionId);
|
|
29
62
|
const drop = () => this.unsubscribe(workspaceId, socket);
|
|
@@ -35,8 +68,10 @@ export class NodeRealtimeHub {
|
|
|
35
68
|
if (!room)
|
|
36
69
|
return;
|
|
37
70
|
room.delete(socket);
|
|
38
|
-
if (room.size === 0)
|
|
71
|
+
if (room.size === 0) {
|
|
39
72
|
this.rooms.delete(workspaceId);
|
|
73
|
+
this.notifyRoom('roomClosed', workspaceId);
|
|
74
|
+
}
|
|
40
75
|
}
|
|
41
76
|
/**
|
|
42
77
|
* Fan a pre-serialised JSON event out to every socket on a workspace's stream. When
|
|
@@ -140,41 +175,90 @@ export class NodeEventPublisher {
|
|
|
140
175
|
}
|
|
141
176
|
/** How often to ping idle sockets to detect (and reap) half-open connections. */
|
|
142
177
|
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
178
|
+
/**
|
|
179
|
+
* Status-line reasons for a refused machine subscription. Keyed by the exact statuses
|
|
180
|
+
* {@link authorizeMachineSubscribe} can return, so a new verdict status fails `tsc` here rather
|
|
181
|
+
* than writing `undefined` into a raw HTTP status line. `503` is unreachable on THIS runtime (see
|
|
182
|
+
* {@link MachineSubscribeDeps}) but is kept so the map stays total over the verdict union — a
|
|
183
|
+
* `Partial` here would trade the compile-time guarantee for a runtime `undefined`.
|
|
184
|
+
*/
|
|
185
|
+
const MACHINE_REJECT_REASON = {
|
|
186
|
+
403: 'Forbidden',
|
|
187
|
+
404: 'Not Found',
|
|
188
|
+
503: 'Service Unavailable',
|
|
189
|
+
};
|
|
143
190
|
/**
|
|
144
191
|
* Attach the real-time WebSocket transport to a running Node HTTP server: accept
|
|
145
192
|
* `GET /workspaces/:ws/events` upgrades (authorising the `?ticket=` exactly like the
|
|
146
193
|
* shared EventsController), register each socket into the {@link NodeRealtimeHub}, and
|
|
147
194
|
* run a heartbeat that terminates dead connections. Returns a stop function that clears
|
|
148
195
|
* the heartbeat and closes the WS server (call it on graceful shutdown).
|
|
196
|
+
*
|
|
197
|
+
* When `machineSubscribe` is supplied it ALSO accepts the mothership-mode machine subscription
|
|
198
|
+
* (`GET /internal/events/subscribe/:ws`), authorised by the shared `authorizeMachineSubscribe`
|
|
199
|
+
* rather than a browser `?ticket=`. Both handshakes land in the same hub room, so a subscribed
|
|
200
|
+
* node receives exactly what a browser on that workspace does — see the initiative doc. The
|
|
201
|
+
* upgrade is handled here (not in the shared `eventsRelayController`) for the same reason the
|
|
202
|
+
* browser stream is: `@hono/node-server` cannot upgrade from a Hono `Response`, so the request
|
|
203
|
+
* never reaches the controller on this runtime.
|
|
149
204
|
*/
|
|
150
|
-
export function attachRealtime(server, hub, auth, log) {
|
|
205
|
+
export function attachRealtime(server, hub, auth, log, machineSubscribe) {
|
|
151
206
|
const wss = new WebSocketServer({ noServer: true });
|
|
207
|
+
/** Complete an authorised handshake: join the hub room and hand the socket to the heartbeat. */
|
|
208
|
+
const accept = (request, socket, head, workspaceId, cid) => {
|
|
209
|
+
wss.handleUpgrade(request, socket, head, (ws) => {
|
|
210
|
+
hub.subscribe(workspaceId, ws, cid);
|
|
211
|
+
wss.emit('connection', ws, request);
|
|
212
|
+
});
|
|
213
|
+
};
|
|
214
|
+
/** Reject a handshake with a bare status line (there is no Response object at this layer). */
|
|
215
|
+
const reject = (socket, status, reason) => {
|
|
216
|
+
socket.write(`HTTP/1.1 ${status} ${reason}\r\n\r\n`);
|
|
217
|
+
socket.destroy();
|
|
218
|
+
};
|
|
152
219
|
server.on('upgrade', (request, socket, head) => {
|
|
153
220
|
const url = new URL(request.url ?? '/', 'http://localhost');
|
|
221
|
+
// The tab's stable connection id (see the SPA's `utils/connectionId.ts`) — or, on the machine
|
|
222
|
+
// route, the subscribing NODE's stable id — so the hub can skip echoing a mutation back to the
|
|
223
|
+
// connection that caused it.
|
|
224
|
+
const cid = url.searchParams.get('cid');
|
|
225
|
+
// Mothership-mode inbound subscription. Checked before the browser route because the two
|
|
226
|
+
// patterns are disjoint; a deployment that isn't a mothership simply never matches (the
|
|
227
|
+
// request falls through to the 404 below).
|
|
228
|
+
const machineMatch = machineSubscribe
|
|
229
|
+
? MACHINE_EVENTS_SUBSCRIBE_PATTERN.exec(url.pathname)
|
|
230
|
+
: null;
|
|
231
|
+
if (machineMatch && machineSubscribe) {
|
|
232
|
+
const workspaceId = decodeURIComponent(machineMatch[1]);
|
|
233
|
+
void authorizeMachineSubscribe({
|
|
234
|
+
auth,
|
|
235
|
+
token: request.headers.authorization,
|
|
236
|
+
workspaceId,
|
|
237
|
+
accountOf: machineSubscribe.accountOf,
|
|
238
|
+
}).then((verdict) => {
|
|
239
|
+
if (!verdict.ok) {
|
|
240
|
+
reject(socket, verdict.status, MACHINE_REJECT_REASON[verdict.status]);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
accept(request, socket, head, workspaceId, cid);
|
|
244
|
+
});
|
|
245
|
+
return;
|
|
246
|
+
}
|
|
154
247
|
const match = WS_EVENTS_PATH.exec(url.pathname);
|
|
155
248
|
// Not our route: refuse rather than leave the socket dangling. (Node mode has no
|
|
156
249
|
// other WebSocket endpoints.)
|
|
157
250
|
if (!match) {
|
|
158
|
-
socket
|
|
159
|
-
socket.destroy();
|
|
251
|
+
reject(socket, 404, 'Not Found');
|
|
160
252
|
return;
|
|
161
253
|
}
|
|
162
254
|
const workspaceId = decodeURIComponent(match[1]);
|
|
163
255
|
const ticket = url.searchParams.get('ticket') ?? undefined;
|
|
164
|
-
// The tab's stable connection id (see the SPA's `utils/connectionId.ts`), so the hub can
|
|
165
|
-
// skip echoing a board mutation back to the connection that caused it.
|
|
166
|
-
const cid = url.searchParams.get('cid');
|
|
167
256
|
void authorizeWsUpgrade(auth, ticket, workspaceId).then((verdict) => {
|
|
168
257
|
if (!verdict.ok) {
|
|
169
|
-
|
|
170
|
-
socket.write(`HTTP/1.1 ${verdict.status} ${reason}\r\n\r\n`);
|
|
171
|
-
socket.destroy();
|
|
258
|
+
reject(socket, verdict.status, verdict.status === 401 ? 'Unauthorized' : 'Service Unavailable');
|
|
172
259
|
return;
|
|
173
260
|
}
|
|
174
|
-
|
|
175
|
-
hub.subscribe(workspaceId, ws, cid);
|
|
176
|
-
wss.emit('connection', ws, request);
|
|
177
|
-
});
|
|
261
|
+
accept(request, socket, head, workspaceId, cid);
|
|
178
262
|
});
|
|
179
263
|
});
|
|
180
264
|
// Liveness sweep: a socket that doesn't answer a ping before the next tick is
|
package/dist/realtime.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"AAoBA,OAAO,
|
|
1
|
+
{"version":3,"file":"realtime.js","sourceRoot":"","sources":["../src/realtime.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,20 +1,33 @@
|
|
|
1
1
|
import type { BrainstormSession, BrainstormSessionRepository, BrainstormStage, ClarityReview, ClarityReviewRepository, ConsensusSession, ConsensusSessionRepository, DocInterviewRepository, DocInterviewSession, RequirementReview, RequirementReviewRepository } from '@cat-factory/kernel';
|
|
2
2
|
import type { DrizzleDb } from '../../db/client.js';
|
|
3
|
-
/**
|
|
4
|
-
* Requirements reviews over Postgres (the Drizzle mirror of the Worker's
|
|
5
|
-
* `D1RequirementReviewRepository`, migration 0021). The reviewed items live as a JSON
|
|
6
|
-
* array in `items`; the service keeps at most one live review per block (it deletes
|
|
7
|
-
* the block's prior review before inserting a fresh one), so `getByBlock` returns the
|
|
8
|
-
* latest. Behaviourally identical to the D1 repo so the cross-runtime conformance
|
|
9
|
-
* suite asserts the same requirements-rework substitution against both stores.
|
|
10
|
-
*/
|
|
11
3
|
export declare class DrizzleRequirementReviewRepository implements RequirementReviewRepository {
|
|
12
4
|
private readonly db;
|
|
13
5
|
constructor(db: DrizzleDb);
|
|
14
6
|
getByBlock(workspaceId: string, blockId: string): Promise<RequirementReview | null>;
|
|
15
7
|
get(workspaceId: string, id: string): Promise<RequirementReview | null>;
|
|
8
|
+
/**
|
|
9
|
+
* Force-write. A fresh insert starts at rev 0; a write over an existing row BUMPS it, so a
|
|
10
|
+
* concurrent {@link compareAndSwap} holding the old revision still detects that the row moved.
|
|
11
|
+
*/
|
|
16
12
|
upsert(workspaceId: string, review: RequirementReview): Promise<void>;
|
|
17
|
-
|
|
13
|
+
compareAndSwap(workspaceId: string, review: RequirementReview): Promise<boolean>;
|
|
14
|
+
/**
|
|
15
|
+
* Publish `review` as the block's one live review, as a SINGLE conflict-targeted upsert on the
|
|
16
|
+
* block's UNIQUE index.
|
|
17
|
+
*
|
|
18
|
+
* This deliberately is NOT a transactioned delete-then-insert. A transaction is not a
|
|
19
|
+
* uniqueness constraint: at Postgres' default READ COMMITTED a DELETE takes no predicate lock,
|
|
20
|
+
* so two concurrent review runs each delete nothing (or each other's already-gone row), each
|
|
21
|
+
* insert, and both commit — leaving the block with TWO live reviews, which is the exact hazard
|
|
22
|
+
* this method exists to prevent. (SQLite serializes writers, so D1 was accidentally safe; a
|
|
23
|
+
* domain invariant must not rest on which runtime it happens to run under.) One statement
|
|
24
|
+
* against the constraint has no window to interleave into on either runtime.
|
|
25
|
+
*
|
|
26
|
+
* The predecessor's row IS this row — its `id` is overwritten — so the superseded review is
|
|
27
|
+
* gone rather than left beside the live one, and `rev` restarts at 0 so a writer still holding
|
|
28
|
+
* the predecessor's revision can't match.
|
|
29
|
+
*/
|
|
30
|
+
replaceForBlock(workspaceId: string, review: RequirementReview): Promise<void>;
|
|
18
31
|
}
|
|
19
32
|
/**
|
|
20
33
|
* Interactive document-interview sessions over Postgres (the Drizzle mirror of the Worker's
|
|
@@ -44,32 +57,34 @@ export declare class DrizzleConsensusSessionRepository implements ConsensusSessi
|
|
|
44
57
|
getByBlock(workspaceId: string, blockId: string): Promise<ConsensusSession | null>;
|
|
45
58
|
upsert(workspaceId: string, session: ConsensusSession): Promise<void>;
|
|
46
59
|
}
|
|
47
|
-
/**
|
|
48
|
-
* Clarity (bug-report triage) reviews over Postgres — the Drizzle mirror of the Worker's
|
|
49
|
-
* `D1ClarityReviewRepository`. Behaviourally identical to the D1 repo so the cross-runtime
|
|
50
|
-
* conformance suite asserts the same clarified-brief substitution against both stores.
|
|
51
|
-
*/
|
|
52
60
|
export declare class DrizzleClarityReviewRepository implements ClarityReviewRepository {
|
|
53
61
|
private readonly db;
|
|
54
62
|
constructor(db: DrizzleDb);
|
|
55
63
|
getByBlock(workspaceId: string, blockId: string): Promise<ClarityReview | null>;
|
|
56
64
|
get(workspaceId: string, id: string): Promise<ClarityReview | null>;
|
|
57
65
|
upsert(workspaceId: string, review: ClarityReview): Promise<void>;
|
|
58
|
-
|
|
66
|
+
compareAndSwap(workspaceId: string, review: ClarityReview): Promise<boolean>;
|
|
67
|
+
/**
|
|
68
|
+
* A single conflict-targeted upsert on the block's UNIQUE index — see
|
|
69
|
+
* {@link DrizzleRequirementReviewRepository.replaceForBlock} for why a transactioned
|
|
70
|
+
* delete-then-insert does not hold this invariant under READ COMMITTED.
|
|
71
|
+
*/
|
|
72
|
+
replaceForBlock(workspaceId: string, review: ClarityReview): Promise<void>;
|
|
59
73
|
}
|
|
60
|
-
/**
|
|
61
|
-
* Brainstorm (structured-dialogue) sessions over Postgres — the Drizzle mirror of the Worker's
|
|
62
|
-
* `D1BrainstormSessionRepository`. Behaviourally identical so the cross-runtime conformance
|
|
63
|
-
* suite asserts the same per-stage round-trip and brainstorm direction handoff against both
|
|
64
|
-
* stores. Keyed per (block, stage): a block may hold a live `requirements` AND `architecture`
|
|
65
|
-
* session at once.
|
|
66
|
-
*/
|
|
67
74
|
export declare class DrizzleBrainstormSessionRepository implements BrainstormSessionRepository {
|
|
68
75
|
private readonly db;
|
|
69
76
|
constructor(db: DrizzleDb);
|
|
70
77
|
getByBlockStage(workspaceId: string, blockId: string, stage: BrainstormStage): Promise<BrainstormSession | null>;
|
|
71
78
|
get(workspaceId: string, id: string): Promise<BrainstormSession | null>;
|
|
72
79
|
upsert(workspaceId: string, session: BrainstormSession): Promise<void>;
|
|
73
|
-
|
|
80
|
+
compareAndSwap(workspaceId: string, session: BrainstormSession): Promise<boolean>;
|
|
81
|
+
/**
|
|
82
|
+
* A single conflict-targeted upsert on the (block, STAGE) UNIQUE index — see
|
|
83
|
+
* {@link DrizzleRequirementReviewRepository.replaceForBlock} for why a transactioned
|
|
84
|
+
* delete-then-insert does not hold this invariant under READ COMMITTED. The stage is part of
|
|
85
|
+
* the conflict target, so the block's other live stage is untouched by construction rather
|
|
86
|
+
* than by a scoped delete.
|
|
87
|
+
*/
|
|
88
|
+
replaceForBlockStage(workspaceId: string, session: BrainstormSession): Promise<void>;
|
|
74
89
|
}
|
|
75
90
|
//# sourceMappingURL=reviews.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"reviews.d.ts","sourceRoot":"","sources":["../../../src/repositories/drizzle/reviews.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAEV,iBAAiB,EACjB,2BAA2B,EAC3B,eAAe,EACf,aAAa,EAEb,uBAAuB,EACvB,gBAAgB,EAChB,0BAA0B,EAE1B,sBAAsB,EACtB,mBAAmB,EAEnB,iBAAiB,EAEjB,2BAA2B,EAC5B,MAAM,qBAAqB,CAAA;AAE5B,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;
|
|
1
|
+
{"version":3,"file":"reviews.d.ts","sourceRoot":"","sources":["../../../src/repositories/drizzle/reviews.ts"],"names":[],"mappings":"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"}
|