@stratasync/server 0.2.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 +188 -0
- package/dist/bootstrap/bootstrap-service.d.ts +41 -0
- package/dist/bootstrap/bootstrap-service.d.ts.map +1 -0
- package/dist/bootstrap/bootstrap-service.js +411 -0
- package/dist/bootstrap/bootstrap-service.js.map +1 -0
- package/dist/config.d.ts +124 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +10 -0
- package/dist/config.js.map +1 -0
- package/dist/create-sync-server.d.ts +5 -0
- package/dist/create-sync-server.d.ts.map +1 -0
- package/dist/create-sync-server.js +96 -0
- package/dist/create-sync-server.js.map +1 -0
- package/dist/dao/sync-dao.d.ts +64 -0
- package/dist/dao/sync-dao.d.ts.map +1 -0
- package/dist/dao/sync-dao.js +137 -0
- package/dist/dao/sync-dao.js.map +1 -0
- package/dist/db.d.ts +37 -0
- package/dist/db.d.ts.map +1 -0
- package/dist/db.js +2 -0
- package/dist/db.js.map +1 -0
- package/dist/delta/delta-publisher.d.ts +52 -0
- package/dist/delta/delta-publisher.d.ts.map +1 -0
- package/dist/delta/delta-publisher.js +217 -0
- package/dist/delta/delta-publisher.js.map +1 -0
- package/dist/delta/delta-service.d.ts +13 -0
- package/dist/delta/delta-service.d.ts.map +1 -0
- package/dist/delta/delta-service.js +36 -0
- package/dist/delta/delta-service.js.map +1 -0
- package/dist/fastify/index.d.ts +7 -0
- package/dist/fastify/index.d.ts.map +1 -0
- package/dist/fastify/index.js +5 -0
- package/dist/fastify/index.js.map +1 -0
- package/dist/fastify/middleware.d.ts +16 -0
- package/dist/fastify/middleware.d.ts.map +1 -0
- package/dist/fastify/middleware.js +101 -0
- package/dist/fastify/middleware.js.map +1 -0
- package/dist/fastify/routes.d.ts +17 -0
- package/dist/fastify/routes.d.ts.map +1 -0
- package/dist/fastify/routes.js +150 -0
- package/dist/fastify/routes.js.map +1 -0
- package/dist/fastify/validation.d.ts +59 -0
- package/dist/fastify/validation.d.ts.map +1 -0
- package/dist/fastify/validation.js +79 -0
- package/dist/fastify/validation.js.map +1 -0
- package/dist/index.d.ts +22 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +22 -0
- package/dist/index.js.map +1 -0
- package/dist/mutate/archive-mutation.d.ts +10 -0
- package/dist/mutate/archive-mutation.d.ts.map +1 -0
- package/dist/mutate/archive-mutation.js +14 -0
- package/dist/mutate/archive-mutation.js.map +1 -0
- package/dist/mutate/field-codecs.d.ts +15 -0
- package/dist/mutate/field-codecs.d.ts.map +1 -0
- package/dist/mutate/field-codecs.js +103 -0
- package/dist/mutate/field-codecs.js.map +1 -0
- package/dist/mutate/model-handlers.d.ts +32 -0
- package/dist/mutate/model-handlers.d.ts.map +1 -0
- package/dist/mutate/model-handlers.js +131 -0
- package/dist/mutate/model-handlers.js.map +1 -0
- package/dist/mutate/mutate-service.d.ts +30 -0
- package/dist/mutate/mutate-service.d.ts.map +1 -0
- package/dist/mutate/mutate-service.js +326 -0
- package/dist/mutate/mutate-service.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +29 -0
- package/dist/types.js.map +1 -0
- package/dist/utils/composite-ids.d.ts +14 -0
- package/dist/utils/composite-ids.d.ts.map +1 -0
- package/dist/utils/composite-ids.js +15 -0
- package/dist/utils/composite-ids.js.map +1 -0
- package/dist/utils/dates.d.ts +8 -0
- package/dist/utils/dates.d.ts.map +1 -0
- package/dist/utils/dates.js +101 -0
- package/dist/utils/dates.js.map +1 -0
- package/dist/utils/sync-scope.d.ts +4 -0
- package/dist/utils/sync-scope.d.ts.map +1 -0
- package/dist/utils/sync-scope.js +17 -0
- package/dist/utils/sync-scope.js.map +1 -0
- package/dist/utils/sync-utils.d.ts +27 -0
- package/dist/utils/sync-utils.d.ts.map +1 -0
- package/dist/utils/sync-utils.js +94 -0
- package/dist/utils/sync-utils.js.map +1 -0
- package/dist/websocket/sync-websocket.d.ts +14 -0
- package/dist/websocket/sync-websocket.d.ts.map +1 -0
- package/dist/websocket/sync-websocket.js +326 -0
- package/dist/websocket/sync-websocket.js.map +1 -0
- package/package.json +70 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export const resolveRequestedSyncGroups = (authorizedGroups, requestedGroups) => {
|
|
2
|
+
if (!requestedGroups || requestedGroups.length === 0) {
|
|
3
|
+
return [...authorizedGroups];
|
|
4
|
+
}
|
|
5
|
+
const allowedGroups = new Set(authorizedGroups);
|
|
6
|
+
return requestedGroups.filter((group) => allowedGroups.has(group));
|
|
7
|
+
};
|
|
8
|
+
export const resolvePublishedDeltaGroups = (groupId, fallbackGroups) => {
|
|
9
|
+
if (groupId) {
|
|
10
|
+
return [groupId];
|
|
11
|
+
}
|
|
12
|
+
return [...fallbackGroups];
|
|
13
|
+
};
|
|
14
|
+
export const dedupeSyncGroups = (groups) => [
|
|
15
|
+
...new Set(groups),
|
|
16
|
+
];
|
|
17
|
+
//# sourceMappingURL=sync-scope.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-scope.js","sourceRoot":"","sources":["../../src/utils/sync-scope.ts"],"names":[],"mappings":"AAAA,MAAM,CAAC,MAAM,0BAA0B,GAAG,CACxC,gBAA0B,EAC1B,eAA0B,EAChB,EAAE;IACZ,IAAI,CAAC,eAAe,IAAI,eAAe,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACrD,OAAO,CAAC,GAAG,gBAAgB,CAAC,CAAC;IAC/B,CAAC;IAED,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,gBAAgB,CAAC,CAAC;IAChD,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC,CAAC;AACrE,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,2BAA2B,GAAG,CACzC,OAAkC,EAClC,cAAwB,EACd,EAAE;IACZ,IAAI,OAAO,EAAE,CAAC;QACZ,OAAO,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IAED,OAAO,CAAC,GAAG,cAAc,CAAC,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,gBAAgB,GAAG,CAAC,MAAgB,EAAY,EAAE,CAAC;IAC9D,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC;CACnB,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { AnyPgColumn, AnyPgTable } from "drizzle-orm/pg-core";
|
|
2
|
+
import type { SerializedSyncActionOutput, SyncActionOutput } from "../types.js";
|
|
3
|
+
/**
|
|
4
|
+
* Gets a typed column reference from a Drizzle table by column name.
|
|
5
|
+
*/
|
|
6
|
+
export declare const getColumn: (table: AnyPgTable, columnName: string) => AnyPgColumn;
|
|
7
|
+
interface RawSyncAction {
|
|
8
|
+
id: bigint;
|
|
9
|
+
model: string;
|
|
10
|
+
modelId: string;
|
|
11
|
+
action: string;
|
|
12
|
+
data: unknown;
|
|
13
|
+
groupId: string | null;
|
|
14
|
+
clientTxId: string | null;
|
|
15
|
+
clientId: string | null;
|
|
16
|
+
createdAt: Date;
|
|
17
|
+
}
|
|
18
|
+
export declare const serializeSyncId: (syncId: bigint) => string;
|
|
19
|
+
export declare const parseSyncIdString: (syncId: string) => bigint;
|
|
20
|
+
/**
|
|
21
|
+
* Converts a raw database sync action to the output format.
|
|
22
|
+
*/
|
|
23
|
+
export declare const toSyncActionOutput: (action: RawSyncAction) => SyncActionOutput;
|
|
24
|
+
export declare const serializeSyncActionOutput: (action: SyncActionOutput) => SerializedSyncActionOutput;
|
|
25
|
+
export declare const parseSyncActionOutput: (raw: unknown) => SyncActionOutput;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=sync-utils.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-utils.d.ts","sourceRoot":"","sources":["../../src/utils/sync-utils.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,MAAM,qBAAqB,CAAC;AAEnE,OAAO,KAAK,EAAE,0BAA0B,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC;AAIhF;;GAEG;AACH,eAAO,MAAM,SAAS,GACpB,OAAO,UAAU,EACjB,YAAY,MAAM,KACjB,WAUF,CAAC;AAEF,UAAU,aAAa;IACrB,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,OAAO,CAAC;IACd,OAAO,EAAE,MAAM,GAAG,IAAI,CAAC;IACvB,UAAU,EAAE,MAAM,GAAG,IAAI,CAAC;IAC1B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,IAAI,CAAC;CACjB;AAKD,eAAO,MAAM,eAAe,GAAI,QAAQ,MAAM,KAAG,MAA2B,CAAC;AAE7E,eAAO,MAAM,iBAAiB,GAAI,QAAQ,MAAM,KAAG,MAKlD,CAAC;AAEF;;GAEG;AACH,eAAO,MAAM,kBAAkB,GAC7B,QAAQ,aAAa,KACpB,gBAUD,CAAC;AAEH,eAAO,MAAM,yBAAyB,GACpC,QAAQ,gBAAgB,KACvB,0BAUD,CAAC;AAEH,eAAO,MAAM,qBAAqB,GAAI,KAAK,OAAO,KAAG,gBAqDpD,CAAC"}
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { getTableColumns } from "drizzle-orm";
|
|
2
|
+
const NON_NEGATIVE_INTEGER_REGEX = /^\d+$/;
|
|
3
|
+
/**
|
|
4
|
+
* Gets a typed column reference from a Drizzle table by column name.
|
|
5
|
+
*/
|
|
6
|
+
export const getColumn = (table, columnName) => {
|
|
7
|
+
const columnMap = getTableColumns(table);
|
|
8
|
+
const column = columnMap[columnName];
|
|
9
|
+
if (!column) {
|
|
10
|
+
throw new Error(`Column ${columnName} not found on table ${table._.name}`);
|
|
11
|
+
}
|
|
12
|
+
return column;
|
|
13
|
+
};
|
|
14
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
15
|
+
export const serializeSyncId = (syncId) => syncId.toString();
|
|
16
|
+
export const parseSyncIdString = (syncId) => {
|
|
17
|
+
if (!NON_NEGATIVE_INTEGER_REGEX.test(syncId)) {
|
|
18
|
+
throw new Error(`Invalid syncId: ${syncId}`);
|
|
19
|
+
}
|
|
20
|
+
return BigInt(syncId);
|
|
21
|
+
};
|
|
22
|
+
/**
|
|
23
|
+
* Converts a raw database sync action to the output format.
|
|
24
|
+
*/
|
|
25
|
+
export const toSyncActionOutput = (action) => ({
|
|
26
|
+
action: action.action,
|
|
27
|
+
clientId: action.clientId ?? undefined,
|
|
28
|
+
clientTxId: action.clientTxId ?? undefined,
|
|
29
|
+
createdAt: action.createdAt,
|
|
30
|
+
data: action.data ?? {},
|
|
31
|
+
groupId: action.groupId ?? undefined,
|
|
32
|
+
modelId: action.modelId,
|
|
33
|
+
modelName: action.model,
|
|
34
|
+
syncId: serializeSyncId(action.id),
|
|
35
|
+
});
|
|
36
|
+
export const serializeSyncActionOutput = (action) => ({
|
|
37
|
+
action: action.action,
|
|
38
|
+
clientId: action.clientId,
|
|
39
|
+
clientTxId: action.clientTxId,
|
|
40
|
+
createdAt: action.createdAt.toISOString(),
|
|
41
|
+
data: action.data,
|
|
42
|
+
groupId: action.groupId,
|
|
43
|
+
modelId: action.modelId,
|
|
44
|
+
modelName: action.modelName,
|
|
45
|
+
syncId: action.syncId,
|
|
46
|
+
});
|
|
47
|
+
export const parseSyncActionOutput = (raw) => {
|
|
48
|
+
if (!isRecord(raw)) {
|
|
49
|
+
throw new Error("Sync action must be an object");
|
|
50
|
+
}
|
|
51
|
+
const { action, createdAt, data, modelId, modelName, syncId } = raw;
|
|
52
|
+
if (typeof action !== "string") {
|
|
53
|
+
throw new TypeError("Sync action action must be a string");
|
|
54
|
+
}
|
|
55
|
+
if (typeof createdAt !== "string") {
|
|
56
|
+
throw new TypeError("Sync action createdAt must be a string");
|
|
57
|
+
}
|
|
58
|
+
if (!isRecord(data)) {
|
|
59
|
+
throw new Error("Sync action data must be an object");
|
|
60
|
+
}
|
|
61
|
+
if (typeof modelId !== "string") {
|
|
62
|
+
throw new TypeError("Sync action modelId must be a string");
|
|
63
|
+
}
|
|
64
|
+
if (typeof modelName !== "string") {
|
|
65
|
+
throw new TypeError("Sync action modelName must be a string");
|
|
66
|
+
}
|
|
67
|
+
if (typeof syncId !== "string") {
|
|
68
|
+
throw new TypeError("Sync action syncId must be a string");
|
|
69
|
+
}
|
|
70
|
+
parseSyncIdString(syncId);
|
|
71
|
+
const createdAtDate = new Date(createdAt);
|
|
72
|
+
if (Number.isNaN(createdAtDate.getTime())) {
|
|
73
|
+
throw new TypeError("Sync action createdAt must be a valid date");
|
|
74
|
+
}
|
|
75
|
+
const parsed = {
|
|
76
|
+
action,
|
|
77
|
+
createdAt: createdAtDate,
|
|
78
|
+
data,
|
|
79
|
+
modelId,
|
|
80
|
+
modelName,
|
|
81
|
+
syncId,
|
|
82
|
+
};
|
|
83
|
+
if (typeof raw.groupId === "string") {
|
|
84
|
+
parsed.groupId = raw.groupId;
|
|
85
|
+
}
|
|
86
|
+
if (typeof raw.clientTxId === "string") {
|
|
87
|
+
parsed.clientTxId = raw.clientTxId;
|
|
88
|
+
}
|
|
89
|
+
if (typeof raw.clientId === "string") {
|
|
90
|
+
parsed.clientId = raw.clientId;
|
|
91
|
+
}
|
|
92
|
+
return parsed;
|
|
93
|
+
};
|
|
94
|
+
//# sourceMappingURL=sync-utils.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-utils.js","sourceRoot":"","sources":["../../src/utils/sync-utils.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import type { FastifyInstance } from "fastify";
|
|
2
|
+
import type { SyncAuthConfig, SyncLogger, WebSocketHooks } from "../config.js";
|
|
3
|
+
import type { SyncDao } from "../dao/sync-dao.js";
|
|
4
|
+
import type { DeltaSubscriberLike } from "../delta/delta-publisher.js";
|
|
5
|
+
interface RegisterWebSocketOptions {
|
|
6
|
+
auth: SyncAuthConfig;
|
|
7
|
+
syncDao: SyncDao;
|
|
8
|
+
deltaSubscriber?: DeltaSubscriberLike;
|
|
9
|
+
hooks?: WebSocketHooks;
|
|
10
|
+
logger?: SyncLogger;
|
|
11
|
+
}
|
|
12
|
+
export declare const registerSyncWebsocket: (server: FastifyInstance, options: RegisterWebSocketOptions) => void;
|
|
13
|
+
export {};
|
|
14
|
+
//# sourceMappingURL=sync-websocket.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-websocket.d.ts","sourceRoot":"","sources":["../../src/websocket/sync-websocket.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,SAAS,CAAC;AAG/C,OAAO,KAAK,EAAE,cAAc,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAE/E,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,oBAAoB,CAAC;AAClD,OAAO,KAAK,EAAE,mBAAmB,EAAE,MAAM,6BAA6B,CAAC;AA0SvE,UAAU,wBAAwB;IAChC,IAAI,EAAE,cAAc,CAAC;IACrB,OAAO,EAAE,OAAO,CAAC;IACjB,eAAe,CAAC,EAAE,mBAAmB,CAAC;IACtC,KAAK,CAAC,EAAE,cAAc,CAAC;IACvB,MAAM,CAAC,EAAE,UAAU,CAAC;CACrB;AAED,eAAO,MAAM,qBAAqB,GAChC,QAAQ,eAAe,EACvB,SAAS,wBAAwB,KAChC,IAwQF,CAAC"}
|
|
@@ -0,0 +1,326 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
import { noopLogger } from "../config.js";
|
|
3
|
+
import { safeJsonStringify } from "../delta/delta-publisher.js";
|
|
4
|
+
import { dedupeSyncGroups, resolveRequestedSyncGroups, } from "../utils/sync-scope.js";
|
|
5
|
+
import { parseSyncIdString, serializeSyncId, toSyncActionOutput, } from "../utils/sync-utils.js";
|
|
6
|
+
const HEARTBEAT_INTERVAL_MS = 30_000;
|
|
7
|
+
const NON_NEGATIVE_INTEGER_REGEX = /^\d+$/;
|
|
8
|
+
const hasGroupOverlap = (clientGroups, deltaGroups) => {
|
|
9
|
+
if (deltaGroups.length === 0) {
|
|
10
|
+
return true;
|
|
11
|
+
}
|
|
12
|
+
return clientGroups.some((group) => deltaGroups.includes(group));
|
|
13
|
+
};
|
|
14
|
+
const isRecord = (value) => typeof value === "object" && value !== null;
|
|
15
|
+
const isNonNegativeIntegerString = (value) => NON_NEGATIVE_INTEGER_REGEX.test(value);
|
|
16
|
+
const isSubscribeMessage = (msg) => {
|
|
17
|
+
if (typeof msg !== "object" || msg === null) {
|
|
18
|
+
return false;
|
|
19
|
+
}
|
|
20
|
+
const record = msg;
|
|
21
|
+
if (record.type !== "subscribe") {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
if (typeof record.token !== "string") {
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
const { afterSyncId } = record;
|
|
28
|
+
const { groups } = record;
|
|
29
|
+
const groupsValid = groups === undefined ||
|
|
30
|
+
(Array.isArray(groups) &&
|
|
31
|
+
groups.every((group) => typeof group === "string"));
|
|
32
|
+
if (!groupsValid) {
|
|
33
|
+
return false;
|
|
34
|
+
}
|
|
35
|
+
return (afterSyncId === undefined ||
|
|
36
|
+
(typeof afterSyncId === "string" && isNonNegativeIntegerString(afterSyncId)));
|
|
37
|
+
};
|
|
38
|
+
const compareSyncActionOutput = (left, right) => {
|
|
39
|
+
const leftId = parseSyncIdString(left.syncId);
|
|
40
|
+
const rightId = parseSyncIdString(right.syncId);
|
|
41
|
+
if (leftId === rightId) {
|
|
42
|
+
return 0;
|
|
43
|
+
}
|
|
44
|
+
return leftId < rightId ? -1 : 1;
|
|
45
|
+
};
|
|
46
|
+
const unsubscribeClient = (state) => {
|
|
47
|
+
if (!state.unsubscribe) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
state.unsubscribe();
|
|
51
|
+
state.unsubscribe = null;
|
|
52
|
+
};
|
|
53
|
+
const resetClientAuthState = (state) => {
|
|
54
|
+
state.authenticated = false;
|
|
55
|
+
state.userId = null;
|
|
56
|
+
state.groups = [];
|
|
57
|
+
state.afterSyncId = "0";
|
|
58
|
+
state.replaying = false;
|
|
59
|
+
state.bufferedActions = [];
|
|
60
|
+
};
|
|
61
|
+
const setSubscribedState = (state, userId, groups, afterSyncId) => {
|
|
62
|
+
state.userId = userId;
|
|
63
|
+
state.groups = groups;
|
|
64
|
+
state.authenticated = true;
|
|
65
|
+
state.afterSyncId = afterSyncId;
|
|
66
|
+
state.replaying = true;
|
|
67
|
+
state.bufferedActions = [];
|
|
68
|
+
};
|
|
69
|
+
const resolveSubscribeGroups = (authorizedGroups, requestedGroups) => {
|
|
70
|
+
const dedupedAuthorizedGroups = dedupeSyncGroups(authorizedGroups);
|
|
71
|
+
const dedupedRequestedGroups = requestedGroups
|
|
72
|
+
? dedupeSyncGroups(requestedGroups)
|
|
73
|
+
: undefined;
|
|
74
|
+
const allowedGroups = new Set(dedupedAuthorizedGroups);
|
|
75
|
+
const groups = resolveRequestedSyncGroups(dedupedAuthorizedGroups, dedupedRequestedGroups);
|
|
76
|
+
const rejectedGroups = dedupedRequestedGroups
|
|
77
|
+
? dedupedRequestedGroups.filter((group) => !allowedGroups.has(group))
|
|
78
|
+
: [];
|
|
79
|
+
return { groups, rejectedGroups };
|
|
80
|
+
};
|
|
81
|
+
const sendSubscribedMessage = (ws, state) => {
|
|
82
|
+
ws.send(JSON.stringify({
|
|
83
|
+
afterSyncId: state.afterSyncId,
|
|
84
|
+
groups: state.groups,
|
|
85
|
+
type: "subscribed",
|
|
86
|
+
}));
|
|
87
|
+
};
|
|
88
|
+
const bufferOrSendLiveDelta = (ws, state, action, groups, sendDeltaAction) => {
|
|
89
|
+
if (!hasGroupOverlap(state.groups, groups)) {
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
const { syncId } = action;
|
|
93
|
+
if (parseSyncIdString(syncId) <= parseSyncIdString(state.afterSyncId)) {
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
if (state.replaying) {
|
|
97
|
+
state.bufferedActions.push({ action, groups });
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
sendDeltaAction(ws, state, action);
|
|
101
|
+
};
|
|
102
|
+
const installDeltaSubscription = (ws, state, deltaSubscriber, sendDeltaAction) => {
|
|
103
|
+
if (!deltaSubscriber) {
|
|
104
|
+
return;
|
|
105
|
+
}
|
|
106
|
+
state.unsubscribe = deltaSubscriber.onDelta((action, groups) => {
|
|
107
|
+
bufferOrSendLiveDelta(ws, state, action, groups, sendDeltaAction);
|
|
108
|
+
});
|
|
109
|
+
};
|
|
110
|
+
const replaySyncActions = async (syncDao, ws, state, sendDeltaAction) => {
|
|
111
|
+
let replayCursor = state.afterSyncId;
|
|
112
|
+
while (true) {
|
|
113
|
+
const actions = await syncDao.getSyncActions(parseSyncIdString(replayCursor), state.groups, 1000);
|
|
114
|
+
if (actions.length === 0) {
|
|
115
|
+
break;
|
|
116
|
+
}
|
|
117
|
+
for (const action of actions) {
|
|
118
|
+
sendDeltaAction(ws, state, toSyncActionOutput(action));
|
|
119
|
+
}
|
|
120
|
+
const lastAction = actions.at(-1);
|
|
121
|
+
if (!lastAction || actions.length < 1000) {
|
|
122
|
+
break;
|
|
123
|
+
}
|
|
124
|
+
replayCursor = serializeSyncId(lastAction.id);
|
|
125
|
+
}
|
|
126
|
+
};
|
|
127
|
+
const flushBufferedActions = (ws, state, sendDeltaAction) => {
|
|
128
|
+
state.replaying = false;
|
|
129
|
+
state.bufferedActions.sort((left, right) => compareSyncActionOutput(left.action, right.action));
|
|
130
|
+
for (const entry of state.bufferedActions) {
|
|
131
|
+
if (!hasGroupOverlap(state.groups, entry.groups)) {
|
|
132
|
+
continue;
|
|
133
|
+
}
|
|
134
|
+
sendDeltaAction(ws, state, entry.action);
|
|
135
|
+
}
|
|
136
|
+
state.bufferedActions = [];
|
|
137
|
+
};
|
|
138
|
+
export const registerSyncWebsocket = (server, options) => {
|
|
139
|
+
const { auth, deltaSubscriber, hooks, logger = noopLogger, syncDao, } = options;
|
|
140
|
+
// Use route registration that is compatible with @fastify/websocket
|
|
141
|
+
// The `websocket: true` option and handler signature come from the plugin
|
|
142
|
+
server.get("/sync/ws", {
|
|
143
|
+
preValidation: async (request, reply) => {
|
|
144
|
+
const token = request.query?.token;
|
|
145
|
+
if (!token) {
|
|
146
|
+
reply.code(401).send({ error: "Token required" });
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
const payload = await auth.verifyToken(token);
|
|
150
|
+
if (!payload) {
|
|
151
|
+
reply.code(401).send({ error: "Invalid token" });
|
|
152
|
+
}
|
|
153
|
+
},
|
|
154
|
+
websocket: true,
|
|
155
|
+
}, (socket) => {
|
|
156
|
+
const connectionId = randomUUID();
|
|
157
|
+
const clientState = {
|
|
158
|
+
afterSyncId: "0",
|
|
159
|
+
authenticated: false,
|
|
160
|
+
bufferedActions: [],
|
|
161
|
+
groups: [],
|
|
162
|
+
replaying: false,
|
|
163
|
+
subscribePromise: null,
|
|
164
|
+
unsubscribe: null,
|
|
165
|
+
userId: null,
|
|
166
|
+
};
|
|
167
|
+
const heartbeatInterval = setInterval(() => {
|
|
168
|
+
if (socket.readyState === socket.OPEN) {
|
|
169
|
+
socket.ping();
|
|
170
|
+
}
|
|
171
|
+
}, HEARTBEAT_INTERVAL_MS);
|
|
172
|
+
const getConnectionContext = () => ({
|
|
173
|
+
connId: connectionId,
|
|
174
|
+
groups: clientState.groups,
|
|
175
|
+
userId: clientState.userId ?? `anonymous-${connectionId}`,
|
|
176
|
+
});
|
|
177
|
+
const sendDeltaAction = (ws, state, action) => {
|
|
178
|
+
const { syncId } = action;
|
|
179
|
+
if (parseSyncIdString(syncId) <= parseSyncIdString(state.afterSyncId)) {
|
|
180
|
+
return;
|
|
181
|
+
}
|
|
182
|
+
state.afterSyncId = syncId;
|
|
183
|
+
const deltaMessage = {
|
|
184
|
+
packet: {
|
|
185
|
+
actions: [
|
|
186
|
+
{
|
|
187
|
+
action: action.action,
|
|
188
|
+
clientId: action.clientId,
|
|
189
|
+
clientTxId: action.clientTxId,
|
|
190
|
+
createdAt: action.createdAt.toISOString(),
|
|
191
|
+
data: action.data,
|
|
192
|
+
groupId: action.groupId,
|
|
193
|
+
modelId: action.modelId,
|
|
194
|
+
modelName: action.modelName,
|
|
195
|
+
syncId,
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
lastSyncId: syncId,
|
|
199
|
+
},
|
|
200
|
+
type: "delta",
|
|
201
|
+
};
|
|
202
|
+
if (ws.readyState === ws.OPEN) {
|
|
203
|
+
ws.send(safeJsonStringify(deltaMessage));
|
|
204
|
+
}
|
|
205
|
+
};
|
|
206
|
+
const handleSubscribe = async (ws, state, msg) => {
|
|
207
|
+
const previousContext = getConnectionContext();
|
|
208
|
+
unsubscribeClient(state);
|
|
209
|
+
resetClientAuthState(state);
|
|
210
|
+
if (hooks?.onSubscribe) {
|
|
211
|
+
await hooks.onSubscribe(ws, getConnectionContext(), previousContext);
|
|
212
|
+
}
|
|
213
|
+
const payload = await auth.verifyToken(msg.token);
|
|
214
|
+
if (!payload) {
|
|
215
|
+
ws.send(JSON.stringify({ message: "Invalid token", type: "error" }));
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
const dbGroups = await syncDao.getUserGroups(payload.userId);
|
|
219
|
+
const authorizedGroups = dedupeSyncGroups([
|
|
220
|
+
...dbGroups,
|
|
221
|
+
payload.userId,
|
|
222
|
+
]);
|
|
223
|
+
const { groups, rejectedGroups } = resolveSubscribeGroups(authorizedGroups, msg.groups);
|
|
224
|
+
if (rejectedGroups.length > 0) {
|
|
225
|
+
logger.warn({
|
|
226
|
+
authorizedGroups,
|
|
227
|
+
requestedGroups: rejectedGroups,
|
|
228
|
+
userId: payload.userId,
|
|
229
|
+
}, "Rejected unauthorized WebSocket sync groups");
|
|
230
|
+
}
|
|
231
|
+
setSubscribedState(state, payload.userId, groups, msg.afterSyncId ?? "0");
|
|
232
|
+
installDeltaSubscription(ws, state, deltaSubscriber, sendDeltaAction);
|
|
233
|
+
sendSubscribedMessage(ws, state);
|
|
234
|
+
try {
|
|
235
|
+
await replaySyncActions(syncDao, ws, state, sendDeltaAction);
|
|
236
|
+
flushBufferedActions(ws, state, sendDeltaAction);
|
|
237
|
+
}
|
|
238
|
+
catch (replayError) {
|
|
239
|
+
state.replaying = false;
|
|
240
|
+
state.bufferedActions = [];
|
|
241
|
+
throw replayError;
|
|
242
|
+
}
|
|
243
|
+
};
|
|
244
|
+
const handleSubscribeMessage = async (ws, state, msg) => {
|
|
245
|
+
if (state.subscribePromise) {
|
|
246
|
+
await state.subscribePromise;
|
|
247
|
+
}
|
|
248
|
+
const subscribePromise = handleSubscribe(ws, state, msg);
|
|
249
|
+
state.subscribePromise = subscribePromise;
|
|
250
|
+
try {
|
|
251
|
+
await subscribePromise;
|
|
252
|
+
}
|
|
253
|
+
finally {
|
|
254
|
+
if (state.subscribePromise === subscribePromise) {
|
|
255
|
+
state.subscribePromise = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
};
|
|
259
|
+
const maybeHandleSubscribeFrame = async (message) => {
|
|
260
|
+
if (isSubscribeMessage(message)) {
|
|
261
|
+
await handleSubscribeMessage(socket, clientState, message);
|
|
262
|
+
return true;
|
|
263
|
+
}
|
|
264
|
+
if (message.type === "subscribe") {
|
|
265
|
+
socket.send(JSON.stringify({
|
|
266
|
+
message: "Authentication required",
|
|
267
|
+
type: "error",
|
|
268
|
+
}));
|
|
269
|
+
return true;
|
|
270
|
+
}
|
|
271
|
+
return false;
|
|
272
|
+
};
|
|
273
|
+
const maybeHandleHookMessage = async (message) => {
|
|
274
|
+
if (!hooks?.onMessage) {
|
|
275
|
+
return false;
|
|
276
|
+
}
|
|
277
|
+
if (!clientState.authenticated) {
|
|
278
|
+
return false;
|
|
279
|
+
}
|
|
280
|
+
return await hooks.onMessage(socket, message, getConnectionContext());
|
|
281
|
+
};
|
|
282
|
+
socket.on("message", async (data) => {
|
|
283
|
+
try {
|
|
284
|
+
const message = JSON.parse(data.toString());
|
|
285
|
+
if (!isRecord(message)) {
|
|
286
|
+
return;
|
|
287
|
+
}
|
|
288
|
+
if (await maybeHandleSubscribeFrame(message)) {
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
if (clientState.subscribePromise) {
|
|
292
|
+
await clientState.subscribePromise;
|
|
293
|
+
}
|
|
294
|
+
await maybeHandleHookMessage(message);
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
logger.warn({ error }, "WebSocket message handling error");
|
|
298
|
+
}
|
|
299
|
+
});
|
|
300
|
+
const safeRunOnClose = async () => {
|
|
301
|
+
if (!hooks?.onClose) {
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
try {
|
|
305
|
+
await hooks.onClose(socket, getConnectionContext());
|
|
306
|
+
}
|
|
307
|
+
catch {
|
|
308
|
+
// Silently ignore cleanup errors during disconnect
|
|
309
|
+
}
|
|
310
|
+
};
|
|
311
|
+
const cleanupWebSocket = () => {
|
|
312
|
+
clearInterval(heartbeatInterval);
|
|
313
|
+
if (clientState.unsubscribe) {
|
|
314
|
+
clientState.unsubscribe();
|
|
315
|
+
clientState.unsubscribe = null;
|
|
316
|
+
}
|
|
317
|
+
safeRunOnClose();
|
|
318
|
+
};
|
|
319
|
+
socket.on("close", cleanupWebSocket);
|
|
320
|
+
socket.on("error", (error) => {
|
|
321
|
+
logger.warn({ error }, "WebSocket error");
|
|
322
|
+
cleanupWebSocket();
|
|
323
|
+
});
|
|
324
|
+
});
|
|
325
|
+
};
|
|
326
|
+
//# sourceMappingURL=sync-websocket.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sync-websocket.js","sourceRoot":"","sources":["../../src/websocket/sync-websocket.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAMzC,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAG1C,OAAO,EAAE,iBAAiB,EAAE,MAAM,6BAA6B,CAAC;AAEhE,OAAO,EACL,gBAAgB,EAChB,0BAA0B,GAC3B,MAAM,wBAAwB,CAAC;AAChC,OAAO,EACL,iBAAiB,EACjB,eAAe,EACf,kBAAkB,GACnB,MAAM,wBAAwB,CAAC;AAyChC,MAAM,qBAAqB,GAAG,MAAM,CAAC;AACrC,MAAM,0BAA0B,GAAG,OAAO,CAAC;AAE3C,MAAM,eAAe,GAAG,CACtB,YAAsB,EACtB,WAAqB,EACZ,EAAE;IACX,IAAI,WAAW,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QAC7B,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,YAAY,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC;AACnE,CAAC,CAAC;AAEF,MAAM,QAAQ,GAAG,CAAC,KAAc,EAAoC,EAAE,CACpE,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,KAAK,IAAI,CAAC;AAE9C,MAAM,0BAA0B,GAAG,CAAC,KAAa,EAAW,EAAE,CAC5D,0BAA0B,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC;AAEzC,MAAM,kBAAkB,GAAG,CAAC,GAAY,EAA2B,EAAE;IACnE,IAAI,OAAO,GAAG,KAAK,QAAQ,IAAI,GAAG,KAAK,IAAI,EAAE,CAAC;QAC5C,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,MAAM,GAAG,GAA8B,CAAC;IAC9C,IAAI,MAAM,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;QAChC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,IAAI,OAAO,MAAM,CAAC,KAAK,KAAK,QAAQ,EAAE,CAAC;QACrC,OAAO,KAAK,CAAC;IACf,CAAC;IACD,MAAM,EAAE,WAAW,EAAE,GAAG,MAAM,CAAC;IAC/B,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC1B,MAAM,WAAW,GACf,MAAM,KAAK,SAAS;QACpB,CAAC,KAAK,CAAC,OAAO,CAAC,MAAM,CAAC;YACpB,MAAM,CAAC,KAAK,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,OAAO,KAAK,KAAK,QAAQ,CAAC,CAAC,CAAC;IACxD,IAAI,CAAC,WAAW,EAAE,CAAC;QACjB,OAAO,KAAK,CAAC;IACf,CAAC;IACD,OAAO,CACL,WAAW,KAAK,SAAS;QACzB,CAAC,OAAO,WAAW,KAAK,QAAQ,IAAI,0BAA0B,CAAC,WAAW,CAAC,CAAC,CAC7E,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,uBAAuB,GAAG,CAC9B,IAAsB,EACtB,KAAuB,EACf,EAAE;IACV,MAAM,MAAM,GAAG,iBAAiB,CAAC,IAAI,CAAC,MAAM,CAAC,CAAC;IAC9C,MAAM,OAAO,GAAG,iBAAiB,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;IAChD,IAAI,MAAM,KAAK,OAAO,EAAE,CAAC;QACvB,OAAO,CAAC,CAAC;IACX,CAAC;IACD,OAAO,MAAM,GAAG,OAAO,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;AACnC,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,CAAC,KAAkB,EAAQ,EAAE;IACrD,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC;QACvB,OAAO;IACT,CAAC;IACD,KAAK,CAAC,WAAW,EAAE,CAAC;IACpB,KAAK,CAAC,WAAW,GAAG,IAAI,CAAC;AAC3B,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAAC,KAAkB,EAAQ,EAAE;IACxD,KAAK,CAAC,aAAa,GAAG,KAAK,CAAC;IAC5B,KAAK,CAAC,MAAM,GAAG,IAAI,CAAC;IACpB,KAAK,CAAC,MAAM,GAAG,EAAE,CAAC;IAClB,KAAK,CAAC,WAAW,GAAG,GAAG,CAAC;IACxB,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;IACxB,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,kBAAkB,GAAG,CACzB,KAAkB,EAClB,MAAc,EACd,MAAgB,EAChB,WAAmB,EACb,EAAE;IACR,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,KAAK,CAAC,MAAM,GAAG,MAAM,CAAC;IACtB,KAAK,CAAC,aAAa,GAAG,IAAI,CAAC;IAC3B,KAAK,CAAC,WAAW,GAAG,WAAW,CAAC;IAChC,KAAK,CAAC,SAAS,GAAG,IAAI,CAAC;IACvB,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;AAC7B,CAAC,CAAC;AAEF,MAAM,sBAAsB,GAAG,CAC7B,gBAA0B,EAC1B,eAA0B,EAI1B,EAAE;IACF,MAAM,uBAAuB,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,CAAC;IACnE,MAAM,sBAAsB,GAAG,eAAe;QAC5C,CAAC,CAAC,gBAAgB,CAAC,eAAe,CAAC;QACnC,CAAC,CAAC,SAAS,CAAC;IACd,MAAM,aAAa,GAAG,IAAI,GAAG,CAAC,uBAAuB,CAAC,CAAC;IACvD,MAAM,MAAM,GAAG,0BAA0B,CACvC,uBAAuB,EACvB,sBAAsB,CACvB,CAAC;IACF,MAAM,cAAc,GAAG,sBAAsB;QAC3C,CAAC,CAAC,sBAAsB,CAAC,MAAM,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,aAAa,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;QACrE,CAAC,CAAC,EAAE,CAAC;IAEP,OAAO,EAAE,MAAM,EAAE,cAAc,EAAE,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,EAAa,EAAE,KAAkB,EAAQ,EAAE;IACxE,EAAE,CAAC,IAAI,CACL,IAAI,CAAC,SAAS,CAAC;QACb,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,IAAI,EAAE,YAAY;KACnB,CAAC,CACH,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAC5B,EAAa,EACb,KAAkB,EAClB,MAAwB,EACxB,MAAgB,EAChB,eAIS,EACH,EAAE;IACR,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CAAC;QAC3C,OAAO;IACT,CAAC;IAED,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;IAC1B,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;QACtE,OAAO;IACT,CAAC;IAED,IAAI,KAAK,CAAC,SAAS,EAAE,CAAC;QACpB,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC,CAAC;QAC/C,OAAO;IACT,CAAC;IAED,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,CAAC,CAAC;AACrC,CAAC,CAAC;AAEF,MAAM,wBAAwB,GAAG,CAC/B,EAAa,EACb,KAAkB,EAClB,eAAgD,EAChD,eAIS,EACH,EAAE;IACR,IAAI,CAAC,eAAe,EAAE,CAAC;QACrB,OAAO;IACT,CAAC;IAED,KAAK,CAAC,WAAW,GAAG,eAAe,CAAC,OAAO,CACzC,CAAC,MAAwB,EAAE,MAAgB,EAAE,EAAE;QAC7C,qBAAqB,CAAC,EAAE,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,eAAe,CAAC,CAAC;IACpE,CAAC,CACF,CAAC;AACJ,CAAC,CAAC;AAEF,MAAM,iBAAiB,GAAG,KAAK,EAC7B,OAAgB,EAChB,EAAa,EACb,KAAkB,EAClB,eAIS,EACM,EAAE;IACjB,IAAI,YAAY,GAAG,KAAK,CAAC,WAAW,CAAC;IAErC,OAAO,IAAI,EAAE,CAAC;QACZ,MAAM,OAAO,GAAG,MAAM,OAAO,CAAC,cAAc,CAC1C,iBAAiB,CAAC,YAAY,CAAC,EAC/B,KAAK,CAAC,MAAM,EACZ,IAAI,CACL,CAAC;QACF,IAAI,OAAO,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YACzB,MAAM;QACR,CAAC;QAED,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC7B,eAAe,CACb,EAAE,EACF,KAAK,EACL,kBAAkB,CAChB,MAUC,CACF,CACF,CAAC;QACJ,CAAC;QAED,MAAM,UAAU,GAAG,OAAO,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,CAAC;QAClC,IAAI,CAAC,UAAU,IAAI,OAAO,CAAC,MAAM,GAAG,IAAI,EAAE,CAAC;YACzC,MAAM;QACR,CAAC;QAED,YAAY,GAAG,eAAe,CAAE,UAA6B,CAAC,EAAE,CAAC,CAAC;IACpE,CAAC;AACH,CAAC,CAAC;AAEF,MAAM,oBAAoB,GAAG,CAC3B,EAAa,EACb,KAAkB,EAClB,eAIS,EACH,EAAE;IACR,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;IACxB,KAAK,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CACzC,uBAAuB,CAAC,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,CACnD,CAAC;IAEF,KAAK,MAAM,KAAK,IAAI,KAAK,CAAC,eAAe,EAAE,CAAC;QAC1C,IAAI,CAAC,eAAe,CAAC,KAAK,CAAC,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,EAAE,CAAC;YACjD,SAAS;QACX,CAAC;QAED,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3C,CAAC;IAED,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;AAC7B,CAAC,CAAC;AAUF,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACnC,MAAuB,EACvB,OAAiC,EAC3B,EAAE;IACR,MAAM,EACJ,IAAI,EACJ,eAAe,EACf,KAAK,EACL,MAAM,GAAG,UAAU,EACnB,OAAO,GACR,GAAG,OAAO,CAAC;IAEZ,oEAAoE;IACpE,0EAA0E;IAExE,MAOD,CAAC,GAAG,CACH,UAAU,EACV;QACE,aAAa,EAAE,KAAK,EAClB,OAA0C,EAC1C,KAAyD,EACzD,EAAE;YACF,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,EAAE,KAAK,CAAC;YACnC,IAAI,CAAC,KAAK,EAAE,CAAC;gBACX,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,gBAAgB,EAAE,CAAC,CAAC;gBAClD,OAAO;YACT,CAAC;YACD,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,KAAK,CAAC,CAAC;YAC9C,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,eAAe,EAAE,CAAC,CAAC;YACnD,CAAC;QACH,CAAC;QACD,SAAS,EAAE,IAAI;KAChB,EACD,CAAC,MAAiB,EAAE,EAAE;QACpB,MAAM,YAAY,GAAG,UAAU,EAAE,CAAC;QAClC,MAAM,WAAW,GAAgB;YAC/B,WAAW,EAAE,GAAG;YAChB,aAAa,EAAE,KAAK;YACpB,eAAe,EAAE,EAAE;YACnB,MAAM,EAAE,EAAE;YACV,SAAS,EAAE,KAAK;YAChB,gBAAgB,EAAE,IAAI;YACtB,WAAW,EAAE,IAAI;YACjB,MAAM,EAAE,IAAI;SACb,CAAC;QAEF,MAAM,iBAAiB,GAAG,WAAW,CAAC,GAAG,EAAE;YACzC,IAAI,MAAM,CAAC,UAAU,KAAK,MAAM,CAAC,IAAI,EAAE,CAAC;gBACtC,MAAM,CAAC,IAAI,EAAE,CAAC;YAChB,CAAC;QACH,CAAC,EAAE,qBAAqB,CAAC,CAAC;QAE1B,MAAM,oBAAoB,GAAG,GAAG,EAAE,CAAC,CAAC;YAClC,MAAM,EAAE,YAAY;YACpB,MAAM,EAAE,WAAW,CAAC,MAAM;YAC1B,MAAM,EAAE,WAAW,CAAC,MAAM,IAAI,aAAa,YAAY,EAAE;SAC1D,CAAC,CAAC;QAEH,MAAM,eAAe,GAAG,CACtB,EAAa,EACb,KAAkB,EAClB,MAAwB,EAClB,EAAE;YACR,MAAM,EAAE,MAAM,EAAE,GAAG,MAAM,CAAC;YAC1B,IAAI,iBAAiB,CAAC,MAAM,CAAC,IAAI,iBAAiB,CAAC,KAAK,CAAC,WAAW,CAAC,EAAE,CAAC;gBACtE,OAAO;YACT,CAAC;YAED,KAAK,CAAC,WAAW,GAAG,MAAM,CAAC;YAE3B,MAAM,YAAY,GAAiB;gBACjC,MAAM,EAAE;oBACN,OAAO,EAAE;wBACP;4BACE,MAAM,EAAE,MAAM,CAAC,MAAM;4BACrB,QAAQ,EAAE,MAAM,CAAC,QAAQ;4BACzB,UAAU,EAAE,MAAM,CAAC,UAAU;4BAC7B,SAAS,EAAE,MAAM,CAAC,SAAS,CAAC,WAAW,EAAE;4BACzC,IAAI,EAAE,MAAM,CAAC,IAAI;4BACjB,OAAO,EAAE,MAAM,CAAC,OAAO;4BACvB,OAAO,EAAE,MAAM,CAAC,OAAO;4BACvB,SAAS,EAAE,MAAM,CAAC,SAAS;4BAC3B,MAAM;yBACP;qBACF;oBACD,UAAU,EAAE,MAAM;iBACnB;gBACD,IAAI,EAAE,OAAO;aACd,CAAC;YAEF,IAAI,EAAE,CAAC,UAAU,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC;gBAC9B,EAAE,CAAC,IAAI,CAAC,iBAAiB,CAAC,YAAY,CAAC,CAAC,CAAC;YAC3C,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,eAAe,GAAG,KAAK,EAC3B,EAAa,EACb,KAAkB,EAClB,GAAqB,EACN,EAAE;YACjB,MAAM,eAAe,GAAG,oBAAoB,EAAE,CAAC;YAE/C,iBAAiB,CAAC,KAAK,CAAC,CAAC;YACzB,oBAAoB,CAAC,KAAK,CAAC,CAAC;YAE5B,IAAI,KAAK,EAAE,WAAW,EAAE,CAAC;gBACvB,MAAM,KAAK,CAAC,WAAW,CAAC,EAAE,EAAE,oBAAoB,EAAE,EAAE,eAAe,CAAC,CAAC;YACvE,CAAC;YAED,MAAM,OAAO,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,GAAG,CAAC,KAAK,CAAC,CAAC;YAClD,IAAI,CAAC,OAAO,EAAE,CAAC;gBACb,EAAE,CAAC,IAAI,CAAC,IAAI,CAAC,SAAS,CAAC,EAAE,OAAO,EAAE,eAAe,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC,CAAC,CAAC;gBACrE,OAAO;YACT,CAAC;YAED,MAAM,QAAQ,GAAG,MAAM,OAAO,CAAC,aAAa,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;YAC7D,MAAM,gBAAgB,GAAG,gBAAgB,CAAC;gBACxC,GAAG,QAAQ;gBACX,OAAO,CAAC,MAAM;aACf,CAAC,CAAC;YACH,MAAM,EAAE,MAAM,EAAE,cAAc,EAAE,GAAG,sBAAsB,CACvD,gBAAgB,EAChB,GAAG,CAAC,MAAM,CACX,CAAC;YAEF,IAAI,cAAc,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC;gBAC9B,MAAM,CAAC,IAAI,CACT;oBACE,gBAAgB;oBAChB,eAAe,EAAE,cAAc;oBAC/B,MAAM,EAAE,OAAO,CAAC,MAAM;iBACvB,EACD,6CAA6C,CAC9C,CAAC;YACJ,CAAC;YAED,kBAAkB,CAChB,KAAK,EACL,OAAO,CAAC,MAAM,EACd,MAAM,EACN,GAAG,CAAC,WAAW,IAAI,GAAG,CACvB,CAAC;YACF,wBAAwB,CAAC,EAAE,EAAE,KAAK,EAAE,eAAe,EAAE,eAAe,CAAC,CAAC;YACtE,qBAAqB,CAAC,EAAE,EAAE,KAAK,CAAC,CAAC;YACjC,IAAI,CAAC;gBACH,MAAM,iBAAiB,CAAC,OAAO,EAAE,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;gBAC7D,oBAAoB,CAAC,EAAE,EAAE,KAAK,EAAE,eAAe,CAAC,CAAC;YACnD,CAAC;YAAC,OAAO,WAAW,EAAE,CAAC;gBACrB,KAAK,CAAC,SAAS,GAAG,KAAK,CAAC;gBACxB,KAAK,CAAC,eAAe,GAAG,EAAE,CAAC;gBAC3B,MAAM,WAAW,CAAC;YACpB,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,sBAAsB,GAAG,KAAK,EAClC,EAAa,EACb,KAAkB,EAClB,GAAqB,EACN,EAAE;YACjB,IAAI,KAAK,CAAC,gBAAgB,EAAE,CAAC;gBAC3B,MAAM,KAAK,CAAC,gBAAgB,CAAC;YAC/B,CAAC;YAED,MAAM,gBAAgB,GAAG,eAAe,CAAC,EAAE,EAAE,KAAK,EAAE,GAAG,CAAC,CAAC;YACzD,KAAK,CAAC,gBAAgB,GAAG,gBAAgB,CAAC;YAE1C,IAAI,CAAC;gBACH,MAAM,gBAAgB,CAAC;YACzB,CAAC;oBAAS,CAAC;gBACT,IAAI,KAAK,CAAC,gBAAgB,KAAK,gBAAgB,EAAE,CAAC;oBAChD,KAAK,CAAC,gBAAgB,GAAG,IAAI,CAAC;gBAChC,CAAC;YACH,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,yBAAyB,GAAG,KAAK,EACrC,OAAgC,EACd,EAAE;YACpB,IAAI,kBAAkB,CAAC,OAAO,CAAC,EAAE,CAAC;gBAChC,MAAM,sBAAsB,CAAC,MAAM,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC;gBAC3D,OAAO,IAAI,CAAC;YACd,CAAC;YAED,IAAI,OAAO,CAAC,IAAI,KAAK,WAAW,EAAE,CAAC;gBACjC,MAAM,CAAC,IAAI,CACT,IAAI,CAAC,SAAS,CAAC;oBACb,OAAO,EAAE,yBAAyB;oBAClC,IAAI,EAAE,OAAO;iBACd,CAAC,CACH,CAAC;gBACF,OAAO,IAAI,CAAC;YACd,CAAC;YAED,OAAO,KAAK,CAAC;QACf,CAAC,CAAC;QAEF,MAAM,sBAAsB,GAAG,KAAK,EAClC,OAAgC,EACd,EAAE;YACpB,IAAI,CAAC,KAAK,EAAE,SAAS,EAAE,CAAC;gBACtB,OAAO,KAAK,CAAC;YACf,CAAC;YAED,IAAI,CAAC,WAAW,CAAC,aAAa,EAAE,CAAC;gBAC/B,OAAO,KAAK,CAAC;YACf,CAAC;YAED,OAAO,MAAM,KAAK,CAAC,SAAS,CAAC,MAAM,EAAE,OAAO,EAAE,oBAAoB,EAAE,CAAC,CAAC;QACxE,CAAC,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,SAAS,EAAE,KAAK,EAAE,IAAqC,EAAE,EAAE;YACnE,IAAI,CAAC;gBACH,MAAM,OAAO,GAAY,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,QAAQ,EAAE,CAAC,CAAC;gBACrD,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;oBACvB,OAAO;gBACT,CAAC;gBAED,IAAI,MAAM,yBAAyB,CAAC,OAAO,CAAC,EAAE,CAAC;oBAC7C,OAAO;gBACT,CAAC;gBAED,IAAI,WAAW,CAAC,gBAAgB,EAAE,CAAC;oBACjC,MAAM,WAAW,CAAC,gBAAgB,CAAC;gBACrC,CAAC;gBAED,MAAM,sBAAsB,CAAC,OAAO,CAAC,CAAC;YACxC,CAAC;YAAC,OAAO,KAAK,EAAE,CAAC;gBACf,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,kCAAkC,CAAC,CAAC;YAC7D,CAAC;QACH,CAAC,CAAC,CAAC;QAEH,MAAM,cAAc,GAAG,KAAK,IAAmB,EAAE;YAC/C,IAAI,CAAC,KAAK,EAAE,OAAO,EAAE,CAAC;gBACpB,OAAO;YACT,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,KAAK,CAAC,OAAO,CAAC,MAAM,EAAE,oBAAoB,EAAE,CAAC,CAAC;YACtD,CAAC;YAAC,MAAM,CAAC;gBACP,mDAAmD;YACrD,CAAC;QACH,CAAC,CAAC;QAEF,MAAM,gBAAgB,GAAG,GAAS,EAAE;YAClC,aAAa,CAAC,iBAAiB,CAAC,CAAC;YACjC,IAAI,WAAW,CAAC,WAAW,EAAE,CAAC;gBAC5B,WAAW,CAAC,WAAW,EAAE,CAAC;gBAC1B,WAAW,CAAC,WAAW,GAAG,IAAI,CAAC;YACjC,CAAC;YACD,cAAc,EAAE,CAAC;QACnB,CAAC,CAAC;QAEF,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,gBAAgB,CAAC,CAAC;QAErC,MAAM,CAAC,EAAE,CAAC,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YAC3B,MAAM,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,EAAE,iBAAiB,CAAC,CAAC;YAC1C,gBAAgB,EAAE,CAAC;QACrB,CAAC,CAAC,CAAC;IACL,CAAC,CACF,CAAC;AACJ,CAAC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@stratasync/server",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
},
|
|
15
|
+
"./fastify": {
|
|
16
|
+
"types": "./dist/fastify/index.d.ts",
|
|
17
|
+
"import": "./dist/fastify/index.js"
|
|
18
|
+
}
|
|
19
|
+
},
|
|
20
|
+
"publishConfig": {
|
|
21
|
+
"access": "public"
|
|
22
|
+
},
|
|
23
|
+
"scripts": {
|
|
24
|
+
"build": "tsc",
|
|
25
|
+
"dev": "tsc --watch",
|
|
26
|
+
"lint": "oxlint .",
|
|
27
|
+
"lint:fix": "oxlint --fix .",
|
|
28
|
+
"format": "oxfmt --write .",
|
|
29
|
+
"format:check": "oxfmt --check .",
|
|
30
|
+
"check-types": "tsc --noEmit",
|
|
31
|
+
"test": "vitest run"
|
|
32
|
+
},
|
|
33
|
+
"dependencies": {
|
|
34
|
+
"uuid": "^13.0.0",
|
|
35
|
+
"zod": "^4.3.6"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@types/node": "^25.5.0",
|
|
39
|
+
"@types/ws": "^8.18.1",
|
|
40
|
+
"fastify": "^5.8.2",
|
|
41
|
+
"lefthook": "^2.1.4",
|
|
42
|
+
"oxfmt": "^0.41.0",
|
|
43
|
+
"oxlint": "^1.56.0",
|
|
44
|
+
"redis": "^5.11.0",
|
|
45
|
+
"typescript": "^5.9.3",
|
|
46
|
+
"ultracite": "^7.3.2",
|
|
47
|
+
"vitest": "^4.1.0",
|
|
48
|
+
"ws": "^8.19.0"
|
|
49
|
+
},
|
|
50
|
+
"peerDependencies": {
|
|
51
|
+
"@fastify/websocket": ">=11.0.0",
|
|
52
|
+
"drizzle-orm": ">=1.0.0-beta.1",
|
|
53
|
+
"fastify": ">=5.0.0",
|
|
54
|
+
"redis": ">=5.0.0"
|
|
55
|
+
},
|
|
56
|
+
"peerDependenciesMeta": {
|
|
57
|
+
"fastify": {
|
|
58
|
+
"optional": true
|
|
59
|
+
},
|
|
60
|
+
"redis": {
|
|
61
|
+
"optional": true
|
|
62
|
+
},
|
|
63
|
+
"@fastify/websocket": {
|
|
64
|
+
"optional": true
|
|
65
|
+
},
|
|
66
|
+
"drizzle-orm": {
|
|
67
|
+
"optional": false
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
}
|