@sylphx/lens-server 1.11.3 → 2.0.1
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/index.d.ts +1244 -260
- package/dist/index.js +1700 -1158
- package/package.json +2 -2
- package/src/context/index.test.ts +425 -0
- package/src/context/index.ts +90 -0
- package/src/e2e/server.test.ts +215 -433
- package/src/handlers/framework.ts +294 -0
- package/src/handlers/http.test.ts +215 -0
- package/src/handlers/http.ts +189 -0
- package/src/handlers/index.ts +55 -0
- package/src/handlers/unified.ts +114 -0
- package/src/handlers/ws-types.ts +126 -0
- package/src/handlers/ws.ts +669 -0
- package/src/index.ts +127 -24
- package/src/plugin/index.ts +41 -0
- package/src/plugin/op-log.ts +286 -0
- package/src/plugin/optimistic.ts +375 -0
- package/src/plugin/types.ts +551 -0
- package/src/reconnect/index.ts +9 -0
- package/src/reconnect/operation-log.test.ts +480 -0
- package/src/reconnect/operation-log.ts +450 -0
- package/src/server/create.test.ts +256 -2193
- package/src/server/create.ts +285 -1481
- package/src/server/dataloader.ts +60 -0
- package/src/server/selection.ts +44 -0
- package/src/server/types.ts +289 -0
- package/src/sse/handler.ts +123 -56
- package/src/state/index.ts +9 -11
- package/src/storage/index.ts +26 -0
- package/src/storage/memory.ts +279 -0
- package/src/storage/types.ts +205 -0
- package/src/state/graph-state-manager.test.ts +0 -1105
- package/src/state/graph-state-manager.ts +0 -890
package/src/index.ts
CHANGED
|
@@ -2,7 +2,35 @@
|
|
|
2
2
|
* @sylphx/lens-server
|
|
3
3
|
*
|
|
4
4
|
* Server runtime for Lens API framework.
|
|
5
|
-
*
|
|
5
|
+
*
|
|
6
|
+
* Architecture:
|
|
7
|
+
* - App = Executor with optional plugin support
|
|
8
|
+
* - Stateless (default): Pure executor
|
|
9
|
+
* - Stateful (with opLog): Cursor-based state synchronization
|
|
10
|
+
* - Handlers = Pure protocol handlers (HTTP, WebSocket, SSE)
|
|
11
|
+
* - No business logic - just translate protocol to app calls
|
|
12
|
+
* - Plugins = App-level middleware (opLog, auth, logger)
|
|
13
|
+
* - Configured at app level, not handler level
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* // Stateless mode (default)
|
|
18
|
+
* const app = createApp({ router });
|
|
19
|
+
* const wsHandler = createWSHandler(app);
|
|
20
|
+
*
|
|
21
|
+
* // With opLog plugin (cursor-based state sync)
|
|
22
|
+
* const app = createApp({
|
|
23
|
+
* router,
|
|
24
|
+
* plugins: [opLog()],
|
|
25
|
+
* });
|
|
26
|
+
*
|
|
27
|
+
* // With external storage for serverless (install @sylphx/lens-storage-upstash)
|
|
28
|
+
* import { upstashStorage } from "@sylphx/lens-storage-upstash";
|
|
29
|
+
* const app = createApp({
|
|
30
|
+
* router,
|
|
31
|
+
* plugins: [opLog({ storage: upstashStorage({ redis }) })],
|
|
32
|
+
* });
|
|
33
|
+
* ```
|
|
6
34
|
*/
|
|
7
35
|
|
|
8
36
|
// =============================================================================
|
|
@@ -25,21 +53,34 @@ export {
|
|
|
25
53
|
} from "@sylphx/lens-core";
|
|
26
54
|
|
|
27
55
|
// =============================================================================
|
|
28
|
-
// Server
|
|
56
|
+
// Context System (Server-side implementation)
|
|
57
|
+
// =============================================================================
|
|
58
|
+
|
|
59
|
+
export {
|
|
60
|
+
createContext,
|
|
61
|
+
extendContext,
|
|
62
|
+
hasContext,
|
|
63
|
+
runWithContext,
|
|
64
|
+
runWithContextAsync,
|
|
65
|
+
tryUseContext,
|
|
66
|
+
useContext,
|
|
67
|
+
} from "./context/index.js";
|
|
68
|
+
|
|
69
|
+
// =============================================================================
|
|
70
|
+
// Server (Pure Executor)
|
|
29
71
|
// =============================================================================
|
|
30
72
|
|
|
31
73
|
export {
|
|
74
|
+
// Types
|
|
75
|
+
type ClientSendFn,
|
|
32
76
|
// Factory
|
|
33
|
-
|
|
77
|
+
createApp,
|
|
34
78
|
type EntitiesMap,
|
|
35
|
-
// Type inference utilities (tRPC-style)
|
|
36
79
|
type InferApi,
|
|
37
80
|
type InferInput,
|
|
38
81
|
type InferOutput,
|
|
39
|
-
// In-process transport types
|
|
40
82
|
type LensOperation,
|
|
41
83
|
type LensResult,
|
|
42
|
-
// Types
|
|
43
84
|
type LensServer,
|
|
44
85
|
type LensServerConfig as ServerConfig,
|
|
45
86
|
type MutationsMap,
|
|
@@ -47,39 +88,101 @@ export {
|
|
|
47
88
|
type OperationsMap,
|
|
48
89
|
type QueriesMap,
|
|
49
90
|
type SelectionObject,
|
|
50
|
-
// Metadata types (for transport handshake)
|
|
51
91
|
type ServerMetadata,
|
|
52
92
|
type WebSocketLike,
|
|
53
93
|
} from "./server/create.js";
|
|
54
94
|
|
|
55
95
|
// =============================================================================
|
|
56
|
-
//
|
|
96
|
+
// Protocol Handlers
|
|
97
|
+
// =============================================================================
|
|
98
|
+
|
|
99
|
+
export {
|
|
100
|
+
// Framework Handler Utilities
|
|
101
|
+
createFrameworkHandler,
|
|
102
|
+
// Unified Handler (HTTP + SSE)
|
|
103
|
+
createHandler,
|
|
104
|
+
// HTTP Handler
|
|
105
|
+
createHTTPHandler,
|
|
106
|
+
createServerClientProxy,
|
|
107
|
+
// SSE Handler
|
|
108
|
+
createSSEHandler,
|
|
109
|
+
// WebSocket Handler
|
|
110
|
+
createWSHandler,
|
|
111
|
+
type FrameworkHandlerOptions,
|
|
112
|
+
type Handler,
|
|
113
|
+
type HandlerOptions,
|
|
114
|
+
type HTTPHandler,
|
|
115
|
+
type HTTPHandlerOptions,
|
|
116
|
+
handleWebMutation,
|
|
117
|
+
handleWebQuery,
|
|
118
|
+
handleWebSSE,
|
|
119
|
+
type SSEHandlerOptions,
|
|
120
|
+
type WSHandler,
|
|
121
|
+
type WSHandlerOptions,
|
|
122
|
+
} from "./handlers/index.js";
|
|
123
|
+
|
|
124
|
+
// =============================================================================
|
|
125
|
+
// Plugin System
|
|
126
|
+
// =============================================================================
|
|
127
|
+
|
|
128
|
+
export {
|
|
129
|
+
// Context types
|
|
130
|
+
type AfterMutationContext,
|
|
131
|
+
type AfterSendContext,
|
|
132
|
+
type BeforeMutationContext,
|
|
133
|
+
type BeforeSendContext,
|
|
134
|
+
// Operation Log Plugin (cursor-based state management)
|
|
135
|
+
type BroadcastResult,
|
|
136
|
+
type ConnectContext,
|
|
137
|
+
// Plugin manager
|
|
138
|
+
createPluginManager,
|
|
139
|
+
type DisconnectContext,
|
|
140
|
+
type EnhanceOperationMetaContext,
|
|
141
|
+
isOpLogPlugin,
|
|
142
|
+
// Optimistic Plugin
|
|
143
|
+
isOptimisticPlugin,
|
|
144
|
+
type OpLogOptions,
|
|
145
|
+
type OpLogPlugin,
|
|
146
|
+
type OptimisticPluginOptions,
|
|
147
|
+
opLog,
|
|
148
|
+
optimisticPlugin,
|
|
149
|
+
PluginManager,
|
|
150
|
+
// Plugin interface
|
|
151
|
+
type ServerPlugin,
|
|
152
|
+
type SubscribeContext,
|
|
153
|
+
type UnsubscribeContext,
|
|
154
|
+
} from "./plugin/index.js";
|
|
155
|
+
|
|
156
|
+
// =============================================================================
|
|
157
|
+
// Storage (for opLog plugin)
|
|
57
158
|
// =============================================================================
|
|
58
159
|
|
|
59
160
|
export {
|
|
60
|
-
// Factory
|
|
61
|
-
createGraphStateManager,
|
|
62
161
|
// Types
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
type
|
|
68
|
-
type
|
|
69
|
-
type
|
|
70
|
-
type
|
|
71
|
-
} from "./
|
|
162
|
+
DEFAULT_STORAGE_CONFIG,
|
|
163
|
+
type EmitResult,
|
|
164
|
+
// In-memory (default)
|
|
165
|
+
memoryStorage,
|
|
166
|
+
type OpLogStorage,
|
|
167
|
+
type OpLogStorageConfig,
|
|
168
|
+
type StoredEntityState,
|
|
169
|
+
type StoredPatchEntry,
|
|
170
|
+
} from "./storage/index.js";
|
|
72
171
|
|
|
73
172
|
// =============================================================================
|
|
74
|
-
// SSE
|
|
173
|
+
// SSE Handler (additional exports not in handlers/index.js)
|
|
75
174
|
// =============================================================================
|
|
76
175
|
|
|
77
176
|
export {
|
|
78
|
-
//
|
|
79
|
-
|
|
80
|
-
type SSEClientInfo,
|
|
177
|
+
// Types
|
|
178
|
+
type SSEClient,
|
|
81
179
|
// Class
|
|
82
180
|
SSEHandler,
|
|
83
|
-
// Types
|
|
84
181
|
type SSEHandlerConfig,
|
|
85
182
|
} from "./sse/handler.js";
|
|
183
|
+
|
|
184
|
+
// =============================================================================
|
|
185
|
+
// Reconnection (Server-side)
|
|
186
|
+
// =============================================================================
|
|
187
|
+
|
|
188
|
+
export { coalescePatches, estimatePatchSize, OperationLog } from "./reconnect/index.js";
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-server - Plugin System
|
|
3
|
+
*
|
|
4
|
+
* Export all plugin-related types and utilities.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
// Operation Log Plugin (cursor-based state management)
|
|
8
|
+
export {
|
|
9
|
+
type BroadcastResult,
|
|
10
|
+
isOpLogPlugin,
|
|
11
|
+
type OpLogOptions,
|
|
12
|
+
type OpLogPlugin,
|
|
13
|
+
opLog,
|
|
14
|
+
} from "./op-log.js";
|
|
15
|
+
|
|
16
|
+
// Optimistic Updates Plugin
|
|
17
|
+
export {
|
|
18
|
+
isOptimisticPlugin,
|
|
19
|
+
type OptimisticPlugin,
|
|
20
|
+
type OptimisticPluginOptions,
|
|
21
|
+
optimisticPlugin,
|
|
22
|
+
} from "./optimistic.js";
|
|
23
|
+
|
|
24
|
+
export {
|
|
25
|
+
// Context types
|
|
26
|
+
type AfterMutationContext,
|
|
27
|
+
type AfterSendContext,
|
|
28
|
+
type BeforeMutationContext,
|
|
29
|
+
type BeforeSendContext,
|
|
30
|
+
type BroadcastContext,
|
|
31
|
+
type ConnectContext,
|
|
32
|
+
// Plugin manager
|
|
33
|
+
createPluginManager,
|
|
34
|
+
type DisconnectContext,
|
|
35
|
+
type EnhanceOperationMetaContext,
|
|
36
|
+
PluginManager,
|
|
37
|
+
// Plugin interface
|
|
38
|
+
type ServerPlugin,
|
|
39
|
+
type SubscribeContext,
|
|
40
|
+
type UnsubscribeContext,
|
|
41
|
+
} from "./types.js";
|
|
@@ -0,0 +1,286 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @sylphx/lens-server - Operation Log Plugin
|
|
3
|
+
*
|
|
4
|
+
* Server-side plugin for cursor-based state synchronization.
|
|
5
|
+
* Provides:
|
|
6
|
+
* - Canonical state per entity (server truth)
|
|
7
|
+
* - Version tracking (cursor-based)
|
|
8
|
+
* - Operation log for efficient reconnection
|
|
9
|
+
* - Patch computation
|
|
10
|
+
*
|
|
11
|
+
* This plugin ONLY handles state management.
|
|
12
|
+
* Subscription routing is handled by the handler layer.
|
|
13
|
+
*
|
|
14
|
+
* Memory: O(entities × history) - does not scale with client count
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* // Default (in-memory storage)
|
|
19
|
+
* const server = createApp({
|
|
20
|
+
* router: appRouter,
|
|
21
|
+
* plugins: [opLog()],
|
|
22
|
+
* });
|
|
23
|
+
*
|
|
24
|
+
* // With external storage for serverless
|
|
25
|
+
* const server = createApp({
|
|
26
|
+
* router: appRouter,
|
|
27
|
+
* plugins: [opLog({
|
|
28
|
+
* storage: redisStorage({ url: process.env.REDIS_URL }),
|
|
29
|
+
* })],
|
|
30
|
+
* });
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
import type { PatchOperation } from "@sylphx/lens-core";
|
|
35
|
+
import { memoryStorage, type OpLogStorage, type OpLogStorageConfig } from "../storage/index.js";
|
|
36
|
+
import type {
|
|
37
|
+
BroadcastContext,
|
|
38
|
+
ReconnectContext,
|
|
39
|
+
ReconnectHookResult,
|
|
40
|
+
ServerPlugin,
|
|
41
|
+
} from "./types.js";
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Operation log plugin configuration.
|
|
45
|
+
*/
|
|
46
|
+
export interface OpLogOptions extends OpLogStorageConfig {
|
|
47
|
+
/**
|
|
48
|
+
* Storage adapter for state/version/patches.
|
|
49
|
+
* Defaults to in-memory storage.
|
|
50
|
+
*
|
|
51
|
+
* @example
|
|
52
|
+
* ```typescript
|
|
53
|
+
* // In-memory (default)
|
|
54
|
+
* opLog()
|
|
55
|
+
*
|
|
56
|
+
* // Redis for serverless
|
|
57
|
+
* opLog({ storage: redisStorage({ url: REDIS_URL }) })
|
|
58
|
+
* ```
|
|
59
|
+
*/
|
|
60
|
+
storage?: OpLogStorage;
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Whether to enable debug logging.
|
|
64
|
+
* @default false
|
|
65
|
+
*/
|
|
66
|
+
debug?: boolean;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Broadcast result returned by the plugin.
|
|
71
|
+
* Handler uses this to send updates to subscribers.
|
|
72
|
+
*/
|
|
73
|
+
export interface BroadcastResult {
|
|
74
|
+
/** Current version after update */
|
|
75
|
+
version: number;
|
|
76
|
+
/** Patch operations (null if first emit or log evicted) */
|
|
77
|
+
patch: PatchOperation[] | null;
|
|
78
|
+
/** Full data (for initial sends or when patch unavailable) */
|
|
79
|
+
data: Record<string, unknown>;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/**
|
|
83
|
+
* OpLog plugin instance type.
|
|
84
|
+
*/
|
|
85
|
+
export interface OpLogPlugin extends ServerPlugin {
|
|
86
|
+
/** Get the storage adapter */
|
|
87
|
+
getStorage(): OpLogStorage;
|
|
88
|
+
/** Get version for an entity (async) */
|
|
89
|
+
getVersion(entity: string, entityId: string): Promise<number>;
|
|
90
|
+
/** Get current canonical state for an entity (async) */
|
|
91
|
+
getState(entity: string, entityId: string): Promise<Record<string, unknown> | null>;
|
|
92
|
+
/** Get latest patch for an entity (async) */
|
|
93
|
+
getLatestPatch(entity: string, entityId: string): Promise<PatchOperation[] | null>;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* Create an operation log plugin.
|
|
98
|
+
*
|
|
99
|
+
* This plugin provides cursor-based state synchronization:
|
|
100
|
+
* - Canonical state per entity (server truth)
|
|
101
|
+
* - Version tracking for cursor-based sync
|
|
102
|
+
* - Operation log for efficient reconnection (patches or snapshot)
|
|
103
|
+
*
|
|
104
|
+
* This plugin does NOT handle subscription routing - that's the handler's job.
|
|
105
|
+
* Memory: O(entities × history) - does not scale with client count.
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* // Default (in-memory)
|
|
110
|
+
* const server = createApp({
|
|
111
|
+
* router: appRouter,
|
|
112
|
+
* plugins: [opLog()],
|
|
113
|
+
* });
|
|
114
|
+
*
|
|
115
|
+
* // With Redis for serverless
|
|
116
|
+
* const server = createApp({
|
|
117
|
+
* router: appRouter,
|
|
118
|
+
* plugins: [opLog({
|
|
119
|
+
* storage: redisStorage({ url: process.env.REDIS_URL }),
|
|
120
|
+
* })],
|
|
121
|
+
* });
|
|
122
|
+
* ```
|
|
123
|
+
*/
|
|
124
|
+
export function opLog(options: OpLogOptions = {}): OpLogPlugin {
|
|
125
|
+
const storage = options.storage ?? memoryStorage(options);
|
|
126
|
+
const debug = options.debug ?? false;
|
|
127
|
+
|
|
128
|
+
const log = (...args: unknown[]) => {
|
|
129
|
+
if (debug) {
|
|
130
|
+
console.log("[opLog]", ...args);
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
|
|
134
|
+
return {
|
|
135
|
+
name: "opLog",
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Get the storage adapter.
|
|
139
|
+
*/
|
|
140
|
+
getStorage(): OpLogStorage {
|
|
141
|
+
return storage;
|
|
142
|
+
},
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Get version for an entity.
|
|
146
|
+
*/
|
|
147
|
+
async getVersion(entity: string, entityId: string): Promise<number> {
|
|
148
|
+
return storage.getVersion(entity, entityId);
|
|
149
|
+
},
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Get current canonical state for an entity.
|
|
153
|
+
*/
|
|
154
|
+
async getState(entity: string, entityId: string): Promise<Record<string, unknown> | null> {
|
|
155
|
+
return storage.getState(entity, entityId);
|
|
156
|
+
},
|
|
157
|
+
|
|
158
|
+
/**
|
|
159
|
+
* Get latest patch for an entity.
|
|
160
|
+
*/
|
|
161
|
+
async getLatestPatch(entity: string, entityId: string): Promise<PatchOperation[] | null> {
|
|
162
|
+
return storage.getLatestPatch(entity, entityId);
|
|
163
|
+
},
|
|
164
|
+
|
|
165
|
+
/**
|
|
166
|
+
* Handle broadcast - update canonical state and return patch info.
|
|
167
|
+
* Handler is responsible for routing to subscribers.
|
|
168
|
+
*/
|
|
169
|
+
async onBroadcast(ctx: BroadcastContext): Promise<BroadcastResult> {
|
|
170
|
+
const { entity, entityId, data } = ctx;
|
|
171
|
+
|
|
172
|
+
log("onBroadcast:", entity, entityId);
|
|
173
|
+
|
|
174
|
+
// Update canonical state (computes and logs patch)
|
|
175
|
+
const result = await storage.emit(entity, entityId, data);
|
|
176
|
+
|
|
177
|
+
log(" Version:", result.version, "Patch ops:", result.patch?.length ?? 0);
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
version: result.version,
|
|
181
|
+
patch: result.patch,
|
|
182
|
+
data,
|
|
183
|
+
};
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
/**
|
|
187
|
+
* Handle reconnection - return patches or snapshot based on client's version.
|
|
188
|
+
*/
|
|
189
|
+
async onReconnect(ctx: ReconnectContext): Promise<ReconnectHookResult[]> {
|
|
190
|
+
log("Reconnect:", ctx.clientId, "subscriptions:", ctx.subscriptions.length);
|
|
191
|
+
|
|
192
|
+
const results: ReconnectHookResult[] = [];
|
|
193
|
+
|
|
194
|
+
for (const sub of ctx.subscriptions) {
|
|
195
|
+
const currentVersion = await storage.getVersion(sub.entity, sub.entityId);
|
|
196
|
+
const currentState = await storage.getState(sub.entity, sub.entityId);
|
|
197
|
+
|
|
198
|
+
// Entity doesn't exist (might have been deleted)
|
|
199
|
+
if (currentState === null) {
|
|
200
|
+
results.push({
|
|
201
|
+
id: sub.id,
|
|
202
|
+
entity: sub.entity,
|
|
203
|
+
entityId: sub.entityId,
|
|
204
|
+
status: "deleted",
|
|
205
|
+
version: 0,
|
|
206
|
+
});
|
|
207
|
+
log(" Subscription", sub.id, `${sub.entity}:${sub.entityId}`, "status: deleted");
|
|
208
|
+
continue;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Client is already at latest version
|
|
212
|
+
if (sub.version >= currentVersion) {
|
|
213
|
+
results.push({
|
|
214
|
+
id: sub.id,
|
|
215
|
+
entity: sub.entity,
|
|
216
|
+
entityId: sub.entityId,
|
|
217
|
+
status: "current",
|
|
218
|
+
version: currentVersion,
|
|
219
|
+
});
|
|
220
|
+
log(
|
|
221
|
+
" Subscription",
|
|
222
|
+
sub.id,
|
|
223
|
+
`${sub.entity}:${sub.entityId}`,
|
|
224
|
+
"status: current",
|
|
225
|
+
"version:",
|
|
226
|
+
currentVersion,
|
|
227
|
+
);
|
|
228
|
+
continue;
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Try to get patches from operation log
|
|
232
|
+
const patches = await storage.getPatchesSince(sub.entity, sub.entityId, sub.version);
|
|
233
|
+
|
|
234
|
+
if (patches !== null && patches.length > 0) {
|
|
235
|
+
// Can patch - return patches
|
|
236
|
+
results.push({
|
|
237
|
+
id: sub.id,
|
|
238
|
+
entity: sub.entity,
|
|
239
|
+
entityId: sub.entityId,
|
|
240
|
+
status: "patched",
|
|
241
|
+
version: currentVersion,
|
|
242
|
+
patches,
|
|
243
|
+
});
|
|
244
|
+
log(
|
|
245
|
+
" Subscription",
|
|
246
|
+
sub.id,
|
|
247
|
+
`${sub.entity}:${sub.entityId}`,
|
|
248
|
+
"status: patched",
|
|
249
|
+
"version:",
|
|
250
|
+
currentVersion,
|
|
251
|
+
"patches:",
|
|
252
|
+
patches.length,
|
|
253
|
+
);
|
|
254
|
+
continue;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Patches not available - send full snapshot
|
|
258
|
+
results.push({
|
|
259
|
+
id: sub.id,
|
|
260
|
+
entity: sub.entity,
|
|
261
|
+
entityId: sub.entityId,
|
|
262
|
+
status: "snapshot",
|
|
263
|
+
version: currentVersion,
|
|
264
|
+
data: currentState,
|
|
265
|
+
});
|
|
266
|
+
log(
|
|
267
|
+
" Subscription",
|
|
268
|
+
sub.id,
|
|
269
|
+
`${sub.entity}:${sub.entityId}`,
|
|
270
|
+
"status: snapshot",
|
|
271
|
+
"version:",
|
|
272
|
+
currentVersion,
|
|
273
|
+
);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
return results;
|
|
277
|
+
},
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Check if a plugin is an opLog plugin.
|
|
283
|
+
*/
|
|
284
|
+
export function isOpLogPlugin(plugin: ServerPlugin): plugin is OpLogPlugin {
|
|
285
|
+
return plugin.name === "opLog" && "getStorage" in plugin;
|
|
286
|
+
}
|