@noya-app/noya-multiplayer-react 0.1.70 → 0.1.72
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/.turbo/turbo-build.log +11 -11
- package/CHANGELOG.md +24 -0
- package/dist/index.bundle.js +46 -20
- package/dist/index.d.mts +84 -55
- package/dist/index.d.ts +84 -55
- package/dist/index.js +518 -273
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +471 -233
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/NoyaStateContext.tsx +156 -105
- package/src/__tests__/noyaApp.test.ts +34 -0
- package/src/__tests__/resourcePayload.test.ts +24 -134
- package/src/components/UserPointersOverlay.tsx +22 -20
- package/src/hooks.ts +22 -14
- package/src/inspector/StateInspector.tsx +318 -255
- package/src/inspector/StateInspectorRow.tsx +3 -1
- package/src/inspector/sections/ServerScriptLogsSection.tsx +128 -0
- package/src/inspector/serialization.ts +4 -2
- package/src/inspector/useStateInspector.tsx +10 -4
- package/src/noyaApp.ts +94 -22
- package/src/singleton.tsx +46 -8
|
@@ -2,14 +2,9 @@ import {
|
|
|
2
2
|
ModifiedResource,
|
|
3
3
|
Resource,
|
|
4
4
|
ResourceCreate,
|
|
5
|
-
ResourceCreateMap,
|
|
6
|
-
ResourceMap,
|
|
7
5
|
} from "@noya-app/noya-schemas";
|
|
8
6
|
import { describe, expect, it } from "bun:test";
|
|
9
|
-
import {
|
|
10
|
-
buildCreateResourcePayload,
|
|
11
|
-
resourceToCreateParameters,
|
|
12
|
-
} from "../NoyaStateContext";
|
|
7
|
+
import { buildResourcePatchPayload } from "../NoyaStateContext";
|
|
13
8
|
|
|
14
9
|
const ISO_DATE = "2024-01-01T00:00:00.000Z";
|
|
15
10
|
|
|
@@ -82,25 +77,23 @@ describe("resource payload helpers", () => {
|
|
|
82
77
|
it("returns create parameters for added resources", () => {
|
|
83
78
|
const addedResource = createDirectoryResourceCreate();
|
|
84
79
|
|
|
85
|
-
const result =
|
|
80
|
+
const result = buildResourcePatchPayload({
|
|
86
81
|
addedResources: [addedResource],
|
|
87
82
|
modifiedResources: [],
|
|
88
|
-
|
|
89
|
-
[addedResource.path]: addedResource,
|
|
90
|
-
} satisfies ResourceCreateMap,
|
|
91
|
-
resourceMap: {},
|
|
83
|
+
removedResources: [],
|
|
92
84
|
});
|
|
93
85
|
|
|
94
|
-
expect(result.
|
|
86
|
+
expect(result.create).toEqual([
|
|
95
87
|
{
|
|
96
88
|
path: "docs",
|
|
97
89
|
type: "directory",
|
|
98
90
|
},
|
|
99
91
|
]);
|
|
100
|
-
expect(result.
|
|
92
|
+
expect(result.update).toBeUndefined();
|
|
93
|
+
expect(result.delete).toBeUndefined();
|
|
101
94
|
});
|
|
102
95
|
|
|
103
|
-
it("includes
|
|
96
|
+
it("includes update entries when a modified resource swaps the asset", () => {
|
|
104
97
|
const existingResource = createAssetResource({
|
|
105
98
|
id: "existing-resource",
|
|
106
99
|
stableId: "existing-stable",
|
|
@@ -115,117 +108,31 @@ describe("resource payload helpers", () => {
|
|
|
115
108
|
assetId: "asset-new",
|
|
116
109
|
});
|
|
117
110
|
|
|
118
|
-
const result =
|
|
111
|
+
const result = buildResourcePatchPayload({
|
|
119
112
|
addedResources: [],
|
|
120
113
|
modifiedResources: [
|
|
121
114
|
{
|
|
122
115
|
id: existingResource.id,
|
|
123
116
|
stableId: existingResource.stableId,
|
|
124
117
|
assetId: updatedResource.assetId,
|
|
125
|
-
} satisfies ModifiedResource,
|
|
126
|
-
],
|
|
127
|
-
newResourceMap: {
|
|
128
|
-
[updatedResource.path]: updatedResource,
|
|
129
|
-
} satisfies ResourceCreateMap,
|
|
130
|
-
resourceMap: {
|
|
131
|
-
[existingResource.path]: existingResource,
|
|
132
|
-
} satisfies ResourceMap,
|
|
133
|
-
});
|
|
134
|
-
|
|
135
|
-
expect(result.payload).toEqual([
|
|
136
|
-
resourceToCreateParameters(updatedResource),
|
|
137
|
-
]);
|
|
138
|
-
expect(result.consumedModifiedResourceIds).toEqual([
|
|
139
|
-
existingResource.id,
|
|
140
|
-
]);
|
|
141
|
-
});
|
|
142
|
-
|
|
143
|
-
it("includes asset replacements when a modified resource provides inline asset data", () => {
|
|
144
|
-
const existingResource = createAssetResource({
|
|
145
|
-
id: "existing-resource",
|
|
146
|
-
stableId: "existing-stable",
|
|
147
|
-
path: "images/logo.png",
|
|
148
|
-
assetId: "asset-old",
|
|
149
|
-
});
|
|
150
|
-
|
|
151
|
-
const updatedResource = createAssetResourceCreate({
|
|
152
|
-
id: "existing-resource",
|
|
153
|
-
stableId: "existing-stable",
|
|
154
|
-
path: "images/logo.png",
|
|
155
|
-
assetId: "asset-old",
|
|
156
|
-
asset: {
|
|
157
|
-
content: "ZGF0YQ==",
|
|
158
|
-
contentType: "image/png",
|
|
159
|
-
encoding: "base64" as const,
|
|
160
|
-
},
|
|
161
|
-
});
|
|
162
|
-
|
|
163
|
-
const result = buildCreateResourcePayload({
|
|
164
|
-
addedResources: [],
|
|
165
|
-
modifiedResources: [
|
|
166
|
-
{
|
|
167
|
-
id: existingResource.id,
|
|
168
|
-
stableId: existingResource.stableId,
|
|
169
118
|
asset: updatedResource.asset,
|
|
170
119
|
} satisfies ModifiedResource,
|
|
171
120
|
],
|
|
172
|
-
|
|
173
|
-
[updatedResource.path]: updatedResource,
|
|
174
|
-
} satisfies ResourceCreateMap,
|
|
175
|
-
resourceMap: {
|
|
176
|
-
[existingResource.path]: existingResource,
|
|
177
|
-
} satisfies ResourceMap,
|
|
121
|
+
removedResources: [],
|
|
178
122
|
});
|
|
179
123
|
|
|
180
|
-
expect(result.
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
});
|
|
187
|
-
|
|
188
|
-
it("deduplicates payload entries by path", () => {
|
|
189
|
-
const existingResource = createAssetResource({
|
|
190
|
-
id: "existing-resource",
|
|
191
|
-
stableId: "existing-stable",
|
|
192
|
-
path: "shared/path",
|
|
193
|
-
assetId: "asset-old",
|
|
194
|
-
});
|
|
195
|
-
|
|
196
|
-
const updatedResource = createAssetResourceCreate({
|
|
197
|
-
id: "new-resource",
|
|
198
|
-
stableId: "new-stable",
|
|
199
|
-
path: "shared/path",
|
|
200
|
-
assetId: "asset-new",
|
|
201
|
-
});
|
|
202
|
-
|
|
203
|
-
const result = buildCreateResourcePayload({
|
|
204
|
-
addedResources: [updatedResource],
|
|
205
|
-
modifiedResources: [
|
|
206
|
-
{
|
|
207
|
-
id: existingResource.id,
|
|
208
|
-
stableId: existingResource.stableId,
|
|
209
|
-
assetId: updatedResource.assetId,
|
|
210
|
-
} satisfies ModifiedResource,
|
|
211
|
-
],
|
|
212
|
-
newResourceMap: {
|
|
213
|
-
[updatedResource.path]: updatedResource,
|
|
214
|
-
} satisfies ResourceCreateMap,
|
|
215
|
-
resourceMap: {
|
|
216
|
-
[existingResource.path]: existingResource,
|
|
217
|
-
} satisfies ResourceMap,
|
|
218
|
-
});
|
|
219
|
-
|
|
220
|
-
expect(result.payload).toEqual([
|
|
221
|
-
resourceToCreateParameters(updatedResource),
|
|
222
|
-
]);
|
|
223
|
-
expect(result.consumedModifiedResourceIds).toEqual([
|
|
224
|
-
existingResource.id,
|
|
124
|
+
expect(result.update).toEqual([
|
|
125
|
+
{
|
|
126
|
+
id: existingResource.id,
|
|
127
|
+
assetId: updatedResource.assetId,
|
|
128
|
+
asset: updatedResource.asset,
|
|
129
|
+
},
|
|
225
130
|
]);
|
|
131
|
+
expect(result.create).toBeUndefined();
|
|
132
|
+
expect(result.delete).toBeUndefined();
|
|
226
133
|
});
|
|
227
134
|
|
|
228
|
-
it("
|
|
135
|
+
it("includes delete entries for removed resources", () => {
|
|
229
136
|
const existingResource = createAssetResource({
|
|
230
137
|
id: "existing-resource",
|
|
231
138
|
stableId: "existing-stable",
|
|
@@ -233,31 +140,14 @@ describe("resource payload helpers", () => {
|
|
|
233
140
|
assetId: "asset-old",
|
|
234
141
|
});
|
|
235
142
|
|
|
236
|
-
const
|
|
237
|
-
id: "existing-resource",
|
|
238
|
-
stableId: "existing-stable",
|
|
239
|
-
path: "images/logo.png",
|
|
240
|
-
assetId: "asset-old",
|
|
241
|
-
});
|
|
242
|
-
|
|
243
|
-
const result = buildCreateResourcePayload({
|
|
143
|
+
const result = buildResourcePatchPayload({
|
|
244
144
|
addedResources: [],
|
|
245
|
-
modifiedResources: [
|
|
246
|
-
|
|
247
|
-
id: existingResource.id,
|
|
248
|
-
stableId: existingResource.stableId,
|
|
249
|
-
path: updatedResource.path,
|
|
250
|
-
} satisfies ModifiedResource,
|
|
251
|
-
],
|
|
252
|
-
newResourceMap: {
|
|
253
|
-
[updatedResource.path]: updatedResource,
|
|
254
|
-
} satisfies ResourceCreateMap,
|
|
255
|
-
resourceMap: {
|
|
256
|
-
[existingResource.path]: existingResource,
|
|
257
|
-
} satisfies ResourceMap,
|
|
145
|
+
modifiedResources: [],
|
|
146
|
+
removedResources: [existingResource],
|
|
258
147
|
});
|
|
259
148
|
|
|
260
|
-
expect(result.
|
|
261
|
-
expect(result.
|
|
149
|
+
expect(result.delete).toEqual([{ id: existingResource.id }]);
|
|
150
|
+
expect(result.create).toBeUndefined();
|
|
151
|
+
expect(result.update).toBeUndefined();
|
|
262
152
|
});
|
|
263
153
|
});
|
|
@@ -3,9 +3,9 @@
|
|
|
3
3
|
import { Observable } from "@noya-app/observable";
|
|
4
4
|
import { memoGeneric } from "@noya-app/react-utils";
|
|
5
5
|
import {
|
|
6
|
-
ConnectedUsersManager,
|
|
7
|
-
EphemeralUserDataManager,
|
|
8
6
|
MultiplayerUser,
|
|
7
|
+
SharedConnectionDataManager,
|
|
8
|
+
UserManager,
|
|
9
9
|
} from "@noya-app/state-manager";
|
|
10
10
|
import React, { useEffect, useMemo, useState } from "react";
|
|
11
11
|
import { useObservable } from "../useObservable";
|
|
@@ -26,7 +26,7 @@ export type RenderUserPointer = {
|
|
|
26
26
|
|
|
27
27
|
type InternalUserPointerProps<E extends UserPointerData> = {
|
|
28
28
|
user: MultiplayerUser;
|
|
29
|
-
|
|
29
|
+
sharedConnectionDataManager: SharedConnectionDataManager<E>;
|
|
30
30
|
hideAfter?: number;
|
|
31
31
|
} & RenderUserPointer;
|
|
32
32
|
|
|
@@ -38,22 +38,22 @@ const UserPointerInternal = memoGeneric(function UserPointerInternal<
|
|
|
38
38
|
E extends UserPointerData,
|
|
39
39
|
>({
|
|
40
40
|
user,
|
|
41
|
-
|
|
41
|
+
sharedConnectionDataManager,
|
|
42
42
|
hideAfter = 5000,
|
|
43
43
|
renderUserPointer,
|
|
44
44
|
}: InternalUserPointerProps<E>) {
|
|
45
45
|
const observable = useMemo(() => {
|
|
46
|
-
const metadata$ =
|
|
47
|
-
.observePath([user.
|
|
46
|
+
const metadata$ = sharedConnectionDataManager.metadata$
|
|
47
|
+
.observePath([user.connectionId] as const)
|
|
48
48
|
.throttle(50);
|
|
49
|
-
const data$ =
|
|
50
|
-
.observePath([user.
|
|
49
|
+
const data$ = sharedConnectionDataManager.data$
|
|
50
|
+
.observePath([user.connectionId] as const)
|
|
51
51
|
.throttle(50);
|
|
52
52
|
|
|
53
53
|
return Observable.combine([metadata$, data$], ([metadata, data]) => {
|
|
54
54
|
return { metadata, data };
|
|
55
55
|
});
|
|
56
|
-
}, [
|
|
56
|
+
}, [sharedConnectionDataManager, user.connectionId]);
|
|
57
57
|
|
|
58
58
|
const { metadata, data } = useObservable(observable);
|
|
59
59
|
|
|
@@ -75,7 +75,7 @@ const UserPointerInternal = memoGeneric(function UserPointerInternal<
|
|
|
75
75
|
if (!data?.pointer) return null;
|
|
76
76
|
|
|
77
77
|
return renderUserPointer({
|
|
78
|
-
userId: user.
|
|
78
|
+
userId: user.connectionId,
|
|
79
79
|
name: user.name,
|
|
80
80
|
point: data.pointer,
|
|
81
81
|
visible: show,
|
|
@@ -83,31 +83,33 @@ const UserPointerInternal = memoGeneric(function UserPointerInternal<
|
|
|
83
83
|
});
|
|
84
84
|
|
|
85
85
|
export type UserPointersOverlayProps<E extends UserPointerData> = {
|
|
86
|
-
|
|
87
|
-
|
|
86
|
+
userManager: UserManager;
|
|
87
|
+
sharedConnectionDataManager: SharedConnectionDataManager<E>;
|
|
88
88
|
} & RenderUserPointer;
|
|
89
89
|
|
|
90
90
|
export const UserPointersOverlay = memoGeneric(function UserPointers<
|
|
91
91
|
E extends UserPointerData,
|
|
92
92
|
>({
|
|
93
|
-
|
|
94
|
-
|
|
93
|
+
userManager,
|
|
94
|
+
sharedConnectionDataManager,
|
|
95
95
|
renderUserPointer,
|
|
96
96
|
}: UserPointersOverlayProps<E>) {
|
|
97
|
-
const
|
|
98
|
-
|
|
97
|
+
const currentConnectionId = useObservable(
|
|
98
|
+
sharedConnectionDataManager.currentConnectionId$
|
|
99
|
+
);
|
|
100
|
+
const connectedUsers = useObservable(userManager.connectedUsers$);
|
|
99
101
|
|
|
100
102
|
return (
|
|
101
103
|
<>
|
|
102
104
|
{connectedUsers.map((user) => {
|
|
103
|
-
if (user.
|
|
105
|
+
if (user.connectionId === currentConnectionId) return null;
|
|
104
106
|
|
|
105
107
|
return (
|
|
106
108
|
<UserPointerInternal
|
|
107
|
-
key={user.
|
|
109
|
+
key={user.connectionId}
|
|
108
110
|
user={user}
|
|
109
|
-
|
|
110
|
-
|
|
111
|
+
sharedConnectionDataManager={
|
|
112
|
+
sharedConnectionDataManager as unknown as SharedConnectionDataManager<UserPointerData>
|
|
111
113
|
}
|
|
112
114
|
renderUserPointer={renderUserPointer}
|
|
113
115
|
/>
|
package/src/hooks.ts
CHANGED
|
@@ -108,8 +108,9 @@ export type UseMultiplayerStateOptions<
|
|
|
108
108
|
M extends object = object,
|
|
109
109
|
E extends object = object,
|
|
110
110
|
MenuT extends string = string,
|
|
111
|
+
I extends Record<string, any> = Record<string, unknown>,
|
|
111
112
|
> = NoyaManagerOptions<S, M> & {
|
|
112
|
-
sync?: SyncAdapter<S, M, E, MenuT>;
|
|
113
|
+
sync?: SyncAdapter<S, M, E, MenuT, I>;
|
|
113
114
|
inspector?: boolean | StateInspectorOptions;
|
|
114
115
|
};
|
|
115
116
|
|
|
@@ -119,10 +120,11 @@ export function useBindNoyaManager<
|
|
|
119
120
|
M extends object = object,
|
|
120
121
|
E extends object = object,
|
|
121
122
|
MenuT extends string = string,
|
|
123
|
+
I extends Record<string, any> = Record<string, unknown>,
|
|
122
124
|
>(
|
|
123
|
-
noyaManager: NoyaManager<S, M, E, MenuT>,
|
|
125
|
+
noyaManager: NoyaManager<S, M, E, MenuT, I>,
|
|
124
126
|
options?: Pick<
|
|
125
|
-
UseMultiplayerStateOptions<S, M, E, MenuT>,
|
|
127
|
+
UseMultiplayerStateOptions<S, M, E, MenuT, I>,
|
|
126
128
|
"inspector" | "sync"
|
|
127
129
|
>
|
|
128
130
|
) {
|
|
@@ -135,8 +137,8 @@ export function useBindNoyaManager<
|
|
|
135
137
|
noyaManager: typeof noyaManager;
|
|
136
138
|
taskManager: typeof noyaManager.taskManager;
|
|
137
139
|
connectionEventManager: typeof noyaManager.connectionEventManager;
|
|
138
|
-
|
|
139
|
-
|
|
140
|
+
userManager: typeof noyaManager.userManager;
|
|
141
|
+
sharedConnectionDataManager: typeof noyaManager.sharedConnectionDataManager;
|
|
140
142
|
multiplayerStateManager: typeof noyaManager.multiplayerStateManager;
|
|
141
143
|
assetManager: typeof noyaManager.assetManager;
|
|
142
144
|
aiManager: typeof noyaManager.aiManager;
|
|
@@ -154,8 +156,8 @@ export function useBindNoyaManager<
|
|
|
154
156
|
noyaManager,
|
|
155
157
|
taskManager: noyaManager.taskManager,
|
|
156
158
|
connectionEventManager: noyaManager.connectionEventManager,
|
|
157
|
-
|
|
158
|
-
|
|
159
|
+
userManager: noyaManager.userManager,
|
|
160
|
+
sharedConnectionDataManager: noyaManager.sharedConnectionDataManager,
|
|
159
161
|
multiplayerStateManager: noyaManager.multiplayerStateManager,
|
|
160
162
|
assetManager: noyaManager.assetManager,
|
|
161
163
|
aiManager: noyaManager.aiManager,
|
|
@@ -179,9 +181,10 @@ export function useBindNoyaManager<
|
|
|
179
181
|
|
|
180
182
|
useErrorOverlay<MenuT>(noyaManager);
|
|
181
183
|
|
|
182
|
-
useStateInspector<S, M, E, MenuT>({
|
|
184
|
+
useStateInspector<S, M, E, MenuT, I>({
|
|
183
185
|
noyaManager,
|
|
184
186
|
disabled: !options?.inspector,
|
|
187
|
+
advanced: true,
|
|
185
188
|
...(typeof options?.inspector === "object" && options?.inspector),
|
|
186
189
|
});
|
|
187
190
|
|
|
@@ -193,30 +196,35 @@ export function useMultiplayerState<
|
|
|
193
196
|
M extends object = object,
|
|
194
197
|
E extends object = object,
|
|
195
198
|
MenuT extends string = string,
|
|
199
|
+
I extends Record<string, any> = Record<string, unknown>,
|
|
196
200
|
>(
|
|
197
201
|
initialState: S | (() => S),
|
|
198
|
-
options?: UseMultiplayerStateOptions<S, M, E, MenuT>
|
|
202
|
+
options?: UseMultiplayerStateOptions<S, M, E, MenuT, I>
|
|
199
203
|
) {
|
|
200
204
|
const {
|
|
201
205
|
sync = stubSync<
|
|
202
206
|
unknown,
|
|
203
207
|
object,
|
|
204
208
|
object,
|
|
205
|
-
string
|
|
206
|
-
|
|
209
|
+
string,
|
|
210
|
+
Record<string, unknown>
|
|
211
|
+
>() as unknown as SyncAdapter<S, M, E, MenuT, I>,
|
|
207
212
|
inspector,
|
|
208
213
|
...rest
|
|
209
214
|
} = options ?? {};
|
|
210
215
|
|
|
211
216
|
const [noyaManager] = useState(
|
|
212
|
-
() => new NoyaManager<S, M, E, MenuT>(initialState, rest)
|
|
217
|
+
() => new NoyaManager<S, M, E, MenuT, I>(initialState, rest)
|
|
213
218
|
);
|
|
214
219
|
|
|
215
|
-
return useBindNoyaManager<S, M, E, MenuT>(noyaManager, {
|
|
220
|
+
return useBindNoyaManager<S, M, E, MenuT, I>(noyaManager, {
|
|
221
|
+
sync,
|
|
222
|
+
inspector,
|
|
223
|
+
});
|
|
216
224
|
}
|
|
217
225
|
|
|
218
226
|
function useErrorOverlay<MenuT extends string = string>(
|
|
219
|
-
noyaManager: NoyaManager<any, any, any, MenuT>
|
|
227
|
+
noyaManager: NoyaManager<any, any, any, MenuT, any>
|
|
220
228
|
) {
|
|
221
229
|
const unrecoverableError = useObservable(noyaManager.unrecoverableError);
|
|
222
230
|
|