@opendaw/studio-core 0.0.130 → 0.0.132
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/AssetService.d.ts.map +1 -1
- package/dist/AssetService.js +11 -2
- package/dist/AudioOfflineRenderer.js +1 -1
- package/dist/EffectBox.d.ts +2 -2
- package/dist/EffectBox.d.ts.map +1 -1
- package/dist/EffectFactories.d.ts +3 -0
- package/dist/EffectFactories.d.ts.map +1 -1
- package/dist/EffectFactories.js +19 -3
- package/dist/Engine.d.ts +1 -1
- package/dist/Engine.d.ts.map +1 -1
- package/dist/EngineFacade.d.ts +1 -1
- package/dist/EngineFacade.d.ts.map +1 -1
- package/dist/EngineFacade.js +2 -2
- package/dist/EngineWorklet.d.ts +1 -1
- package/dist/EngineWorklet.d.ts.map +1 -1
- package/dist/EngineWorklet.js +9 -46
- package/dist/MonitoringRouter.d.ts +10 -0
- package/dist/MonitoringRouter.d.ts.map +1 -0
- package/dist/MonitoringRouter.js +89 -0
- package/dist/capture/CaptureAudio.d.ts +10 -0
- package/dist/capture/CaptureAudio.d.ts.map +1 -1
- package/dist/capture/CaptureAudio.js +105 -30
- package/dist/capture/RecordAudio.d.ts.map +1 -1
- package/dist/capture/RecordAudio.js +10 -2
- package/dist/capture/Recording.js +1 -1
- package/dist/processors.js +28 -28
- package/dist/processors.js.map +4 -4
- package/dist/project/AudioWavExport.d.ts +6 -0
- package/dist/project/AudioWavExport.d.ts.map +1 -0
- package/dist/project/AudioWavExport.js +15 -0
- package/dist/project/NoteMidiExport.d.ts +9 -0
- package/dist/project/NoteMidiExport.d.ts.map +1 -0
- package/dist/project/NoteMidiExport.js +27 -0
- package/dist/project/Project.d.ts.map +1 -1
- package/dist/project/Project.js +1 -2
- package/dist/project/ProjectApi.d.ts +3 -1
- package/dist/project/ProjectApi.d.ts.map +1 -1
- package/dist/project/ProjectApi.js +8 -0
- package/dist/project/ProjectStorage.d.ts.map +1 -1
- package/dist/project/ProjectStorage.js +1 -0
- package/dist/project/audio/AudioContentModifier.d.ts.map +1 -1
- package/dist/project/audio/AudioContentModifier.js +43 -3
- package/dist/project/index.d.ts +2 -0
- package/dist/project/index.d.ts.map +1 -1
- package/dist/project/index.js +2 -0
- package/dist/project/migration/MigrateValueEventCollection.test.js +3 -3
- package/dist/samples/OpenSampleAPI.d.ts +1 -0
- package/dist/samples/OpenSampleAPI.d.ts.map +1 -1
- package/dist/samples/OpenSampleAPI.js +1 -0
- package/dist/samples/SampleService.js +1 -1
- package/dist/soundfont/DefaultSoundfontLoader.d.ts.map +1 -1
- package/dist/soundfont/DefaultSoundfontLoader.js +3 -0
- package/dist/soundfont/OpenSoundfontAPI.d.ts +1 -0
- package/dist/soundfont/OpenSoundfontAPI.d.ts.map +1 -1
- package/dist/soundfont/OpenSoundfontAPI.js +1 -0
- package/dist/soundfont/SoundfontService.js +1 -1
- package/dist/sync-log/SyncLogWriter.d.ts.map +1 -1
- package/dist/sync-log/SyncLogWriter.js +3 -2
- package/dist/ui/clipboard/ClipboardUtils.js +1 -1
- package/dist/ui/clipboard/types/DevicesClipboardHandler.d.ts.map +1 -1
- package/dist/ui/clipboard/types/DevicesClipboardHandler.js +22 -12
- package/dist/ui/clipboard/types/DevicesClipboardHandler.test.js +124 -4
- package/dist/ysync/YService.d.ts.map +1 -1
- package/dist/ysync/YService.js +0 -5
- package/dist/ysync/YSync.d.ts +1 -0
- package/dist/ysync/YSync.d.ts.map +1 -1
- package/dist/ysync/YSync.js +127 -84
- package/dist/ysync/YSync.test.d.ts +2 -0
- package/dist/ysync/YSync.test.d.ts.map +1 -0
- package/dist/ysync/YSync.test.js +259 -0
- package/package.json +16 -15
|
@@ -0,0 +1,259 @@
|
|
|
1
|
+
import { beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import * as Y from "yjs";
|
|
3
|
+
import { Option, panic, safeExecute, UUID } from "@opendaw/lib-std";
|
|
4
|
+
import { Box, BoxGraph, BooleanField, Int32Field, NoPointers, PointerField, StringField } from "@opendaw/lib-box";
|
|
5
|
+
import { YSync } from "./YSync";
|
|
6
|
+
// --- Minimal box fixtures ------------------------------------------------
|
|
7
|
+
var Pointer;
|
|
8
|
+
(function (Pointer) {
|
|
9
|
+
Pointer[Pointer["Target"] = 0] = "Target";
|
|
10
|
+
})(Pointer || (Pointer = {}));
|
|
11
|
+
class LeafBox extends Box {
|
|
12
|
+
static create(graph, uuid, constructor) {
|
|
13
|
+
return graph.stageBox(new LeafBox({
|
|
14
|
+
uuid, graph, name: "LeafBox",
|
|
15
|
+
pointerRules: { accepts: [Pointer.Target], mandatory: false, exclusive: false }
|
|
16
|
+
}), constructor);
|
|
17
|
+
}
|
|
18
|
+
constructor(construct) { super(construct); }
|
|
19
|
+
initializeFields() {
|
|
20
|
+
return {
|
|
21
|
+
1: Int32Field.create({ parent: this, fieldKey: 1, fieldName: "count", deprecated: false, pointerRules: NoPointers }, "any", "none"),
|
|
22
|
+
2: StringField.create({ parent: this, fieldKey: 2, fieldName: "label", deprecated: false, pointerRules: NoPointers }),
|
|
23
|
+
3: BooleanField.create({ parent: this, fieldKey: 3, fieldName: "flag", deprecated: false, pointerRules: NoPointers }, false)
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
accept(visitor) { return safeExecute(visitor.visitLeafBox, this); }
|
|
27
|
+
get tags() { return {}; }
|
|
28
|
+
get count() { return this.getField(1); }
|
|
29
|
+
get label() { return this.getField(2); }
|
|
30
|
+
get flag() { return this.getField(3); }
|
|
31
|
+
}
|
|
32
|
+
class RefBox extends Box {
|
|
33
|
+
static create(graph, uuid, constructor) {
|
|
34
|
+
return graph.stageBox(new RefBox({ uuid, graph, name: "RefBox", pointerRules: NoPointers }), constructor);
|
|
35
|
+
}
|
|
36
|
+
constructor(construct) { super(construct); }
|
|
37
|
+
initializeFields() {
|
|
38
|
+
return {
|
|
39
|
+
1: PointerField.create({ parent: this, fieldKey: 1, fieldName: "target", deprecated: false, pointerRules: NoPointers }, Pointer.Target, false)
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
accept(visitor) { return safeExecute(visitor.visitRefBox, this); }
|
|
43
|
+
get tags() { return {}; }
|
|
44
|
+
get target() { return this.getField(1); }
|
|
45
|
+
}
|
|
46
|
+
const factory = (name, graph, uuid, constructor) => {
|
|
47
|
+
switch (name) {
|
|
48
|
+
case "LeafBox":
|
|
49
|
+
return LeafBox.create(graph, uuid, constructor);
|
|
50
|
+
case "RefBox":
|
|
51
|
+
return RefBox.create(graph, uuid, constructor);
|
|
52
|
+
default:
|
|
53
|
+
return panic(`Unknown box: ${name}`);
|
|
54
|
+
}
|
|
55
|
+
};
|
|
56
|
+
const makeScene = async () => {
|
|
57
|
+
const doc = new Y.Doc();
|
|
58
|
+
const boxes = doc.getMap("boxes");
|
|
59
|
+
const graph = new BoxGraph(Option.wrap(factory));
|
|
60
|
+
const sync = await YSync.populateRoom({ boxGraph: graph, boxes });
|
|
61
|
+
return { doc, boxes, graph, sync };
|
|
62
|
+
};
|
|
63
|
+
const beginCommit = (graph, fn) => {
|
|
64
|
+
graph.beginTransaction();
|
|
65
|
+
try {
|
|
66
|
+
fn();
|
|
67
|
+
}
|
|
68
|
+
finally {
|
|
69
|
+
graph.endTransaction();
|
|
70
|
+
}
|
|
71
|
+
};
|
|
72
|
+
// --- Tests ---------------------------------------------------------------
|
|
73
|
+
describe("YSync.flush", () => {
|
|
74
|
+
let scene;
|
|
75
|
+
beforeEach(async () => {
|
|
76
|
+
scene = await makeScene();
|
|
77
|
+
});
|
|
78
|
+
it("writes new boxes, primitives, and pointers into yjs", () => {
|
|
79
|
+
let leafId;
|
|
80
|
+
let refId;
|
|
81
|
+
beginCommit(scene.graph, () => {
|
|
82
|
+
const leaf = LeafBox.create(scene.graph, UUID.generate());
|
|
83
|
+
leaf.count.setValue(42);
|
|
84
|
+
leaf.label.setValue("hello");
|
|
85
|
+
leaf.flag.setValue(true);
|
|
86
|
+
const ref = RefBox.create(scene.graph, UUID.generate());
|
|
87
|
+
ref.target.refer(leaf);
|
|
88
|
+
leafId = leaf.address.uuid;
|
|
89
|
+
refId = ref.address.uuid;
|
|
90
|
+
});
|
|
91
|
+
expect(scene.boxes.size).toBe(2);
|
|
92
|
+
const leafMap = scene.boxes.get(UUID.toString(leafId));
|
|
93
|
+
expect(leafMap.get("name")).toBe("LeafBox");
|
|
94
|
+
const leafFields = leafMap.get("fields");
|
|
95
|
+
expect(leafFields).toBeInstanceOf(Y.Map);
|
|
96
|
+
expect(leafFields.get("1")).toBe(42);
|
|
97
|
+
expect(leafFields.get("2")).toBe("hello");
|
|
98
|
+
expect(leafFields.get("3")).toBe(true);
|
|
99
|
+
const refMap = scene.boxes.get(UUID.toString(refId));
|
|
100
|
+
const refFields = refMap.get("fields");
|
|
101
|
+
expect(typeof refFields.get("1")).toBe("string");
|
|
102
|
+
});
|
|
103
|
+
it("drops phantom boxes created and deleted in the same transaction", () => {
|
|
104
|
+
let phantomId;
|
|
105
|
+
let survivorId;
|
|
106
|
+
beginCommit(scene.graph, () => {
|
|
107
|
+
const phantom = LeafBox.create(scene.graph, UUID.generate());
|
|
108
|
+
phantom.count.setValue(7);
|
|
109
|
+
phantomId = phantom.address.uuid;
|
|
110
|
+
phantom.delete();
|
|
111
|
+
const survivor = LeafBox.create(scene.graph, UUID.generate());
|
|
112
|
+
survivor.label.setValue("keep");
|
|
113
|
+
survivorId = survivor.address.uuid;
|
|
114
|
+
});
|
|
115
|
+
expect(scene.boxes.has(UUID.toString(phantomId))).toBe(false);
|
|
116
|
+
expect(scene.boxes.has(UUID.toString(survivorId))).toBe(true);
|
|
117
|
+
const survivorMap = scene.boxes.get(UUID.toString(survivorId));
|
|
118
|
+
const fields = survivorMap.get("fields");
|
|
119
|
+
expect(fields.get("2")).toBe("keep");
|
|
120
|
+
});
|
|
121
|
+
it("updates mutable fields on existing boxes", () => {
|
|
122
|
+
let uuid;
|
|
123
|
+
beginCommit(scene.graph, () => {
|
|
124
|
+
const leaf = LeafBox.create(scene.graph, UUID.generate());
|
|
125
|
+
leaf.count.setValue(1);
|
|
126
|
+
uuid = leaf.address.uuid;
|
|
127
|
+
});
|
|
128
|
+
const leaf = scene.graph.findBox(uuid).unwrap();
|
|
129
|
+
beginCommit(scene.graph, () => {
|
|
130
|
+
leaf.count.setValue(99);
|
|
131
|
+
leaf.flag.setValue(true);
|
|
132
|
+
});
|
|
133
|
+
const map = scene.boxes.get(UUID.toString(uuid));
|
|
134
|
+
const fields = map.get("fields");
|
|
135
|
+
expect(fields.get("1")).toBe(99);
|
|
136
|
+
expect(fields.get("3")).toBe(true);
|
|
137
|
+
});
|
|
138
|
+
it("drains the queue even if a later flush throws", () => {
|
|
139
|
+
// First commit succeeds and establishes the box in yjs.
|
|
140
|
+
let uuid;
|
|
141
|
+
beginCommit(scene.graph, () => {
|
|
142
|
+
const leaf = LeafBox.create(scene.graph, UUID.generate());
|
|
143
|
+
leaf.label.setValue("initial");
|
|
144
|
+
uuid = leaf.address.uuid;
|
|
145
|
+
});
|
|
146
|
+
expect(scene.boxes.has(UUID.toString(uuid))).toBe(true);
|
|
147
|
+
// Simulate drift: wipe the "fields" sub-map of the stored box so the next
|
|
148
|
+
// pointer/primitive update cannot traverse into it.
|
|
149
|
+
const map = scene.boxes.get(UUID.toString(uuid));
|
|
150
|
+
map.delete("fields");
|
|
151
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
152
|
+
try {
|
|
153
|
+
// This used to throw a TypeError inside transact and leave #updates populated.
|
|
154
|
+
expect(() => beginCommit(scene.graph, () => {
|
|
155
|
+
const leaf = scene.graph.findBox(uuid).unwrap();
|
|
156
|
+
leaf.label.setValue("second");
|
|
157
|
+
})).not.toThrow();
|
|
158
|
+
expect(warn).toHaveBeenCalled();
|
|
159
|
+
}
|
|
160
|
+
finally {
|
|
161
|
+
warn.mockRestore();
|
|
162
|
+
}
|
|
163
|
+
// A subsequent transaction must still be able to flush cleanly.
|
|
164
|
+
let secondId;
|
|
165
|
+
beginCommit(scene.graph, () => {
|
|
166
|
+
const next = LeafBox.create(scene.graph, UUID.generate());
|
|
167
|
+
next.label.setValue("third");
|
|
168
|
+
secondId = next.address.uuid;
|
|
169
|
+
});
|
|
170
|
+
expect(scene.boxes.has(UUID.toString(secondId))).toBe(true);
|
|
171
|
+
const nextMap = scene.boxes.get(UUID.toString(secondId));
|
|
172
|
+
const nextFields = nextMap.get("fields");
|
|
173
|
+
expect(nextFields.get("2")).toBe("third");
|
|
174
|
+
});
|
|
175
|
+
it("clears pending updates on aborted/rolled-back transactions", () => {
|
|
176
|
+
// Stage work then abort; the pending queue must not leak into the next tx.
|
|
177
|
+
scene.graph.beginTransaction();
|
|
178
|
+
LeafBox.create(scene.graph, UUID.generate()).count.setValue(123);
|
|
179
|
+
scene.graph.abortTransaction();
|
|
180
|
+
expect(scene.boxes.size).toBe(0);
|
|
181
|
+
let uuid;
|
|
182
|
+
beginCommit(scene.graph, () => {
|
|
183
|
+
const leaf = LeafBox.create(scene.graph, UUID.generate());
|
|
184
|
+
leaf.label.setValue("fresh");
|
|
185
|
+
uuid = leaf.address.uuid;
|
|
186
|
+
});
|
|
187
|
+
expect(scene.boxes.size).toBe(1);
|
|
188
|
+
const map = scene.boxes.get(UUID.toString(uuid));
|
|
189
|
+
const fields = map.get("fields");
|
|
190
|
+
expect(fields.get("2")).toBe("fresh");
|
|
191
|
+
});
|
|
192
|
+
it("mirrors the crash scenario: create + mutate + cascade-delete in one transaction", () => {
|
|
193
|
+
// A close analogue of report #920: a newly-created box is mutated and, in
|
|
194
|
+
// the same transaction, another existing box is deleted (defer + unstage).
|
|
195
|
+
// The queue contains New, Pointer, Primitive and Delete updates; a missing
|
|
196
|
+
// "fields" sub-map on the box being deleted used to take down the whole
|
|
197
|
+
// transact callback. With the fix, the flush must still complete and leave
|
|
198
|
+
// the yjs state consistent with the graph.
|
|
199
|
+
let survivorId;
|
|
200
|
+
let oldLeafId;
|
|
201
|
+
// Pre-existing state.
|
|
202
|
+
beginCommit(scene.graph, () => {
|
|
203
|
+
const oldLeaf = LeafBox.create(scene.graph, UUID.generate());
|
|
204
|
+
oldLeaf.label.setValue("old");
|
|
205
|
+
oldLeafId = oldLeaf.address.uuid;
|
|
206
|
+
});
|
|
207
|
+
// Intentionally corrupt the stored box to provoke the broken-fields path.
|
|
208
|
+
const leafMap = scene.boxes.get(UUID.toString(oldLeafId));
|
|
209
|
+
leafMap.delete("fields");
|
|
210
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
211
|
+
try {
|
|
212
|
+
beginCommit(scene.graph, () => {
|
|
213
|
+
const newLeaf = LeafBox.create(scene.graph, UUID.generate());
|
|
214
|
+
newLeaf.label.setValue("survivor");
|
|
215
|
+
survivorId = newLeaf.address.uuid;
|
|
216
|
+
const ref = RefBox.create(scene.graph, UUID.generate());
|
|
217
|
+
ref.target.refer(newLeaf);
|
|
218
|
+
scene.graph.findBox(oldLeafId).unwrap().delete();
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
finally {
|
|
222
|
+
warn.mockRestore();
|
|
223
|
+
}
|
|
224
|
+
// Old leaf is gone, new leaf is present with all fields.
|
|
225
|
+
expect(scene.boxes.has(UUID.toString(oldLeafId))).toBe(false);
|
|
226
|
+
const survivorMap = scene.boxes.get(UUID.toString(survivorId));
|
|
227
|
+
expect(survivorMap).toBeInstanceOf(Y.Map);
|
|
228
|
+
const survivorFields = survivorMap.get("fields");
|
|
229
|
+
expect(survivorFields.get("2")).toBe("survivor");
|
|
230
|
+
// Graph and yjs agree on box count.
|
|
231
|
+
expect(scene.boxes.size).toBe(scene.graph.boxes().length);
|
|
232
|
+
});
|
|
233
|
+
it("tolerates a pointer update whose field path is broken mid-way", () => {
|
|
234
|
+
let refId;
|
|
235
|
+
let leafId;
|
|
236
|
+
beginCommit(scene.graph, () => {
|
|
237
|
+
const leaf = LeafBox.create(scene.graph, UUID.generate());
|
|
238
|
+
const ref = RefBox.create(scene.graph, UUID.generate());
|
|
239
|
+
ref.target.refer(leaf);
|
|
240
|
+
refId = ref.address.uuid;
|
|
241
|
+
leafId = leaf.address.uuid;
|
|
242
|
+
});
|
|
243
|
+
const refMap = scene.boxes.get(UUID.toString(refId));
|
|
244
|
+
const refFields = refMap.get("fields");
|
|
245
|
+
// Pretend the pointer slot was swapped to a non-map value (drift).
|
|
246
|
+
refFields.set("1", "garbage");
|
|
247
|
+
const warn = vi.spyOn(console, "warn").mockImplementation(() => { });
|
|
248
|
+
try {
|
|
249
|
+
expect(() => beginCommit(scene.graph, () => {
|
|
250
|
+
const ref = scene.graph.findBox(refId).unwrap();
|
|
251
|
+
ref.target.defer();
|
|
252
|
+
})).not.toThrow();
|
|
253
|
+
}
|
|
254
|
+
finally {
|
|
255
|
+
warn.mockRestore();
|
|
256
|
+
}
|
|
257
|
+
expect(scene.graph.findBox(leafId).nonEmpty()).toBe(true);
|
|
258
|
+
});
|
|
259
|
+
});
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@opendaw/studio-core",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.132",
|
|
4
4
|
"license": "LGPL-3.0-or-later",
|
|
5
5
|
"publishConfig": {
|
|
6
6
|
"access": "public"
|
|
@@ -33,17 +33,18 @@
|
|
|
33
33
|
"test": "vitest run --config vitest.config.ts"
|
|
34
34
|
},
|
|
35
35
|
"dependencies": {
|
|
36
|
-
"@opendaw/lib-box": "^0.0.
|
|
37
|
-
"@opendaw/lib-dawproject": "^0.0.
|
|
38
|
-
"@opendaw/lib-dom": "^0.0.
|
|
39
|
-
"@opendaw/lib-dsp": "^0.0.
|
|
40
|
-
"@opendaw/lib-fusion": "^0.0.
|
|
41
|
-
"@opendaw/lib-
|
|
42
|
-
"@opendaw/lib-
|
|
36
|
+
"@opendaw/lib-box": "^0.0.82",
|
|
37
|
+
"@opendaw/lib-dawproject": "^0.0.67",
|
|
38
|
+
"@opendaw/lib-dom": "^0.0.80",
|
|
39
|
+
"@opendaw/lib-dsp": "^0.0.81",
|
|
40
|
+
"@opendaw/lib-fusion": "^0.0.89",
|
|
41
|
+
"@opendaw/lib-midi": "^0.0.63",
|
|
42
|
+
"@opendaw/lib-runtime": "^0.0.76",
|
|
43
|
+
"@opendaw/lib-std": "^0.0.75",
|
|
43
44
|
"@opendaw/nam-wasm": "^1.0.3",
|
|
44
|
-
"@opendaw/studio-adapters": "^0.0.
|
|
45
|
-
"@opendaw/studio-boxes": "^0.0.
|
|
46
|
-
"@opendaw/studio-enums": "^0.0.
|
|
45
|
+
"@opendaw/studio-adapters": "^0.0.104",
|
|
46
|
+
"@opendaw/studio-boxes": "^0.0.87",
|
|
47
|
+
"@opendaw/studio-enums": "^0.0.72",
|
|
47
48
|
"dropbox": "^10.34.0",
|
|
48
49
|
"y-websocket": "^1.4.5",
|
|
49
50
|
"yjs": "^13.6.27",
|
|
@@ -57,10 +58,10 @@
|
|
|
57
58
|
"@ffmpeg/ffmpeg": "^0.12.15",
|
|
58
59
|
"@ffmpeg/util": "^0.12.2",
|
|
59
60
|
"@opendaw/eslint-config": "^0.0.27",
|
|
60
|
-
"@opendaw/studio-core-processors": "^0.0.
|
|
61
|
-
"@opendaw/studio-core-workers": "^0.0.
|
|
62
|
-
"@opendaw/studio-forge-boxes": "^0.0.
|
|
61
|
+
"@opendaw/studio-core-processors": "^0.0.108",
|
|
62
|
+
"@opendaw/studio-core-workers": "^0.0.99",
|
|
63
|
+
"@opendaw/studio-forge-boxes": "^0.0.87",
|
|
63
64
|
"@opendaw/typescript-config": "^0.0.29"
|
|
64
65
|
},
|
|
65
|
-
"gitHead": "
|
|
66
|
+
"gitHead": "6da62e8f1d245fb6a332ec254e286db6a479c397"
|
|
66
67
|
}
|