@noya-app/noya-multiplayer-react 0.1.65 → 0.1.67
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 +15 -0
- package/dist/index.bundle.js +19 -19
- package/dist/index.d.mts +95 -25
- package/dist/index.d.ts +95 -25
- package/dist/index.js +419 -177
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +381 -145
- package/dist/index.mjs.map +1 -1
- package/package.json +2 -2
- package/src/NoyaStateContext.tsx +179 -90
- package/src/__tests__/NoyaStateContext.test.ts +3 -2
- package/src/__tests__/resourcePayload.test.ts +263 -0
- package/src/__tests__/serialize.test.ts +8 -6
- package/src/hooks.ts +46 -47
- package/src/index.ts +1 -0
- package/src/inspector/StateInspector.tsx +16 -7
- package/src/inspector/serialization.ts +6 -12
- package/src/inspector/useStateInspector.tsx +15 -7
- package/src/noyaApp.ts +37 -17
- package/src/singleton.tsx +140 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@noya-app/noya-multiplayer-react",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.67",
|
|
4
4
|
"main": "./dist/index.js",
|
|
5
5
|
"module": "./dist/index.mjs",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"dev": "npm run build -- --watch"
|
|
13
13
|
},
|
|
14
14
|
"dependencies": {
|
|
15
|
-
"@noya-app/state-manager": "0.1.
|
|
15
|
+
"@noya-app/state-manager": "0.1.53",
|
|
16
16
|
"@noya-app/emitter": "0.1.0",
|
|
17
17
|
"@noya-app/observable": "0.1.12",
|
|
18
18
|
"@noya-app/task-runner": "0.1.6",
|
package/src/NoyaStateContext.tsx
CHANGED
|
@@ -2,11 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
import { MenuItem } from "@noya-app/noya-designsystem";
|
|
4
4
|
import {
|
|
5
|
+
CreateResourceParametersWithoutAccessibleByFileId,
|
|
5
6
|
diffResourceMaps,
|
|
6
7
|
Input,
|
|
7
8
|
MediaItem,
|
|
9
|
+
ModifiedResource,
|
|
8
10
|
OutputTransform,
|
|
9
11
|
Resource,
|
|
12
|
+
ResourceCreate,
|
|
10
13
|
ResourceCreateMap,
|
|
11
14
|
ResourceMap,
|
|
12
15
|
} from "@noya-app/noya-schemas";
|
|
@@ -19,7 +22,6 @@ import {
|
|
|
19
22
|
import { useJsonMemo, useStableCallback } from "@noya-app/react-utils";
|
|
20
23
|
import {
|
|
21
24
|
ActivityEventsManager,
|
|
22
|
-
ConnectedUsersManager,
|
|
23
25
|
CreateParams,
|
|
24
26
|
EphemeralUserDataManager,
|
|
25
27
|
MultiplayerPatchMetadata,
|
|
@@ -46,11 +48,112 @@ import React, {
|
|
|
46
48
|
import {
|
|
47
49
|
AppTheme,
|
|
48
50
|
AppViewType,
|
|
49
|
-
|
|
51
|
+
useNoyaStateInternal,
|
|
50
52
|
UseNoyaStateOptions,
|
|
51
53
|
} from "./noyaApp";
|
|
54
|
+
import { getNoyaManagerSingleton, getNoyaSingletonBindings } from "./singleton";
|
|
52
55
|
import { useObservable } from "./useObservable";
|
|
53
56
|
|
|
57
|
+
export function resourceToCreateParameters(
|
|
58
|
+
resource: ResourceCreate
|
|
59
|
+
): CreateResourceParametersWithoutAccessibleByFileId {
|
|
60
|
+
switch (resource.type) {
|
|
61
|
+
case "directory":
|
|
62
|
+
return {
|
|
63
|
+
path: resource.path,
|
|
64
|
+
type: "directory",
|
|
65
|
+
};
|
|
66
|
+
case "asset":
|
|
67
|
+
return {
|
|
68
|
+
path: resource.path,
|
|
69
|
+
type: "asset",
|
|
70
|
+
assetId: resource.assetId,
|
|
71
|
+
asset: resource.asset,
|
|
72
|
+
};
|
|
73
|
+
case "file":
|
|
74
|
+
return {
|
|
75
|
+
path: resource.path,
|
|
76
|
+
type: "file",
|
|
77
|
+
fileId: resource.fileId,
|
|
78
|
+
};
|
|
79
|
+
case "resource":
|
|
80
|
+
return {
|
|
81
|
+
path: resource.path,
|
|
82
|
+
type: "resource",
|
|
83
|
+
fileId: resource.fileId,
|
|
84
|
+
resourceId: resource.resourceId,
|
|
85
|
+
};
|
|
86
|
+
default:
|
|
87
|
+
throw new Error(`Unknown resource type: ${(resource as any).type}`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
type BuildCreateResourcePayloadParameters = {
|
|
92
|
+
addedResources: ResourceCreate[];
|
|
93
|
+
modifiedResources: ModifiedResource[];
|
|
94
|
+
newResourceMap: ResourceCreateMap;
|
|
95
|
+
resourceMap: ResourceMap;
|
|
96
|
+
};
|
|
97
|
+
|
|
98
|
+
type BuildCreateResourcePayloadResult = {
|
|
99
|
+
payload: CreateResourceParametersWithoutAccessibleByFileId[];
|
|
100
|
+
consumedModifiedResourceIds: string[];
|
|
101
|
+
};
|
|
102
|
+
|
|
103
|
+
export function buildCreateResourcePayload({
|
|
104
|
+
addedResources,
|
|
105
|
+
modifiedResources,
|
|
106
|
+
newResourceMap,
|
|
107
|
+
resourceMap,
|
|
108
|
+
}: BuildCreateResourcePayloadParameters): BuildCreateResourcePayloadResult {
|
|
109
|
+
const toCreateParameters = resourceToCreateParameters;
|
|
110
|
+
|
|
111
|
+
const resourcesById = new Map(
|
|
112
|
+
Object.values(resourceMap).map((resource) => [resource.id, resource])
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
const payload: CreateResourceParametersWithoutAccessibleByFileId[] = [
|
|
116
|
+
...addedResources.map(toCreateParameters),
|
|
117
|
+
];
|
|
118
|
+
|
|
119
|
+
const handledModifiedResourceIds: string[] = [];
|
|
120
|
+
|
|
121
|
+
const assetReplacements = modifiedResources
|
|
122
|
+
.filter(
|
|
123
|
+
(resource) =>
|
|
124
|
+
resource.assetId !== undefined || resource.asset !== undefined
|
|
125
|
+
)
|
|
126
|
+
.map((resource) => {
|
|
127
|
+
const previousResource = resourcesById.get(resource.id);
|
|
128
|
+
const path = resource.path ?? previousResource?.path;
|
|
129
|
+
|
|
130
|
+
if (!path) return undefined;
|
|
131
|
+
|
|
132
|
+
const updated = newResourceMap[path];
|
|
133
|
+
|
|
134
|
+
if (!updated || updated.type !== "asset") return undefined;
|
|
135
|
+
|
|
136
|
+
handledModifiedResourceIds.push(resource.id);
|
|
137
|
+
|
|
138
|
+
return toCreateParameters(updated);
|
|
139
|
+
})
|
|
140
|
+
.filter(
|
|
141
|
+
(
|
|
142
|
+
resource
|
|
143
|
+
): resource is CreateResourceParametersWithoutAccessibleByFileId =>
|
|
144
|
+
resource !== undefined
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
const deduped = new Map(
|
|
148
|
+
[...payload, ...assetReplacements].map((item) => [item.path, item])
|
|
149
|
+
);
|
|
150
|
+
|
|
151
|
+
return {
|
|
152
|
+
payload: Array.from(deduped.values()),
|
|
153
|
+
consumedModifiedResourceIds: [...new Set(handledModifiedResourceIds)],
|
|
154
|
+
};
|
|
155
|
+
}
|
|
156
|
+
|
|
54
157
|
export interface NoyaStateProviderProps<
|
|
55
158
|
S,
|
|
56
159
|
M extends object,
|
|
@@ -79,6 +182,16 @@ export function useAnyNoyaStateContext() {
|
|
|
79
182
|
const value = useContext(AnyNoyaStateContext);
|
|
80
183
|
|
|
81
184
|
if (!value) {
|
|
185
|
+
const bindings = getNoyaSingletonBindings();
|
|
186
|
+
|
|
187
|
+
if (bindings) {
|
|
188
|
+
return {
|
|
189
|
+
noyaManager: getNoyaManagerSingleton(),
|
|
190
|
+
theme: bindings.theme,
|
|
191
|
+
viewType: bindings.viewType,
|
|
192
|
+
} as AnyNoyaStateContextValue;
|
|
193
|
+
}
|
|
194
|
+
|
|
82
195
|
throw new Error(
|
|
83
196
|
"useNoyaStateContext must be used within a NoyaStateProvider"
|
|
84
197
|
);
|
|
@@ -111,8 +224,10 @@ export function useAsset(id: string | undefined) {
|
|
|
111
224
|
const { noyaManager } = useAnyNoyaStateContext();
|
|
112
225
|
const observable = useMemo(
|
|
113
226
|
() =>
|
|
114
|
-
noyaManager.assetManager.assets$.map(
|
|
115
|
-
assets
|
|
227
|
+
noyaManager.assetManager.assets$.map(
|
|
228
|
+
(assets) =>
|
|
229
|
+
assets.find((a) => a.stableId === id) ??
|
|
230
|
+
assets.find((a) => a.id === id)
|
|
116
231
|
),
|
|
117
232
|
[noyaManager, id]
|
|
118
233
|
);
|
|
@@ -212,12 +327,9 @@ export function usePipelineManager() {
|
|
|
212
327
|
return noyaManager.pipelineManager;
|
|
213
328
|
}
|
|
214
329
|
|
|
215
|
-
export const ConnectedUsersContext = createContext<
|
|
216
|
-
ConnectedUsersManager | undefined
|
|
217
|
-
>(undefined);
|
|
218
|
-
|
|
219
330
|
export function useConnectedUsersManager() {
|
|
220
|
-
|
|
331
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
332
|
+
return noyaManager.connectedUsersManager;
|
|
221
333
|
}
|
|
222
334
|
|
|
223
335
|
export function useConnectedUsers() {
|
|
@@ -241,12 +353,9 @@ export function useConnectedUser(userId?: string | null) {
|
|
|
241
353
|
);
|
|
242
354
|
}
|
|
243
355
|
|
|
244
|
-
export const AnyEphemeralUserDataManagerContext = createContext<
|
|
245
|
-
EphemeralUserDataManager<object> | undefined
|
|
246
|
-
>(undefined);
|
|
247
|
-
|
|
248
356
|
export function useAnyEphemeralUserData() {
|
|
249
|
-
|
|
357
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
358
|
+
return noyaManager.ephemeralUserDataManager;
|
|
250
359
|
}
|
|
251
360
|
|
|
252
361
|
export function useCurrentUserId() {
|
|
@@ -259,6 +368,22 @@ export function useIsProcessing() {
|
|
|
259
368
|
return useObservable(noyaManager.isProcessing$);
|
|
260
369
|
}
|
|
261
370
|
|
|
371
|
+
export function useFileName() {
|
|
372
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
373
|
+
return useObservable(noyaManager.filePropertyManager.optimisticValue$);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
export function useFilePropertyManager() {
|
|
377
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
378
|
+
return noyaManager.filePropertyManager;
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
export function useSetFileNameState() {
|
|
382
|
+
const fileName = useFileName();
|
|
383
|
+
const filePropertyManager = useFilePropertyManager();
|
|
384
|
+
return [fileName, filePropertyManager.updateName] as const;
|
|
385
|
+
}
|
|
386
|
+
|
|
262
387
|
export function useResourceManager(): ResourceManager {
|
|
263
388
|
const { noyaManager } = useAnyNoyaStateContext();
|
|
264
389
|
return noyaManager.resourceManager;
|
|
@@ -306,63 +431,45 @@ export function useResources({
|
|
|
306
431
|
const { addedResources, removedResources, modifiedResources } =
|
|
307
432
|
diffResourceMaps(resourceMap, newResourceMap, "id");
|
|
308
433
|
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
type: "directory" as const,
|
|
316
|
-
};
|
|
317
|
-
case "asset":
|
|
318
|
-
return {
|
|
319
|
-
path: resource.path,
|
|
320
|
-
type: "asset" as const,
|
|
321
|
-
assetId: resource.assetId,
|
|
322
|
-
asset: resource.asset,
|
|
323
|
-
};
|
|
324
|
-
case "file":
|
|
325
|
-
return {
|
|
326
|
-
path: resource.path,
|
|
327
|
-
type: "file" as const,
|
|
328
|
-
fileId: resource.fileId,
|
|
329
|
-
};
|
|
330
|
-
case "resource":
|
|
331
|
-
return {
|
|
332
|
-
path: resource.path,
|
|
333
|
-
type: "resource" as const,
|
|
334
|
-
fileId: resource.fileId,
|
|
335
|
-
resourceId: resource.resourceId,
|
|
336
|
-
};
|
|
337
|
-
default:
|
|
338
|
-
throw new Error(
|
|
339
|
-
`Unknown resource type: ${(resource as any).type}`
|
|
340
|
-
);
|
|
341
|
-
}
|
|
434
|
+
const { payload: createPayload, consumedModifiedResourceIds } =
|
|
435
|
+
buildCreateResourcePayload({
|
|
436
|
+
addedResources,
|
|
437
|
+
modifiedResources,
|
|
438
|
+
newResourceMap,
|
|
439
|
+
resourceMap,
|
|
342
440
|
});
|
|
343
441
|
|
|
344
|
-
|
|
442
|
+
if (createPayload.length > 0) {
|
|
443
|
+
await resourceManager.createResource(createPayload);
|
|
345
444
|
}
|
|
346
445
|
|
|
347
|
-
|
|
348
|
-
|
|
446
|
+
const consumedModifiedResourceIdsSet = new Set(
|
|
447
|
+
consumedModifiedResourceIds
|
|
448
|
+
);
|
|
349
449
|
|
|
350
|
-
|
|
351
|
-
|
|
450
|
+
const remainingModifiedResources = modifiedResources.filter(
|
|
451
|
+
(resource) => !consumedModifiedResourceIdsSet.has(resource.id)
|
|
452
|
+
);
|
|
453
|
+
|
|
454
|
+
await resourceManager.deleteResource(
|
|
455
|
+
removedResources.map((resource) => ({ id: resource.id }))
|
|
456
|
+
);
|
|
457
|
+
|
|
458
|
+
if (shouldDeleteAssets) {
|
|
459
|
+
for (const resource of removedResources) {
|
|
460
|
+
if (resource.type === "asset") {
|
|
352
461
|
await assetManager.delete(resource.assetId);
|
|
353
|
-
} catch (error) {
|
|
354
|
-
console.error("Failed to delete asset:", error);
|
|
355
462
|
}
|
|
356
463
|
}
|
|
357
464
|
}
|
|
358
465
|
|
|
359
466
|
await Promise.all(
|
|
360
|
-
|
|
467
|
+
remainingModifiedResources.map(({ id, path, assetId, asset }) =>
|
|
361
468
|
resourceManager.updateResource({
|
|
362
469
|
id,
|
|
363
470
|
path,
|
|
364
471
|
assetId,
|
|
365
|
-
asset,
|
|
472
|
+
...(assetId ? {} : asset ? { asset } : {}),
|
|
366
473
|
})
|
|
367
474
|
)
|
|
368
475
|
);
|
|
@@ -453,11 +560,6 @@ export function createNoyaContext<
|
|
|
453
560
|
NoyaManager<S, M, E> | undefined
|
|
454
561
|
>;
|
|
455
562
|
|
|
456
|
-
const EphemeralUserDataManagerContext =
|
|
457
|
-
AnyEphemeralUserDataManagerContext as React.Context<
|
|
458
|
-
EphemeralUserDataManager<E> | undefined
|
|
459
|
-
>;
|
|
460
|
-
|
|
461
563
|
function NoyaContextProvider({
|
|
462
564
|
children,
|
|
463
565
|
contextValue,
|
|
@@ -467,15 +569,7 @@ export function createNoyaContext<
|
|
|
467
569
|
}) {
|
|
468
570
|
return (
|
|
469
571
|
<AnyNoyaStateContext.Provider value={contextValue}>
|
|
470
|
-
|
|
471
|
-
value={contextValue.noyaManager.connectedUsersManager}
|
|
472
|
-
>
|
|
473
|
-
<EphemeralUserDataManagerContext.Provider
|
|
474
|
-
value={contextValue.noyaManager.ephemeralUserDataManager}
|
|
475
|
-
>
|
|
476
|
-
{children}
|
|
477
|
-
</EphemeralUserDataManagerContext.Provider>
|
|
478
|
-
</ConnectedUsersContext.Provider>
|
|
572
|
+
{children}
|
|
479
573
|
</AnyNoyaStateContext.Provider>
|
|
480
574
|
);
|
|
481
575
|
}
|
|
@@ -485,17 +579,19 @@ export function createNoyaContext<
|
|
|
485
579
|
initialState,
|
|
486
580
|
...options
|
|
487
581
|
}: NoyaStateProviderProps<S, M, E, MenuT>) {
|
|
488
|
-
const [, , { noyaManager, theme, viewType }] =
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
582
|
+
const [, , { noyaManager, theme, viewType }] = useNoyaStateInternal<
|
|
583
|
+
S,
|
|
584
|
+
M,
|
|
585
|
+
E,
|
|
586
|
+
MenuT
|
|
587
|
+
>(initialState, {
|
|
588
|
+
schema,
|
|
589
|
+
mergeHistoryEntries,
|
|
590
|
+
outputTransforms,
|
|
591
|
+
inputs,
|
|
592
|
+
safeEval,
|
|
593
|
+
...(options as any),
|
|
594
|
+
});
|
|
499
595
|
|
|
500
596
|
const contextValue = useMemo(
|
|
501
597
|
() => ({
|
|
@@ -635,15 +731,8 @@ export function createNoyaContext<
|
|
|
635
731
|
}
|
|
636
732
|
|
|
637
733
|
function useEphemeralUserDataManager() {
|
|
638
|
-
const
|
|
639
|
-
|
|
640
|
-
if (!ephemeralUserData) {
|
|
641
|
-
throw new Error(
|
|
642
|
-
"useEphemeralUserData must be used within a NoyaStateProvider"
|
|
643
|
-
);
|
|
644
|
-
}
|
|
645
|
-
|
|
646
|
-
return ephemeralUserData;
|
|
734
|
+
const { noyaManager } = useAnyNoyaStateContext();
|
|
735
|
+
return noyaManager.ephemeralUserDataManager as EphemeralUserDataManager<E>;
|
|
647
736
|
}
|
|
648
737
|
|
|
649
738
|
function useNoyaManager() {
|
|
@@ -13,7 +13,6 @@ it("typechecks", () => {
|
|
|
13
13
|
});
|
|
14
14
|
|
|
15
15
|
// For type checking
|
|
16
|
-
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
17
16
|
function TestComponent() {
|
|
18
17
|
const state = useValue();
|
|
19
18
|
const count = useValue("count");
|
|
@@ -38,6 +37,8 @@ it("typechecks", () => {
|
|
|
38
37
|
expectType<number>(count3);
|
|
39
38
|
expectType<number>(count4);
|
|
40
39
|
}
|
|
40
|
+
|
|
41
|
+
void TestComponent;
|
|
41
42
|
});
|
|
42
43
|
|
|
43
|
-
function expectType<T>(
|
|
44
|
+
function expectType<T>(_value: T): void {}
|
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
import {
|
|
2
|
+
ModifiedResource,
|
|
3
|
+
Resource,
|
|
4
|
+
ResourceCreate,
|
|
5
|
+
ResourceCreateMap,
|
|
6
|
+
ResourceMap,
|
|
7
|
+
} from "@noya-app/noya-schemas";
|
|
8
|
+
import { describe, expect, it } from "bun:test";
|
|
9
|
+
import {
|
|
10
|
+
buildCreateResourcePayload,
|
|
11
|
+
resourceToCreateParameters,
|
|
12
|
+
} from "../NoyaStateContext";
|
|
13
|
+
|
|
14
|
+
const ISO_DATE = "2024-01-01T00:00:00.000Z";
|
|
15
|
+
|
|
16
|
+
type AssetResource = Extract<Resource, { type: "asset" }>;
|
|
17
|
+
type AssetResourceCreate = Extract<ResourceCreate, { type: "asset" }>;
|
|
18
|
+
type DirectoryResourceCreate = Extract<ResourceCreate, { type: "directory" }>;
|
|
19
|
+
|
|
20
|
+
function createAssetResource(
|
|
21
|
+
overrides: Partial<AssetResource> = {}
|
|
22
|
+
): AssetResource {
|
|
23
|
+
const resource: AssetResource = {
|
|
24
|
+
id: "resource-id",
|
|
25
|
+
stableId: "stable-id",
|
|
26
|
+
path: "assets/default.png",
|
|
27
|
+
createdAt: ISO_DATE,
|
|
28
|
+
updatedAt: ISO_DATE,
|
|
29
|
+
accessibleByFileId: null,
|
|
30
|
+
accessibleByFileVersionId: null,
|
|
31
|
+
type: "asset",
|
|
32
|
+
assetId: "asset-id",
|
|
33
|
+
...overrides,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
return resource;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function createAssetResourceCreate(
|
|
40
|
+
overrides: Partial<AssetResourceCreate> = {}
|
|
41
|
+
): AssetResourceCreate {
|
|
42
|
+
const resource: AssetResourceCreate = {
|
|
43
|
+
id: "resource-id",
|
|
44
|
+
stableId: "stable-id",
|
|
45
|
+
path: "assets/default.png",
|
|
46
|
+
createdAt: ISO_DATE,
|
|
47
|
+
updatedAt: ISO_DATE,
|
|
48
|
+
accessibleByFileId: null,
|
|
49
|
+
accessibleByFileVersionId: null,
|
|
50
|
+
type: "asset",
|
|
51
|
+
assetId: "asset-id",
|
|
52
|
+
asset: {
|
|
53
|
+
content: "ZGF0YQ==",
|
|
54
|
+
contentType: "image/png",
|
|
55
|
+
encoding: "base64" as const,
|
|
56
|
+
},
|
|
57
|
+
...overrides,
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
return resource;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function createDirectoryResourceCreate(
|
|
64
|
+
overrides: Partial<DirectoryResourceCreate> = {}
|
|
65
|
+
): DirectoryResourceCreate {
|
|
66
|
+
const resource: DirectoryResourceCreate = {
|
|
67
|
+
id: "directory-id",
|
|
68
|
+
stableId: "directory-stable",
|
|
69
|
+
path: "docs",
|
|
70
|
+
createdAt: ISO_DATE,
|
|
71
|
+
updatedAt: ISO_DATE,
|
|
72
|
+
accessibleByFileId: null,
|
|
73
|
+
accessibleByFileVersionId: null,
|
|
74
|
+
type: "directory",
|
|
75
|
+
...overrides,
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
return resource;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("resource payload helpers", () => {
|
|
82
|
+
it("returns create parameters for added resources", () => {
|
|
83
|
+
const addedResource = createDirectoryResourceCreate();
|
|
84
|
+
|
|
85
|
+
const result = buildCreateResourcePayload({
|
|
86
|
+
addedResources: [addedResource],
|
|
87
|
+
modifiedResources: [],
|
|
88
|
+
newResourceMap: {
|
|
89
|
+
[addedResource.path]: addedResource,
|
|
90
|
+
} satisfies ResourceCreateMap,
|
|
91
|
+
resourceMap: {},
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
expect(result.payload).toEqual([
|
|
95
|
+
{
|
|
96
|
+
path: "docs",
|
|
97
|
+
type: "directory",
|
|
98
|
+
},
|
|
99
|
+
]);
|
|
100
|
+
expect(result.consumedModifiedResourceIds).toEqual([]);
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
it("includes asset replacements when a modified resource swaps the asset", () => {
|
|
104
|
+
const existingResource = createAssetResource({
|
|
105
|
+
id: "existing-resource",
|
|
106
|
+
stableId: "existing-stable",
|
|
107
|
+
path: "images/logo.png",
|
|
108
|
+
assetId: "asset-old",
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
const updatedResource = createAssetResourceCreate({
|
|
112
|
+
id: "new-resource",
|
|
113
|
+
stableId: "new-stable",
|
|
114
|
+
path: "images/logo.png",
|
|
115
|
+
assetId: "asset-new",
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
const result = buildCreateResourcePayload({
|
|
119
|
+
addedResources: [],
|
|
120
|
+
modifiedResources: [
|
|
121
|
+
{
|
|
122
|
+
id: existingResource.id,
|
|
123
|
+
stableId: existingResource.stableId,
|
|
124
|
+
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
|
+
asset: updatedResource.asset,
|
|
170
|
+
} satisfies ModifiedResource,
|
|
171
|
+
],
|
|
172
|
+
newResourceMap: {
|
|
173
|
+
[updatedResource.path]: updatedResource,
|
|
174
|
+
} satisfies ResourceCreateMap,
|
|
175
|
+
resourceMap: {
|
|
176
|
+
[existingResource.path]: existingResource,
|
|
177
|
+
} satisfies ResourceMap,
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
expect(result.payload).toEqual([
|
|
181
|
+
resourceToCreateParameters(updatedResource),
|
|
182
|
+
]);
|
|
183
|
+
expect(result.consumedModifiedResourceIds).toEqual([
|
|
184
|
+
existingResource.id,
|
|
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,
|
|
225
|
+
]);
|
|
226
|
+
});
|
|
227
|
+
|
|
228
|
+
it("excludes modified resources without asset changes from the consumed list", () => {
|
|
229
|
+
const existingResource = createAssetResource({
|
|
230
|
+
id: "existing-resource",
|
|
231
|
+
stableId: "existing-stable",
|
|
232
|
+
path: "images/logo.png",
|
|
233
|
+
assetId: "asset-old",
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
const updatedResource = createAssetResourceCreate({
|
|
237
|
+
id: "existing-resource",
|
|
238
|
+
stableId: "existing-stable",
|
|
239
|
+
path: "images/logo.png",
|
|
240
|
+
assetId: "asset-old",
|
|
241
|
+
});
|
|
242
|
+
|
|
243
|
+
const result = buildCreateResourcePayload({
|
|
244
|
+
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,
|
|
258
|
+
});
|
|
259
|
+
|
|
260
|
+
expect(result.payload).toEqual([]);
|
|
261
|
+
expect(result.consumedModifiedResourceIds).toEqual([]);
|
|
262
|
+
});
|
|
263
|
+
});
|