@noya-app/noya-multiplayer-react 0.1.61 → 0.1.63

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@noya-app/noya-multiplayer-react",
3
- "version": "0.1.61",
3
+ "version": "0.1.63",
4
4
  "main": "./dist/index.js",
5
5
  "module": "./dist/index.mjs",
6
6
  "types": "./dist/index.d.ts",
@@ -12,11 +12,11 @@
12
12
  "dev": "npm run build -- --watch"
13
13
  },
14
14
  "dependencies": {
15
- "@noya-app/state-manager": "0.1.47",
15
+ "@noya-app/state-manager": "0.1.49",
16
16
  "@noya-app/emitter": "0.1.0",
17
- "@noya-app/observable": "0.1.9",
17
+ "@noya-app/observable": "0.1.11",
18
18
  "@noya-app/task-runner": "0.1.6",
19
- "@noya-app/react-utils": "0.1.22",
19
+ "@noya-app/react-utils": "0.1.24",
20
20
  "react-inspector": "6.0.2"
21
21
  },
22
22
  "peerDependencies": {
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { MenuItem } from "@noya-app/noya-designsystem";
2
4
  import { Input, OutputTransform } from "@noya-app/noya-schemas";
3
5
  import {
@@ -11,6 +13,7 @@ import {
11
13
  ConnectedUsersManager,
12
14
  CreateParams,
13
15
  EphemeralUserDataManager,
16
+ MultiplayerPatchMetadata,
14
17
  MultiplayerStateManager,
15
18
  NoyaManager,
16
19
  PublishCallback,
@@ -244,13 +247,16 @@ export function createNoyaContext<
244
247
  schema: Schema;
245
248
  mergeHistoryEntries?: StateManagerOptions<
246
249
  Static<Schema>,
247
- M
250
+ M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
248
251
  >["mergeHistoryEntries"];
249
252
  safeEval?: SafeEval;
250
253
  outputTransforms?: CreateParams<OutputTransform>[];
251
254
  inputs?: CreateParams<Input>[];
252
255
  }) {
253
256
  type S = Static<Schema>;
257
+ type PartialM = Partial<
258
+ M & Pick<MultiplayerPatchMetadata, "name" | "timestamp">
259
+ >;
254
260
 
255
261
  const NoyaStateContext = AnyNoyaStateContext as React.Context<
256
262
  NoyaManager<S, M, E> | undefined
@@ -339,14 +345,14 @@ export function createNoyaContext<
339
345
 
340
346
  function useSetValue(): (
341
347
  ...args:
342
- | [metadata: M, value: SetStateAction<S>]
348
+ | [metadata: PartialM, value: SetStateAction<S>]
343
349
  | [value: SetStateAction<S>]
344
350
  ) => void;
345
351
  function useSetValue<P extends PathKey[] | string>(
346
352
  path: P
347
353
  ): (
348
354
  ...args:
349
- | [metadata: M, value: SetStateAction<GetAtPath<S, P>>]
355
+ | [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
350
356
  | [value: SetStateAction<GetAtPath<S, P>>]
351
357
  ) => void;
352
358
  function useSetValue<P extends PathKey[] | string>(path?: P) {
@@ -355,7 +361,7 @@ export function createNoyaContext<
355
361
  const setValue = useCallback(
356
362
  (
357
363
  ...args:
358
- | [metadata: M, value: SetStateAction<GetAtPath<S, P>>]
364
+ | [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
359
365
  | [value: SetStateAction<GetAtPath<S, P>>]
360
366
  ): void => {
361
367
  const [metadata, value] = args.length === 2 ? args : [{}, args[0]];
@@ -376,7 +382,7 @@ export function createNoyaContext<
376
382
  S,
377
383
  (
378
384
  ...args:
379
- | [metadata: M, value: SetStateAction<S>]
385
+ | [metadata: PartialM, value: SetStateAction<S>]
380
386
  | [value: SetStateAction<S>]
381
387
  ) => void,
382
388
  ];
@@ -387,7 +393,7 @@ export function createNoyaContext<
387
393
  A,
388
394
  (
389
395
  ...args:
390
- | [metadata: M, value: SetStateAction<S>]
396
+ | [metadata: PartialM, value: SetStateAction<S>]
391
397
  | [value: SetStateAction<S>]
392
398
  ) => void,
393
399
  ];
@@ -397,7 +403,7 @@ export function createNoyaContext<
397
403
  GetAtPath<S, P>,
398
404
  (
399
405
  ...args:
400
- | [metadata: M, value: SetStateAction<GetAtPath<S, P>>]
406
+ | [metadata: PartialM, value: SetStateAction<GetAtPath<S, P>>]
401
407
  | [value: SetStateAction<GetAtPath<S, P>>]
402
408
  ) => void,
403
409
  ];
@@ -417,7 +423,10 @@ export function createNoyaContext<
417
423
  function useStateManager() {
418
424
  const { noyaManager } = useAnyNoyaStateContext();
419
425
 
420
- return noyaManager.multiplayerStateManager as MultiplayerStateManager<S, M>;
426
+ return noyaManager.multiplayerStateManager as MultiplayerStateManager<
427
+ S,
428
+ PartialM
429
+ >;
421
430
  }
422
431
 
423
432
  function useEphemeralUserDataManager() {
@@ -433,7 +442,12 @@ export function createNoyaContext<
433
442
  }
434
443
 
435
444
  function useNoyaManager() {
436
- return useAnyNoyaStateContext().noyaManager as NoyaManager<S, M, E, MenuT>;
445
+ return useAnyNoyaStateContext().noyaManager as NoyaManager<
446
+ S,
447
+ PartialM,
448
+ E,
449
+ MenuT
450
+ >;
437
451
  }
438
452
 
439
453
  function useLeftMenuItems() {
@@ -0,0 +1,126 @@
1
+ import { Asset } from "@noya-app/noya-schemas";
2
+ import { Base64 } from "@noya-app/noya-utils";
3
+ import { NoyaManager, Static, Type } from "@noya-app/state-manager";
4
+ import { expect, it } from "bun:test";
5
+ import {
6
+ decodeAll,
7
+ encodeAll,
8
+ exportAll,
9
+ importAll,
10
+ } from "../inspector/serialization";
11
+
12
+ const asset1Data = new Uint8Array([0x00, 0x01, 0x02, 0x03]);
13
+ const asset1Id = "2717f281-307d-4e9e-aa3b-68d29d4a1723";
14
+ const asset1DataUrl = `data:application/octet-stream;base64,${Base64.encode(
15
+ asset1Data
16
+ )}`;
17
+ const asset1 = {
18
+ id: asset1Id,
19
+ url: asset1DataUrl,
20
+ createdAt: new Date().toISOString(),
21
+ size: asset1Data.byteLength,
22
+ };
23
+ const assets: Asset[] = [asset1];
24
+ const assetMap = {
25
+ [asset1Id]: asset1Data,
26
+ };
27
+
28
+ it("should serialize and deserialize the state", () => {
29
+ const schema = Type.Object({
30
+ name: Type.String(),
31
+ age: Type.Number(),
32
+ });
33
+
34
+ const initialState: Static<typeof schema> = {
35
+ name: "John",
36
+ age: 30,
37
+ };
38
+
39
+ const buffer = encodeAll({
40
+ state: initialState,
41
+ schema,
42
+ assets,
43
+ assetMap,
44
+ });
45
+
46
+ const decoded = decodeAll(buffer);
47
+
48
+ expect(decoded.state).toEqual(initialState);
49
+ expect(decoded.schema).toEqual(schema);
50
+ expect(decoded.assets).toEqual(assets);
51
+ expect(decoded.assetMap).toEqual(assetMap);
52
+ });
53
+
54
+ it("should import and export the noya manager state", async () => {
55
+ const schema = Type.Object({
56
+ name: Type.String(),
57
+ age: Type.Number(),
58
+ assetRef: Type.String(),
59
+ });
60
+
61
+ const initialState: Static<typeof schema> = {
62
+ name: "John",
63
+ age: 30,
64
+ assetRef: asset1Id,
65
+ };
66
+
67
+ const noyaManager = new NoyaManager(initialState, {
68
+ schema,
69
+ initialAssets: assets,
70
+ });
71
+
72
+ const buffer = await exportAll(noyaManager);
73
+
74
+ const stateToReplace = {
75
+ name: "Jane",
76
+ age: 20,
77
+ assetRef: "not-found",
78
+ };
79
+
80
+ const schemaToReplace = Type.Object({
81
+ name: Type.String(),
82
+ age: Type.Number(),
83
+ isAdmin: Type.Boolean(),
84
+ });
85
+
86
+ const newNoyaManager = new NoyaManager(stateToReplace, {
87
+ schema: schemaToReplace,
88
+ mode: "standalone",
89
+ });
90
+
91
+ let didCall = false;
92
+
93
+ await importAll(buffer, newNoyaManager, {
94
+ shouldAllowSchemaChange: () => {
95
+ didCall = true;
96
+ return true;
97
+ },
98
+ });
99
+
100
+ expect(didCall).toBe(true);
101
+
102
+ const importedAssets = newNoyaManager.assetManager.assets$.get();
103
+ const firstAsset = importedAssets.at(0);
104
+
105
+ if (!firstAsset) {
106
+ throw new Error("First asset not found");
107
+ }
108
+
109
+ expect(newNoyaManager.multiplayerStateManager.optimisticState$.get()).toEqual(
110
+ {
111
+ ...initialState,
112
+ assetRef: firstAsset.id,
113
+ }
114
+ );
115
+
116
+ expect(firstAsset).toMatchObject({
117
+ size: asset1.size,
118
+ });
119
+
120
+ // actualFetch to handle blob: urls
121
+ const firstAssetData = await (globalThis as any)
122
+ .actualFetch(firstAsset.url)
123
+ .then((res: any) => res.arrayBuffer());
124
+
125
+ expect(new Uint8Array(firstAssetData)).toEqual(asset1Data);
126
+ });
package/src/ai.ts CHANGED
@@ -1,3 +1,5 @@
1
+ "use client";
2
+
1
3
  import { AIToolDefinition } from "@noya-app/state-manager";
2
4
  import { useEffect } from "react";
3
5
  import { useAIManager } from "./NoyaStateContext";
package/src/index.ts CHANGED
@@ -2,6 +2,7 @@ export * from "@noya-app/state-manager";
2
2
  export * from "./ai";
3
3
  export * from "./components/UserPointersOverlay";
4
4
  export * from "./hooks";
5
+ export * from "./inspector/serialization";
5
6
  export * from "./inspector/StateInspector";
6
7
  export * from "./inspector/useStateInspector";
7
8
  export * from "./noyaApp";
@@ -0,0 +1,17 @@
1
+ import React from "react";
2
+
3
+ export function ColoredDot({ type }: { type: "success" | "error" }) {
4
+ return (
5
+ <span
6
+ style={{
7
+ display: "inline-block",
8
+ width: 6,
9
+ height: 6,
10
+ background:
11
+ type === "success" ? "rgba(0,200,0,0.8)" : "rgba(200,0,0,0.8)",
12
+ borderRadius: "50%",
13
+ verticalAlign: "middle",
14
+ }}
15
+ />
16
+ );
17
+ }
@@ -0,0 +1,48 @@
1
+ import React, { FC } from "react";
2
+ import { ObjectName, ObjectPreview } from "react-inspector";
3
+
4
+ type MessageDirection = "up" | "down";
5
+
6
+ export const ObjectRootLabel: FC<{
7
+ name?: string;
8
+ data: any;
9
+ direction?: MessageDirection;
10
+ }> = ({ name, data, direction }) => {
11
+ if (typeof name === "string") {
12
+ return (
13
+ <span>
14
+ <ObjectName name={name} />
15
+ <span>: </span>
16
+ <ObjectPreview data={data} />
17
+ </span>
18
+ );
19
+ }
20
+ if (direction === "up" || direction === "down") {
21
+ const arrow = direction === "up" ? "↑" : "↓";
22
+
23
+ return (
24
+ <span>
25
+ <span
26
+ style={{
27
+ background:
28
+ direction === "up" ? "rgba(0,255,0,0.2)" : "rgba(255,0,0,0.2)",
29
+ // color: "white",
30
+ width: 12,
31
+ height: 12,
32
+ borderRadius: 2,
33
+ display: "inline-flex",
34
+ justifyContent: "center",
35
+ alignItems: "center",
36
+ marginRight: 4,
37
+ lineHeight: "12px",
38
+ }}
39
+ >
40
+ {arrow}
41
+ </span>
42
+ <ObjectPreview data={data} />
43
+ </span>
44
+ );
45
+ } else {
46
+ return <ObjectPreview data={data} />;
47
+ }
48
+ };