rwsdk 1.0.0-beta.5 → 1.0.0-beta.50
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/bin/rw-scripts.mjs +13 -13
- package/dist/lib/constants.d.mts +1 -0
- package/dist/lib/constants.mjs +7 -4
- package/dist/lib/e2e/browser.mjs +6 -2
- package/dist/lib/e2e/constants.d.mts +4 -0
- package/dist/lib/e2e/constants.mjs +49 -12
- package/dist/lib/e2e/dev.mjs +49 -57
- package/dist/lib/e2e/environment.d.mts +2 -0
- package/dist/lib/e2e/environment.mjs +201 -64
- package/dist/lib/e2e/index.d.mts +2 -0
- package/dist/lib/e2e/index.mjs +2 -0
- package/dist/lib/e2e/poll.d.mts +1 -1
- package/dist/lib/e2e/release.d.mts +1 -0
- package/dist/lib/e2e/release.mjs +57 -52
- package/dist/lib/e2e/tarball.mjs +2 -34
- package/dist/lib/e2e/testHarness.d.mts +39 -3
- package/dist/lib/e2e/testHarness.mjs +239 -92
- package/dist/lib/e2e/utils.d.mts +1 -0
- package/dist/lib/e2e/utils.mjs +15 -0
- package/dist/lib/normalizeModulePath.mjs +1 -1
- package/dist/runtime/client/client.d.ts +64 -2
- package/dist/runtime/client/client.js +156 -15
- package/dist/runtime/client/navigation.d.ts +45 -0
- package/dist/runtime/client/navigation.js +68 -14
- package/dist/runtime/client/navigationCache.d.ts +68 -0
- package/dist/runtime/client/navigationCache.js +294 -0
- package/dist/runtime/client/navigationCache.test.js +469 -0
- package/dist/runtime/client/types.d.ts +26 -5
- package/dist/runtime/client/types.js +8 -1
- package/dist/runtime/entries/no-react-server-ssr-bridge.d.ts +0 -0
- package/dist/runtime/entries/no-react-server-ssr-bridge.js +2 -0
- package/dist/runtime/entries/no-react-server.js +3 -1
- package/dist/runtime/entries/react-server-only.js +1 -1
- package/dist/runtime/entries/router.d.ts +1 -0
- package/dist/runtime/entries/routerClient.d.ts +1 -0
- package/dist/runtime/entries/routerClient.js +1 -0
- package/dist/runtime/entries/worker.d.ts +4 -0
- package/dist/runtime/entries/worker.js +4 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.d.ts +6 -0
- package/dist/runtime/imports/__mocks__/use-client-lookup.js +6 -0
- package/dist/runtime/lib/db/SqliteDurableObject.d.ts +2 -2
- package/dist/runtime/lib/db/SqliteDurableObject.js +2 -2
- package/dist/runtime/lib/db/createDb.d.ts +1 -2
- package/dist/runtime/lib/db/createDb.js +4 -0
- package/dist/runtime/lib/db/typeInference/builders/alterTable.d.ts +13 -3
- package/dist/runtime/lib/db/typeInference/builders/columnDefinition.d.ts +35 -21
- package/dist/runtime/lib/db/typeInference/builders/createTable.d.ts +9 -2
- package/dist/runtime/lib/db/typeInference/database.d.ts +16 -2
- package/dist/runtime/lib/db/typeInference/typetests/alterTable.typetest.js +80 -5
- package/dist/runtime/lib/db/typeInference/typetests/createTable.typetest.js +104 -2
- package/dist/runtime/lib/db/typeInference/typetests/testUtils.d.ts +1 -0
- package/dist/runtime/lib/db/typeInference/utils.d.ts +59 -9
- package/dist/runtime/lib/links.d.ts +21 -7
- package/dist/runtime/lib/links.js +84 -26
- package/dist/runtime/lib/links.test.d.ts +1 -0
- package/dist/runtime/lib/links.test.js +20 -0
- package/dist/runtime/lib/manifest.d.ts +1 -1
- package/dist/runtime/lib/manifest.js +7 -4
- package/dist/runtime/lib/realtime/client.js +28 -6
- package/dist/runtime/lib/realtime/worker.d.ts +1 -1
- package/dist/runtime/lib/router.d.ts +154 -35
- package/dist/runtime/lib/router.js +491 -105
- package/dist/runtime/lib/router.test.js +611 -1
- package/dist/runtime/lib/stitchDocumentAndAppStreams.d.ts +66 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.js +302 -35
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.d.ts +1 -0
- package/dist/runtime/lib/stitchDocumentAndAppStreams.test.js +418 -0
- package/dist/runtime/lib/{rwContext.d.ts → types.d.ts} +1 -0
- package/dist/runtime/lib/types.js +1 -0
- package/dist/runtime/register/client.d.ts +1 -1
- package/dist/runtime/register/client.js +10 -3
- package/dist/runtime/register/worker.js +13 -4
- package/dist/runtime/render/normalizeActionResult.js +8 -1
- package/dist/runtime/render/renderDocumentHtmlStream.d.ts +1 -1
- package/dist/runtime/render/renderToStream.d.ts +4 -2
- package/dist/runtime/render/renderToStream.js +53 -24
- package/dist/runtime/render/renderToString.d.ts +3 -6
- package/dist/runtime/requestInfo/types.d.ts +5 -1
- package/dist/runtime/requestInfo/utils.d.ts +9 -0
- package/dist/runtime/requestInfo/utils.js +45 -0
- package/dist/runtime/requestInfo/worker.d.ts +0 -1
- package/dist/runtime/requestInfo/worker.js +5 -11
- package/dist/runtime/script.d.ts +1 -3
- package/dist/runtime/script.js +1 -10
- package/dist/runtime/server.d.ts +52 -0
- package/dist/runtime/server.js +88 -0
- package/dist/runtime/state.d.ts +3 -0
- package/dist/runtime/state.js +13 -0
- package/dist/runtime/worker.d.ts +3 -1
- package/dist/runtime/worker.js +45 -2
- package/dist/scripts/debug-sync.mjs +18 -20
- package/dist/scripts/worker-run.d.mts +1 -1
- package/dist/scripts/worker-run.mjs +59 -113
- package/dist/use-synced-state/SyncedStateServer.d.mts +36 -0
- package/dist/use-synced-state/SyncedStateServer.mjs +196 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/SyncStateServer.test.mjs +116 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncState.test.js +115 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.d.ts +1 -0
- package/dist/use-synced-state/__tests__/useSyncedState.test.js +115 -0
- package/dist/use-synced-state/__tests__/worker.test.d.mts +1 -0
- package/dist/use-synced-state/__tests__/worker.test.mjs +70 -0
- package/dist/use-synced-state/client-core.d.ts +29 -0
- package/dist/use-synced-state/client-core.js +103 -0
- package/dist/use-synced-state/client.d.ts +3 -0
- package/dist/use-synced-state/client.js +4 -0
- package/dist/use-synced-state/constants.d.mts +1 -0
- package/dist/use-synced-state/constants.mjs +1 -0
- package/dist/use-synced-state/useSyncedState.d.ts +21 -0
- package/dist/use-synced-state/useSyncedState.js +64 -0
- package/dist/use-synced-state/worker.d.mts +14 -0
- package/dist/use-synced-state/worker.mjs +135 -0
- package/dist/vite/buildApp.mjs +34 -2
- package/dist/vite/cloudflarePreInitPlugin.d.mts +11 -0
- package/dist/vite/cloudflarePreInitPlugin.mjs +40 -0
- package/dist/vite/configPlugin.mjs +9 -14
- package/dist/vite/constants.d.mts +1 -0
- package/dist/vite/constants.mjs +1 -0
- package/dist/vite/createDirectiveLookupPlugin.mjs +10 -7
- package/dist/vite/devServerTimingPlugin.mjs +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.d.mts +4 -0
- package/dist/vite/diagnosticAssetGraphPlugin.mjs +41 -0
- package/dist/vite/directiveModulesDevPlugin.mjs +9 -1
- package/dist/vite/directivesPlugin.mjs +4 -4
- package/dist/vite/envResolvers.d.mts +11 -0
- package/dist/vite/envResolvers.mjs +20 -0
- package/dist/vite/getViteEsbuild.mjs +2 -1
- package/dist/vite/hmrStabilityPlugin.d.mts +2 -0
- package/dist/vite/hmrStabilityPlugin.mjs +73 -0
- package/dist/vite/injectVitePreamblePlugin.mjs +0 -4
- package/dist/vite/knownDepsResolverPlugin.d.mts +0 -6
- package/dist/vite/knownDepsResolverPlugin.mjs +25 -17
- package/dist/vite/linkerPlugin.d.mts +2 -1
- package/dist/vite/linkerPlugin.mjs +11 -3
- package/dist/vite/linkerPlugin.test.mjs +15 -0
- package/dist/vite/miniflareHMRPlugin.mjs +6 -38
- package/dist/vite/moveStaticAssetsPlugin.mjs +35 -4
- package/dist/vite/redwoodPlugin.mjs +9 -11
- package/dist/vite/redwoodPlugin.test.mjs +4 -4
- package/dist/vite/runDirectivesScan.mjs +75 -19
- package/dist/vite/ssrBridgePlugin.mjs +132 -40
- package/dist/vite/ssrBridgeWrapPlugin.d.mts +2 -0
- package/dist/vite/ssrBridgeWrapPlugin.mjs +85 -0
- package/dist/vite/staleDepRetryPlugin.d.mts +2 -0
- package/dist/vite/staleDepRetryPlugin.mjs +74 -0
- package/dist/vite/statePlugin.d.mts +4 -0
- package/dist/vite/statePlugin.mjs +62 -0
- package/dist/vite/transformClientComponents.test.mjs +32 -0
- package/dist/vite/transformJsxScriptTagsPlugin.mjs +0 -5
- package/dist/vite/transformServerFunctions.mjs +66 -4
- package/dist/vite/transformServerFunctions.test.mjs +35 -0
- package/dist/vite/virtualPlugin.mjs +6 -7
- package/package.json +41 -19
- package/dist/vite/manifestPlugin.d.mts +0 -4
- package/dist/vite/manifestPlugin.mjs +0 -63
- /package/dist/runtime/{lib/rwContext.js → client/navigationCache.test.d.ts} +0 -0
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { RpcStub } from "capnweb";
|
|
2
|
+
import { DurableObject } from "cloudflare:workers";
|
|
3
|
+
import type { RequestInfo } from "../runtime/requestInfo/types";
|
|
4
|
+
export type SyncedStateValue = unknown;
|
|
5
|
+
type OnSetHandler = (key: string, value: SyncedStateValue, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
6
|
+
type OnGetHandler = (key: string, value: SyncedStateValue | undefined, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
7
|
+
type OnKeyHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => Promise<string>;
|
|
8
|
+
type OnRoomHandler = (roomId: string | undefined, requestInfo: RequestInfo | null) => Promise<string>;
|
|
9
|
+
type OnSubscribeHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
10
|
+
type OnUnsubscribeHandler = (key: string, stub: DurableObjectStub<SyncedStateServer>) => void;
|
|
11
|
+
/**
|
|
12
|
+
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
13
|
+
*/
|
|
14
|
+
export declare class SyncedStateServer extends DurableObject {
|
|
15
|
+
#private;
|
|
16
|
+
static registerKeyHandler(handler: OnKeyHandler | null): void;
|
|
17
|
+
static getKeyHandler(): OnKeyHandler | null;
|
|
18
|
+
static registerRoomHandler(handler: OnRoomHandler | null): void;
|
|
19
|
+
static getRoomHandler(): OnRoomHandler | null;
|
|
20
|
+
static registerNamespace(namespace: DurableObjectNamespace<SyncedStateServer>, durableObjectName?: string): void;
|
|
21
|
+
static getNamespace(): DurableObjectNamespace<SyncedStateServer> | null;
|
|
22
|
+
static getDurableObjectName(): string;
|
|
23
|
+
setStub(stub: DurableObjectStub<SyncedStateServer>): void;
|
|
24
|
+
static registerSetStateHandler(handler: OnSetHandler | null): void;
|
|
25
|
+
static registerGetStateHandler(handler: OnGetHandler | null): void;
|
|
26
|
+
static registerSubscribeHandler(handler: OnSubscribeHandler | null): void;
|
|
27
|
+
static registerUnsubscribeHandler(handler: OnUnsubscribeHandler | null): void;
|
|
28
|
+
static getSubscribeHandler(): OnSubscribeHandler | null;
|
|
29
|
+
static getUnsubscribeHandler(): OnUnsubscribeHandler | null;
|
|
30
|
+
getState(key: string): SyncedStateValue;
|
|
31
|
+
setState(value: SyncedStateValue, key: string): void;
|
|
32
|
+
subscribe(key: string, client: RpcStub<(value: SyncedStateValue) => void>): void;
|
|
33
|
+
unsubscribe(key: string, client: RpcStub<(value: SyncedStateValue) => void>): void;
|
|
34
|
+
fetch(request: Request): Promise<Response>;
|
|
35
|
+
}
|
|
36
|
+
export {};
|
|
@@ -0,0 +1,196 @@
|
|
|
1
|
+
var __classPrivateFieldSet = (this && this.__classPrivateFieldSet) || function (receiver, state, value, kind, f) {
|
|
2
|
+
if (kind === "m") throw new TypeError("Private method is not writable");
|
|
3
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a setter");
|
|
4
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot write private member to an object whose class did not declare it");
|
|
5
|
+
return (kind === "a" ? f.call(receiver, value) : f ? f.value = value : state.set(receiver, value)), value;
|
|
6
|
+
};
|
|
7
|
+
var __classPrivateFieldGet = (this && this.__classPrivateFieldGet) || function (receiver, state, kind, f) {
|
|
8
|
+
if (kind === "a" && !f) throw new TypeError("Private accessor was defined without a getter");
|
|
9
|
+
if (typeof state === "function" ? receiver !== state || !f : !state.has(receiver)) throw new TypeError("Cannot read private member from an object whose class did not declare it");
|
|
10
|
+
return kind === "m" ? f : kind === "a" ? f.call(receiver) : f ? f.value : state.get(receiver);
|
|
11
|
+
};
|
|
12
|
+
var _SyncedStateServer_instances, _a, _SyncedStateServer_keyHandler, _SyncedStateServer_roomHandler, _SyncedStateServer_setStateHandler, _SyncedStateServer_getStateHandler, _SyncedStateServer_subscribeHandler, _SyncedStateServer_unsubscribeHandler, _SyncedStateServer_namespace, _SyncedStateServer_durableObjectName, _SyncedStateServer_stub, _SyncedStateServer_stateStore, _SyncedStateServer_subscriptions, _SyncedStateServer_subscriptionRefs, _SyncedStateServer_getStubForHandlers, _CoordinatorApi_coordinator, _CoordinatorApi_stub;
|
|
13
|
+
import { RpcTarget, newWorkersRpcResponse } from "capnweb";
|
|
14
|
+
import { DurableObject } from "cloudflare:workers";
|
|
15
|
+
/**
|
|
16
|
+
* Durable Object that keeps shared state for multiple clients and notifies subscribers.
|
|
17
|
+
*/
|
|
18
|
+
export class SyncedStateServer extends DurableObject {
|
|
19
|
+
constructor() {
|
|
20
|
+
super(...arguments);
|
|
21
|
+
_SyncedStateServer_instances.add(this);
|
|
22
|
+
_SyncedStateServer_stub.set(this, null);
|
|
23
|
+
_SyncedStateServer_stateStore.set(this, new Map());
|
|
24
|
+
_SyncedStateServer_subscriptions.set(this, new Map());
|
|
25
|
+
_SyncedStateServer_subscriptionRefs.set(this, new Map());
|
|
26
|
+
}
|
|
27
|
+
static registerKeyHandler(handler) {
|
|
28
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_keyHandler);
|
|
29
|
+
}
|
|
30
|
+
static getKeyHandler() {
|
|
31
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_keyHandler);
|
|
32
|
+
}
|
|
33
|
+
static registerRoomHandler(handler) {
|
|
34
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_roomHandler);
|
|
35
|
+
}
|
|
36
|
+
static getRoomHandler() {
|
|
37
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_roomHandler);
|
|
38
|
+
}
|
|
39
|
+
static registerNamespace(namespace, durableObjectName) {
|
|
40
|
+
__classPrivateFieldSet(_a, _a, namespace, "f", _SyncedStateServer_namespace);
|
|
41
|
+
if (durableObjectName) {
|
|
42
|
+
__classPrivateFieldSet(_a, _a, durableObjectName, "f", _SyncedStateServer_durableObjectName);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
static getNamespace() {
|
|
46
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_namespace);
|
|
47
|
+
}
|
|
48
|
+
static getDurableObjectName() {
|
|
49
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_durableObjectName);
|
|
50
|
+
}
|
|
51
|
+
setStub(stub) {
|
|
52
|
+
__classPrivateFieldSet(this, _SyncedStateServer_stub, stub, "f");
|
|
53
|
+
}
|
|
54
|
+
static registerSetStateHandler(handler) {
|
|
55
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_setStateHandler);
|
|
56
|
+
}
|
|
57
|
+
static registerGetStateHandler(handler) {
|
|
58
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_getStateHandler);
|
|
59
|
+
}
|
|
60
|
+
static registerSubscribeHandler(handler) {
|
|
61
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_subscribeHandler);
|
|
62
|
+
}
|
|
63
|
+
static registerUnsubscribeHandler(handler) {
|
|
64
|
+
__classPrivateFieldSet(_a, _a, handler, "f", _SyncedStateServer_unsubscribeHandler);
|
|
65
|
+
}
|
|
66
|
+
static getSubscribeHandler() {
|
|
67
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_subscribeHandler);
|
|
68
|
+
}
|
|
69
|
+
static getUnsubscribeHandler() {
|
|
70
|
+
return __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_unsubscribeHandler);
|
|
71
|
+
}
|
|
72
|
+
getState(key) {
|
|
73
|
+
const value = __classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").get(key);
|
|
74
|
+
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler)) {
|
|
75
|
+
const stub = __classPrivateFieldGet(this, _SyncedStateServer_instances, "m", _SyncedStateServer_getStubForHandlers).call(this);
|
|
76
|
+
if (stub) {
|
|
77
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_getStateHandler).call(_a, key, value, stub);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
return value;
|
|
81
|
+
}
|
|
82
|
+
setState(value, key) {
|
|
83
|
+
__classPrivateFieldGet(this, _SyncedStateServer_stateStore, "f").set(key, value);
|
|
84
|
+
if (__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler)) {
|
|
85
|
+
const stub = __classPrivateFieldGet(this, _SyncedStateServer_instances, "m", _SyncedStateServer_getStubForHandlers).call(this);
|
|
86
|
+
if (stub) {
|
|
87
|
+
__classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_setStateHandler).call(_a, key, value, stub);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
const subscribers = __classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key);
|
|
91
|
+
if (!subscribers) {
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
for (const subscriber of subscribers) {
|
|
95
|
+
subscriber(value).catch(() => {
|
|
96
|
+
subscribers.delete(subscriber);
|
|
97
|
+
const refs = __classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key);
|
|
98
|
+
if (refs) {
|
|
99
|
+
for (const [original, duplicate] of refs) {
|
|
100
|
+
if (duplicate === subscriber) {
|
|
101
|
+
refs.delete(original);
|
|
102
|
+
break;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (refs.size === 0) {
|
|
106
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").delete(key);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
});
|
|
110
|
+
}
|
|
111
|
+
if (subscribers.size === 0) {
|
|
112
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").delete(key);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
subscribe(key, client) {
|
|
116
|
+
if (!__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").has(key)) {
|
|
117
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").set(key, new Set());
|
|
118
|
+
}
|
|
119
|
+
if (!__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").has(key)) {
|
|
120
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").set(key, new Map());
|
|
121
|
+
}
|
|
122
|
+
const duplicate = typeof client.dup === "function"
|
|
123
|
+
? client.dup()
|
|
124
|
+
: client;
|
|
125
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key).add(duplicate);
|
|
126
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key).set(client, duplicate);
|
|
127
|
+
}
|
|
128
|
+
unsubscribe(key, client) {
|
|
129
|
+
const duplicates = __classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").get(key);
|
|
130
|
+
const duplicate = duplicates?.get(client);
|
|
131
|
+
const subscribers = __classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").get(key);
|
|
132
|
+
if (duplicate && subscribers) {
|
|
133
|
+
subscribers.delete(duplicate);
|
|
134
|
+
duplicates.delete(client);
|
|
135
|
+
if (subscribers.size === 0) {
|
|
136
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptions, "f").delete(key);
|
|
137
|
+
}
|
|
138
|
+
if (duplicates.size === 0) {
|
|
139
|
+
__classPrivateFieldGet(this, _SyncedStateServer_subscriptionRefs, "f").delete(key);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async fetch(request) {
|
|
144
|
+
// Create a placeholder stub - it will be set by the worker via _setStub
|
|
145
|
+
const api = new CoordinatorApi(this, __classPrivateFieldGet(this, _SyncedStateServer_stub, "f") || {});
|
|
146
|
+
return newWorkersRpcResponse(request, api);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
_a = SyncedStateServer, _SyncedStateServer_stub = new WeakMap(), _SyncedStateServer_stateStore = new WeakMap(), _SyncedStateServer_subscriptions = new WeakMap(), _SyncedStateServer_subscriptionRefs = new WeakMap(), _SyncedStateServer_instances = new WeakSet(), _SyncedStateServer_getStubForHandlers = function _SyncedStateServer_getStubForHandlers() {
|
|
150
|
+
// If we have a stub already, use it
|
|
151
|
+
if (__classPrivateFieldGet(this, _SyncedStateServer_stub, "f")) {
|
|
152
|
+
return __classPrivateFieldGet(this, _SyncedStateServer_stub, "f");
|
|
153
|
+
}
|
|
154
|
+
// Otherwise, try to get a stub from the registered namespace using our own ID
|
|
155
|
+
const namespace = __classPrivateFieldGet(_a, _a, "f", _SyncedStateServer_namespace);
|
|
156
|
+
if (namespace) {
|
|
157
|
+
return namespace.get(this.ctx.id);
|
|
158
|
+
}
|
|
159
|
+
return null;
|
|
160
|
+
};
|
|
161
|
+
_SyncedStateServer_keyHandler = { value: null };
|
|
162
|
+
_SyncedStateServer_roomHandler = { value: null };
|
|
163
|
+
_SyncedStateServer_setStateHandler = { value: null };
|
|
164
|
+
_SyncedStateServer_getStateHandler = { value: null };
|
|
165
|
+
_SyncedStateServer_subscribeHandler = { value: null };
|
|
166
|
+
_SyncedStateServer_unsubscribeHandler = { value: null };
|
|
167
|
+
_SyncedStateServer_namespace = { value: null };
|
|
168
|
+
_SyncedStateServer_durableObjectName = { value: "syncedState" };
|
|
169
|
+
class CoordinatorApi extends RpcTarget {
|
|
170
|
+
constructor(coordinator, stub) {
|
|
171
|
+
super();
|
|
172
|
+
_CoordinatorApi_coordinator.set(this, void 0);
|
|
173
|
+
_CoordinatorApi_stub.set(this, void 0);
|
|
174
|
+
__classPrivateFieldSet(this, _CoordinatorApi_coordinator, coordinator, "f");
|
|
175
|
+
__classPrivateFieldSet(this, _CoordinatorApi_stub, stub, "f");
|
|
176
|
+
coordinator.setStub(stub);
|
|
177
|
+
}
|
|
178
|
+
// Internal method to set the stub - called from worker
|
|
179
|
+
_setStub(stub) {
|
|
180
|
+
__classPrivateFieldSet(this, _CoordinatorApi_stub, stub, "f");
|
|
181
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").setStub(stub);
|
|
182
|
+
}
|
|
183
|
+
getState(key) {
|
|
184
|
+
return __classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").getState(key);
|
|
185
|
+
}
|
|
186
|
+
setState(value, key) {
|
|
187
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").setState(value, key);
|
|
188
|
+
}
|
|
189
|
+
subscribe(key, client) {
|
|
190
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").subscribe(key, client);
|
|
191
|
+
}
|
|
192
|
+
unsubscribe(key, client) {
|
|
193
|
+
__classPrivateFieldGet(this, _CoordinatorApi_coordinator, "f").unsubscribe(key, client);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
_CoordinatorApi_coordinator = new WeakMap(), _CoordinatorApi_stub = new WeakMap();
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
vi.mock("cloudflare:workers", () => {
|
|
3
|
+
class DurableObject {
|
|
4
|
+
}
|
|
5
|
+
return { DurableObject };
|
|
6
|
+
});
|
|
7
|
+
import { SyncedStateServer } from "../SyncedStateServer.mjs";
|
|
8
|
+
const createStub = (onInvoke) => {
|
|
9
|
+
const fn = Object.assign(async (value) => {
|
|
10
|
+
await onInvoke(value);
|
|
11
|
+
}, {
|
|
12
|
+
dup: () => fn,
|
|
13
|
+
});
|
|
14
|
+
return fn;
|
|
15
|
+
};
|
|
16
|
+
describe("SyncedStateServer", () => {
|
|
17
|
+
it("notifies subscribers when state changes", async () => {
|
|
18
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
19
|
+
const received = [];
|
|
20
|
+
const stub = createStub((value) => {
|
|
21
|
+
received.push(value);
|
|
22
|
+
});
|
|
23
|
+
coordinator.subscribe("counter", stub);
|
|
24
|
+
coordinator.setState(5, "counter");
|
|
25
|
+
expect(coordinator.getState("counter")).toBe(5);
|
|
26
|
+
expect(received).toEqual([5]);
|
|
27
|
+
});
|
|
28
|
+
it("removes subscriptions on unsubscribe", () => {
|
|
29
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
30
|
+
const stub = createStub(() => { });
|
|
31
|
+
coordinator.subscribe("counter", stub);
|
|
32
|
+
coordinator.unsubscribe("counter", stub);
|
|
33
|
+
coordinator.setState(1, "counter");
|
|
34
|
+
expect(coordinator.getState("counter")).toBe(1);
|
|
35
|
+
});
|
|
36
|
+
it("drops failing subscribers", async () => {
|
|
37
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
38
|
+
const stub = createStub(async () => {
|
|
39
|
+
throw new Error("fail");
|
|
40
|
+
});
|
|
41
|
+
coordinator.subscribe("counter", stub);
|
|
42
|
+
coordinator.setState(3, "counter");
|
|
43
|
+
await Promise.resolve();
|
|
44
|
+
coordinator.setState(4, "counter");
|
|
45
|
+
expect(coordinator.getState("counter")).toBe(4);
|
|
46
|
+
});
|
|
47
|
+
it("invokes registered onSet handler", () => {
|
|
48
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
49
|
+
const mockStub = {};
|
|
50
|
+
coordinator.setStub(mockStub);
|
|
51
|
+
const calls = [];
|
|
52
|
+
SyncedStateServer.registerSetStateHandler((key, value, stub) => {
|
|
53
|
+
calls.push({ key, value });
|
|
54
|
+
expect(stub).toBe(mockStub);
|
|
55
|
+
});
|
|
56
|
+
coordinator.setState(2, "counter");
|
|
57
|
+
expect(calls).toEqual([{ key: "counter", value: 2 }]);
|
|
58
|
+
SyncedStateServer.registerSetStateHandler(null);
|
|
59
|
+
});
|
|
60
|
+
it("invokes registered onGet handler", () => {
|
|
61
|
+
const coordinator = new SyncedStateServer({}, {});
|
|
62
|
+
const mockStub = {};
|
|
63
|
+
coordinator.setStub(mockStub);
|
|
64
|
+
const calls = [];
|
|
65
|
+
SyncedStateServer.registerGetStateHandler((key, value, stub) => {
|
|
66
|
+
calls.push({ key, value });
|
|
67
|
+
expect(stub).toBe(mockStub);
|
|
68
|
+
});
|
|
69
|
+
coordinator.setState(4, "counter");
|
|
70
|
+
expect(coordinator.getState("counter")).toBe(4);
|
|
71
|
+
expect(calls).toEqual([{ key: "counter", value: 4 }]);
|
|
72
|
+
SyncedStateServer.registerGetStateHandler(null);
|
|
73
|
+
});
|
|
74
|
+
describe("registerKeyHandler", () => {
|
|
75
|
+
const mockStub = {};
|
|
76
|
+
afterEach(() => {
|
|
77
|
+
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
|
|
78
|
+
});
|
|
79
|
+
it("stores and retrieves the registered handler", async () => {
|
|
80
|
+
const handler = async (key, stub) => `transformed:${key}`;
|
|
81
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
82
|
+
const retrievedHandler = SyncedStateServer.getKeyHandler();
|
|
83
|
+
expect(retrievedHandler).toBe(handler);
|
|
84
|
+
});
|
|
85
|
+
it("transforms keys using the registered handler", async () => {
|
|
86
|
+
const handler = async (key, stub) => `user:123:${key}`;
|
|
87
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
88
|
+
const result = await handler("counter", mockStub);
|
|
89
|
+
expect(result).toBe("user:123:counter");
|
|
90
|
+
});
|
|
91
|
+
it("returns null when no handler is registered", () => {
|
|
92
|
+
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
|
|
93
|
+
const handler = SyncedStateServer.getKeyHandler();
|
|
94
|
+
expect(handler).not.toBeNull();
|
|
95
|
+
});
|
|
96
|
+
it("allows handler to be async", async () => {
|
|
97
|
+
const handler = async (key, stub) => {
|
|
98
|
+
await new Promise((resolve) => setTimeout(resolve, 10));
|
|
99
|
+
return `async:${key}`;
|
|
100
|
+
};
|
|
101
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
102
|
+
const result = await handler("test", mockStub);
|
|
103
|
+
expect(result).toBe("async:test");
|
|
104
|
+
});
|
|
105
|
+
it("handler receives the correct key parameter", async () => {
|
|
106
|
+
let receivedKey = "";
|
|
107
|
+
const handler = async (key, stub) => {
|
|
108
|
+
receivedKey = key;
|
|
109
|
+
return key;
|
|
110
|
+
};
|
|
111
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
112
|
+
await handler("myKey", mockStub);
|
|
113
|
+
expect(receivedKey).toBe("myKey");
|
|
114
|
+
});
|
|
115
|
+
});
|
|
116
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { setSyncedStateClientForTesting, } from "../client";
|
|
3
|
+
import { createSyncedStateHook, } from "../useSyncedState";
|
|
4
|
+
const createStateHarness = () => {
|
|
5
|
+
let currentState;
|
|
6
|
+
const cleanups = [];
|
|
7
|
+
const useStateImpl = ((initialValue) => {
|
|
8
|
+
const resolved = typeof initialValue === "function"
|
|
9
|
+
? initialValue()
|
|
10
|
+
: initialValue;
|
|
11
|
+
currentState = resolved;
|
|
12
|
+
const setState = (next) => {
|
|
13
|
+
currentState =
|
|
14
|
+
typeof next === "function"
|
|
15
|
+
? next(currentState)
|
|
16
|
+
: next;
|
|
17
|
+
};
|
|
18
|
+
return [currentState, setState];
|
|
19
|
+
});
|
|
20
|
+
const useEffectImpl = (callback) => {
|
|
21
|
+
const cleanup = callback();
|
|
22
|
+
if (typeof cleanup === "function") {
|
|
23
|
+
cleanups.push(cleanup);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const useRefImpl = ((value) => ({
|
|
27
|
+
current: value,
|
|
28
|
+
}));
|
|
29
|
+
const useCallbackImpl = (fn) => fn;
|
|
30
|
+
const deps = {
|
|
31
|
+
useState: useStateImpl,
|
|
32
|
+
useEffect: useEffectImpl,
|
|
33
|
+
useRef: useRefImpl,
|
|
34
|
+
useCallback: useCallbackImpl,
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
deps,
|
|
38
|
+
getState: () => currentState,
|
|
39
|
+
runCleanups: () => cleanups.forEach((fn) => fn()),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
describe("createSyncedStateHook", () => {
|
|
43
|
+
const subscribeHandlers = new Map();
|
|
44
|
+
const client = {
|
|
45
|
+
async getState() {
|
|
46
|
+
return 5;
|
|
47
|
+
},
|
|
48
|
+
async setState(_value, _key) { },
|
|
49
|
+
async subscribe(key, handler) {
|
|
50
|
+
subscribeHandlers.set(key, handler);
|
|
51
|
+
},
|
|
52
|
+
async unsubscribe(key) {
|
|
53
|
+
subscribeHandlers.delete(key);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const resetClient = () => {
|
|
57
|
+
client.getState = async () => 5;
|
|
58
|
+
client.setState = async (_value, _key) => { };
|
|
59
|
+
client.subscribe = async (key, handler) => {
|
|
60
|
+
subscribeHandlers.set(key, handler);
|
|
61
|
+
};
|
|
62
|
+
client.unsubscribe = async (key) => {
|
|
63
|
+
subscribeHandlers.delete(key);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
resetClient();
|
|
68
|
+
setSyncedStateClientForTesting(client);
|
|
69
|
+
subscribeHandlers.clear();
|
|
70
|
+
});
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
setSyncedStateClientForTesting(null);
|
|
73
|
+
});
|
|
74
|
+
it("loads remote state and updates local value", async () => {
|
|
75
|
+
const harness = createStateHarness();
|
|
76
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
77
|
+
const [value] = useSyncedState(0, "counter");
|
|
78
|
+
expect(value).toBe(0);
|
|
79
|
+
await Promise.resolve();
|
|
80
|
+
expect(harness.getState()).toBe(5);
|
|
81
|
+
});
|
|
82
|
+
it("sends updates through the client and applies optimistic value", async () => {
|
|
83
|
+
const harness = createStateHarness();
|
|
84
|
+
const setCalls = [];
|
|
85
|
+
client.setState = async (value, key) => {
|
|
86
|
+
setCalls.push({ key, value });
|
|
87
|
+
};
|
|
88
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
89
|
+
const [, setSyncValue] = useSyncedState(0, "counter");
|
|
90
|
+
setSyncValue(9);
|
|
91
|
+
expect(harness.getState()).toBe(9);
|
|
92
|
+
expect(setCalls).toEqual([{ key: "counter", value: 9 }]);
|
|
93
|
+
});
|
|
94
|
+
it("applies remote updates from the subscription handler", async () => {
|
|
95
|
+
const harness = createStateHarness();
|
|
96
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
97
|
+
useSyncedState(0, "counter");
|
|
98
|
+
await Promise.resolve();
|
|
99
|
+
const handler = subscribeHandlers.get("counter");
|
|
100
|
+
handler?.(7);
|
|
101
|
+
expect(harness.getState()).toBe(7);
|
|
102
|
+
});
|
|
103
|
+
it("unsubscribes during cleanup", () => {
|
|
104
|
+
const harness = createStateHarness();
|
|
105
|
+
const unsubscribed = [];
|
|
106
|
+
client.unsubscribe = async (key) => {
|
|
107
|
+
unsubscribed.push({ key });
|
|
108
|
+
subscribeHandlers.delete(key);
|
|
109
|
+
};
|
|
110
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
111
|
+
useSyncedState(0, "counter");
|
|
112
|
+
harness.runCleanups();
|
|
113
|
+
expect(unsubscribed).toEqual([{ key: "counter" }]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
2
|
+
import { setSyncedStateClientForTesting, } from "../client";
|
|
3
|
+
import { createSyncedStateHook, } from "../useSyncedState";
|
|
4
|
+
const createStateHarness = () => {
|
|
5
|
+
let currentState;
|
|
6
|
+
const cleanups = [];
|
|
7
|
+
const useStateImpl = ((initialValue) => {
|
|
8
|
+
const resolved = typeof initialValue === "function"
|
|
9
|
+
? initialValue()
|
|
10
|
+
: initialValue;
|
|
11
|
+
currentState = resolved;
|
|
12
|
+
const setState = (next) => {
|
|
13
|
+
currentState =
|
|
14
|
+
typeof next === "function"
|
|
15
|
+
? next(currentState)
|
|
16
|
+
: next;
|
|
17
|
+
};
|
|
18
|
+
return [currentState, setState];
|
|
19
|
+
});
|
|
20
|
+
const useEffectImpl = (callback) => {
|
|
21
|
+
const cleanup = callback();
|
|
22
|
+
if (typeof cleanup === "function") {
|
|
23
|
+
cleanups.push(cleanup);
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
const useRefImpl = ((value) => ({
|
|
27
|
+
current: value,
|
|
28
|
+
}));
|
|
29
|
+
const useCallbackImpl = (fn) => fn;
|
|
30
|
+
const deps = {
|
|
31
|
+
useState: useStateImpl,
|
|
32
|
+
useEffect: useEffectImpl,
|
|
33
|
+
useRef: useRefImpl,
|
|
34
|
+
useCallback: useCallbackImpl,
|
|
35
|
+
};
|
|
36
|
+
return {
|
|
37
|
+
deps,
|
|
38
|
+
getState: () => currentState,
|
|
39
|
+
runCleanups: () => cleanups.forEach((fn) => fn()),
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
describe("createSyncedStateHook", () => {
|
|
43
|
+
const subscribeHandlers = new Map();
|
|
44
|
+
const client = {
|
|
45
|
+
async getState() {
|
|
46
|
+
return 5;
|
|
47
|
+
},
|
|
48
|
+
async setState(_value, _key) { },
|
|
49
|
+
async subscribe(key, handler) {
|
|
50
|
+
subscribeHandlers.set(key, handler);
|
|
51
|
+
},
|
|
52
|
+
async unsubscribe(key) {
|
|
53
|
+
subscribeHandlers.delete(key);
|
|
54
|
+
},
|
|
55
|
+
};
|
|
56
|
+
const resetClient = () => {
|
|
57
|
+
client.getState = async () => 5;
|
|
58
|
+
client.setState = async (_value, _key) => { };
|
|
59
|
+
client.subscribe = async (key, handler) => {
|
|
60
|
+
subscribeHandlers.set(key, handler);
|
|
61
|
+
};
|
|
62
|
+
client.unsubscribe = async (key) => {
|
|
63
|
+
subscribeHandlers.delete(key);
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
beforeEach(() => {
|
|
67
|
+
resetClient();
|
|
68
|
+
setSyncedStateClientForTesting(client);
|
|
69
|
+
subscribeHandlers.clear();
|
|
70
|
+
});
|
|
71
|
+
afterEach(() => {
|
|
72
|
+
setSyncedStateClientForTesting(null);
|
|
73
|
+
});
|
|
74
|
+
it("loads remote state and updates local value", async () => {
|
|
75
|
+
const harness = createStateHarness();
|
|
76
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
77
|
+
const [value] = useSyncedState(0, "counter");
|
|
78
|
+
expect(value).toBe(0);
|
|
79
|
+
await Promise.resolve();
|
|
80
|
+
expect(harness.getState()).toBe(5);
|
|
81
|
+
});
|
|
82
|
+
it("sends updates through the client and applies optimistic value", async () => {
|
|
83
|
+
const harness = createStateHarness();
|
|
84
|
+
const setCalls = [];
|
|
85
|
+
client.setState = async (value, key) => {
|
|
86
|
+
setCalls.push({ key, value });
|
|
87
|
+
};
|
|
88
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
89
|
+
const [, setSyncValue] = useSyncedState(0, "counter");
|
|
90
|
+
setSyncValue(9);
|
|
91
|
+
expect(harness.getState()).toBe(9);
|
|
92
|
+
expect(setCalls).toEqual([{ key: "counter", value: 9 }]);
|
|
93
|
+
});
|
|
94
|
+
it("applies remote updates from the subscription handler", async () => {
|
|
95
|
+
const harness = createStateHarness();
|
|
96
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
97
|
+
useSyncedState(0, "counter");
|
|
98
|
+
await Promise.resolve();
|
|
99
|
+
const handler = subscribeHandlers.get("counter");
|
|
100
|
+
handler?.(7);
|
|
101
|
+
expect(harness.getState()).toBe(7);
|
|
102
|
+
});
|
|
103
|
+
it("unsubscribes during cleanup", () => {
|
|
104
|
+
const harness = createStateHarness();
|
|
105
|
+
const unsubscribed = [];
|
|
106
|
+
client.unsubscribe = async (key) => {
|
|
107
|
+
unsubscribed.push({ key });
|
|
108
|
+
subscribeHandlers.delete(key);
|
|
109
|
+
};
|
|
110
|
+
const useSyncedState = createSyncedStateHook({ hooks: harness.deps });
|
|
111
|
+
useSyncedState(0, "counter");
|
|
112
|
+
harness.runCleanups();
|
|
113
|
+
expect(unsubscribed).toEqual([{ key: "counter" }]);
|
|
114
|
+
});
|
|
115
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
vi.mock("cloudflare:workers", () => {
|
|
3
|
+
class DurableObject {
|
|
4
|
+
}
|
|
5
|
+
return { DurableObject, env: {} };
|
|
6
|
+
});
|
|
7
|
+
vi.mock("capnweb", () => ({
|
|
8
|
+
RpcTarget: class RpcTarget {
|
|
9
|
+
},
|
|
10
|
+
newWorkersRpcResponse: vi.fn(),
|
|
11
|
+
}));
|
|
12
|
+
vi.mock("../runtime/entries/router", () => ({
|
|
13
|
+
route: vi.fn((path, handler) => ({ path, handler })),
|
|
14
|
+
}));
|
|
15
|
+
import { SyncedStateServer } from "../SyncedStateServer.mjs";
|
|
16
|
+
describe("SyncedStateProxy", () => {
|
|
17
|
+
let mockCoordinator;
|
|
18
|
+
beforeEach(() => {
|
|
19
|
+
mockCoordinator = new SyncedStateServer({}, {});
|
|
20
|
+
});
|
|
21
|
+
const mockStub = {};
|
|
22
|
+
afterEach(() => {
|
|
23
|
+
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
|
|
24
|
+
});
|
|
25
|
+
it("transforms keys before calling coordinator methods when handler is registered", async () => {
|
|
26
|
+
const handler = async (key, stub) => `transformed:${key}`;
|
|
27
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
28
|
+
const transformedKey = await handler("counter", mockStub);
|
|
29
|
+
expect(transformedKey).toBe("transformed:counter");
|
|
30
|
+
mockCoordinator.setState(5, transformedKey);
|
|
31
|
+
const value = mockCoordinator.getState(transformedKey);
|
|
32
|
+
expect(value).toBe(5);
|
|
33
|
+
});
|
|
34
|
+
it("does not transform keys when no handler is registered", () => {
|
|
35
|
+
SyncedStateServer.registerKeyHandler(async (key, stub) => key);
|
|
36
|
+
const handler = SyncedStateServer.getKeyHandler();
|
|
37
|
+
expect(handler).not.toBeNull();
|
|
38
|
+
});
|
|
39
|
+
it("passes through original key when handler returns it unchanged", async () => {
|
|
40
|
+
const handler = async (key, stub) => key;
|
|
41
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
42
|
+
const result = await handler("counter", mockStub);
|
|
43
|
+
expect(result).toBe("counter");
|
|
44
|
+
});
|
|
45
|
+
it("handler can scope keys per user", async () => {
|
|
46
|
+
const handler = async (key, stub) => {
|
|
47
|
+
const userId = "user123";
|
|
48
|
+
return `user:${userId}:${key}`;
|
|
49
|
+
};
|
|
50
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
51
|
+
const result = await handler("settings", mockStub);
|
|
52
|
+
expect(result).toBe("user:user123:settings");
|
|
53
|
+
});
|
|
54
|
+
it("allows errors from handler to propagate", async () => {
|
|
55
|
+
const handler = async (_key, stub) => {
|
|
56
|
+
throw new Error("Handler error");
|
|
57
|
+
};
|
|
58
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
59
|
+
await expect(handler("test", mockStub)).rejects.toThrow("Handler error");
|
|
60
|
+
});
|
|
61
|
+
it("handles async operations in handler", async () => {
|
|
62
|
+
const handler = async (key, stub) => {
|
|
63
|
+
await new Promise((resolve) => setTimeout(resolve, 5));
|
|
64
|
+
return `async:${key}`;
|
|
65
|
+
};
|
|
66
|
+
SyncedStateServer.registerKeyHandler(handler);
|
|
67
|
+
const result = await handler("data", mockStub);
|
|
68
|
+
expect(result).toBe("async:data");
|
|
69
|
+
});
|
|
70
|
+
});
|