@nimbalyst/extension-sdk 0.4.0 → 0.6.0
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/CHANGELOG.md +39 -0
- package/dist/__tests__/editorHostCapabilities.test.d.ts +2 -0
- package/dist/__tests__/editorHostCapabilities.test.d.ts.map +1 -0
- package/dist/__tests__/editorHostCapabilities.test.js +24 -0
- package/dist/__tests__/fileDirectoryTree.test.js +34 -0
- package/dist/__tests__/gitOperationLog.test.d.ts +2 -0
- package/dist/__tests__/gitOperationLog.test.d.ts.map +1 -0
- package/dist/__tests__/gitOperationLog.test.js +51 -0
- package/dist/__tests__/useCollaborativeEditor.offline.test.js +43 -1
- package/dist/agents/AgentProtocol.d.ts +20 -0
- package/dist/agents/AgentProtocol.d.ts.map +1 -1
- package/dist/agents/AgentProtocolHost.d.ts +1 -1
- package/dist/agents/AgentProtocolHost.d.ts.map +1 -1
- package/dist/createReadOnlyHost.d.ts +11 -1
- package/dist/createReadOnlyHost.d.ts.map +1 -1
- package/dist/createReadOnlyHost.js +3 -0
- package/dist/editorHostCapabilities.d.ts +31 -0
- package/dist/editorHostCapabilities.d.ts.map +1 -0
- package/dist/editorHostCapabilities.js +35 -0
- package/dist/externalsPlugin.d.ts.map +1 -1
- package/dist/externalsPlugin.js +1 -5
- package/dist/fileDirectoryTree.d.ts +9 -2
- package/dist/fileDirectoryTree.d.ts.map +1 -1
- package/dist/fileDirectoryTree.js +82 -14
- package/dist/gitOperationLog.d.ts +104 -0
- package/dist/gitOperationLog.d.ts.map +1 -0
- package/dist/gitOperationLog.js +88 -0
- package/dist/index.d.ts +3 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -0
- package/dist/types/collab.d.ts +31 -2
- package/dist/types/collab.d.ts.map +1 -1
- package/dist/types/comments.d.ts +104 -0
- package/dist/types/comments.d.ts.map +1 -0
- package/dist/types/comments.js +2 -0
- package/dist/types/editor.d.ts +201 -1
- package/dist/types/editor.d.ts.map +1 -1
- package/dist/types/extension.d.ts +49 -0
- package/dist/types/extension.d.ts.map +1 -1
- package/dist/types/index.d.ts +1 -0
- package/dist/types/index.d.ts.map +1 -1
- package/dist/types/index.js +1 -0
- package/dist/types/panel.d.ts +21 -0
- package/dist/types/panel.d.ts.map +1 -1
- package/dist/useCollaborativeEditor.d.ts +13 -8
- package/dist/useCollaborativeEditor.d.ts.map +1 -1
- package/dist/useCollaborativeEditor.js +92 -74
- package/package.json +14 -2
|
@@ -139,8 +139,10 @@ export function useCollaborativeEditor(host, config) {
|
|
|
139
139
|
return;
|
|
140
140
|
let cancelled = false;
|
|
141
141
|
let handle = null;
|
|
142
|
+
let starting = false;
|
|
143
|
+
let unregisterContentFlush = null;
|
|
142
144
|
const tryStart = async () => {
|
|
143
|
-
if (cancelled || handle)
|
|
145
|
+
if (cancelled || handle || starting)
|
|
144
146
|
return;
|
|
145
147
|
const cfg = resolveConfig(configRef.current);
|
|
146
148
|
const connected = collab.getStatus() === 'connected';
|
|
@@ -149,86 +151,100 @@ export function useCollaborativeEditor(host, config) {
|
|
|
149
151
|
// can reopen offline; only first-open seeding requires a live transport.
|
|
150
152
|
if (!connected && cfg.isEmpty(collab.yDoc))
|
|
151
153
|
return;
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
// awaiting -- they would have raced through the WebSocket and
|
|
173
|
-
// applied their update during our await gap. Avoid double-seeding
|
|
174
|
-
// in that case; CRDT merge would otherwise insert duplicate
|
|
175
|
-
// content unless the seed is fully deterministic.
|
|
176
|
-
else if (cfg.isEmpty(collab.yDoc)) {
|
|
177
|
-
collab.yDoc.transact(() => {
|
|
178
|
-
cfg.seed(collab.yDoc, content);
|
|
179
|
-
}, COLLAB_INIT_ORIGIN);
|
|
180
|
-
// Durability: the seed the user sees locally must reach the server
|
|
181
|
-
// before this provider can tear down. flushWithAck resolves only
|
|
182
|
-
// after a server-persisted ack; flushLocalState is the deprecated
|
|
183
|
-
// fire-and-forget fallback for older hosts.
|
|
184
|
-
const flushed = collab.flushWithAck
|
|
185
|
-
? await collab.flushWithAck()
|
|
186
|
-
: (await collab.flushLocalState?.(), true);
|
|
187
|
-
if (!flushed) {
|
|
188
|
-
const err = new Error('Seed flush was not confirmed by the server before timeout; content may not have persisted.');
|
|
189
|
-
console.warn('[useCollaborativeEditor]', err.message);
|
|
190
|
-
setSeedError(err);
|
|
191
|
-
collab.reportSeedOutcome?.({ ok: false, error: err });
|
|
192
|
-
cfg.onSeedOutcome?.({ ok: false, error: err });
|
|
154
|
+
starting = true;
|
|
155
|
+
try {
|
|
156
|
+
// NEVER seed when the transport skipped payloads it could not decode:
|
|
157
|
+
// the Y.Doc looking empty then means "content exists but is unreadable
|
|
158
|
+
// on this client", and seeding would write a default document over the
|
|
159
|
+
// real content for every client (the "Untitled map" clobber).
|
|
160
|
+
const undecoded = collab.hasUndecodedContent?.() === true;
|
|
161
|
+
if (connected && !undecoded && cfg.isEmpty(collab.yDoc)) {
|
|
162
|
+
try {
|
|
163
|
+
const content = await collab.loadInitialContent();
|
|
164
|
+
if (cancelled)
|
|
165
|
+
return;
|
|
166
|
+
// NEVER seed from empty content. A host that has no bytes for this
|
|
167
|
+
// document (reopen of an already-shared doc: no in-memory share
|
|
168
|
+
// payload, no file) returns ''/empty -- seeding from that writes a
|
|
169
|
+
// default document into the shared room and clobbers whatever the
|
|
170
|
+
// room's real content is for every client. Fall through to bind:
|
|
171
|
+
// the room content (or a teammate's seed) will populate the doc.
|
|
172
|
+
if (!hasSeedableContent(content)) {
|
|
173
|
+
console.warn('[useCollaborativeEditor] Skipping first-open seed: host returned empty initial content.');
|
|
193
174
|
}
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
175
|
+
// Re-check emptiness in case another client seeded while we were
|
|
176
|
+
// awaiting -- they would have raced through the WebSocket and
|
|
177
|
+
// applied their update during our await gap. Avoid double-seeding
|
|
178
|
+
// in that case; CRDT merge would otherwise insert duplicate
|
|
179
|
+
// content unless the seed is fully deterministic.
|
|
180
|
+
else if (cfg.isEmpty(collab.yDoc)) {
|
|
181
|
+
collab.yDoc.transact(() => {
|
|
182
|
+
cfg.seed(collab.yDoc, content);
|
|
183
|
+
}, COLLAB_INIT_ORIGIN);
|
|
184
|
+
// Durability: the seed the user sees locally must reach the server
|
|
185
|
+
// before this provider can tear down. flushWithAck resolves only
|
|
186
|
+
// after a server-persisted ack; flushLocalState is the deprecated
|
|
187
|
+
// fire-and-forget fallback for older hosts.
|
|
188
|
+
const flushed = collab.flushWithAck
|
|
189
|
+
? await collab.flushWithAck()
|
|
190
|
+
: (await collab.flushLocalState?.(), true);
|
|
191
|
+
if (!flushed) {
|
|
192
|
+
const err = new Error('Seed flush was not confirmed by the server before timeout; content may not have persisted.');
|
|
193
|
+
console.warn('[useCollaborativeEditor]', err.message);
|
|
194
|
+
setSeedError(err);
|
|
195
|
+
collab.reportSeedOutcome?.({ ok: false, error: err });
|
|
196
|
+
cfg.onSeedOutcome?.({ ok: false, error: err });
|
|
197
|
+
}
|
|
198
|
+
else {
|
|
199
|
+
setSeedError(null);
|
|
200
|
+
collab.reportSeedOutcome?.({ ok: true });
|
|
201
|
+
cfg.onSeedOutcome?.({ ok: true });
|
|
202
|
+
}
|
|
198
203
|
}
|
|
199
204
|
}
|
|
205
|
+
catch (err) {
|
|
206
|
+
console.error('[useCollaborativeEditor] Failed to load/seed initial content:', err);
|
|
207
|
+
setSeedError(err);
|
|
208
|
+
collab.reportSeedOutcome?.({ ok: false, error: err });
|
|
209
|
+
cfg.onSeedOutcome?.({ ok: false, error: err });
|
|
210
|
+
// Continue with bind -- the doc may still be usable once another
|
|
211
|
+
// client seeds it.
|
|
212
|
+
}
|
|
200
213
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
collab.
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
// imperative API. Destroy the freshly-built handle so any
|
|
221
|
-
// observers/awareness subscriptions inside it get cleaned up.
|
|
222
|
-
try {
|
|
223
|
-
resolved.destroy();
|
|
214
|
+
if (cancelled)
|
|
215
|
+
return;
|
|
216
|
+
const created = cfg.bind({
|
|
217
|
+
yDoc: collab.yDoc,
|
|
218
|
+
awareness: collab.awareness,
|
|
219
|
+
user: collab.user,
|
|
220
|
+
});
|
|
221
|
+
const resolved = created instanceof Promise ? await created : created;
|
|
222
|
+
if (cancelled) {
|
|
223
|
+
// Effect unmounted while we were awaiting the extension's
|
|
224
|
+
// imperative API. Destroy the freshly-built handle so any
|
|
225
|
+
// observers/awareness subscriptions inside it get cleaned up.
|
|
226
|
+
try {
|
|
227
|
+
resolved.destroy();
|
|
228
|
+
}
|
|
229
|
+
catch (err) {
|
|
230
|
+
console.error('[useCollaborativeEditor] post-cancel destroy failed:', err);
|
|
231
|
+
}
|
|
232
|
+
return;
|
|
224
233
|
}
|
|
225
|
-
|
|
226
|
-
|
|
234
|
+
handle = resolved;
|
|
235
|
+
// A binding that debounces its local pushes holds the newest edit
|
|
236
|
+
// outside the CRDT. Hand the host a drain so it can complete a write
|
|
237
|
+
// before reporting it done -- notably after a mutating AI tool, which
|
|
238
|
+
// otherwise returns success while the edit is still in the debounce.
|
|
239
|
+
if (typeof handle.syncNow === 'function') {
|
|
240
|
+
const syncNow = handle.syncNow.bind(handle);
|
|
241
|
+
unregisterContentFlush = collab.registerContentFlush?.(syncNow) ?? null;
|
|
227
242
|
}
|
|
228
|
-
|
|
243
|
+
setBinding(handle);
|
|
244
|
+
}
|
|
245
|
+
finally {
|
|
246
|
+
starting = false;
|
|
229
247
|
}
|
|
230
|
-
handle = resolved;
|
|
231
|
-
setBinding(handle);
|
|
232
248
|
};
|
|
233
249
|
void tryStart();
|
|
234
250
|
const unsubscribe = collab.onStatusChange(() => {
|
|
@@ -237,6 +253,8 @@ export function useCollaborativeEditor(host, config) {
|
|
|
237
253
|
return () => {
|
|
238
254
|
cancelled = true;
|
|
239
255
|
unsubscribe();
|
|
256
|
+
unregisterContentFlush?.();
|
|
257
|
+
unregisterContentFlush = null;
|
|
240
258
|
if (handle) {
|
|
241
259
|
handle.destroy();
|
|
242
260
|
handle = null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nimbalyst/extension-sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "SDK for building Nimbalyst extensions",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"./dist/validate.js": "./dist/validate.browser.js"
|
|
18
18
|
},
|
|
19
19
|
"nimbalyst": {
|
|
20
|
-
"minAppVersion": "0.
|
|
20
|
+
"minAppVersion": "0.78.0"
|
|
21
21
|
},
|
|
22
22
|
"publishConfig": {
|
|
23
23
|
"access": "public"
|
|
@@ -33,6 +33,14 @@
|
|
|
33
33
|
"types": "./dist/collab/index.d.ts",
|
|
34
34
|
"import": "./dist/collab/index.js"
|
|
35
35
|
},
|
|
36
|
+
"./agents": {
|
|
37
|
+
"types": "./dist/agents/index.d.ts",
|
|
38
|
+
"import": "./dist/agents/index.js"
|
|
39
|
+
},
|
|
40
|
+
"./types/editor": {
|
|
41
|
+
"types": "./dist/types/editor.d.ts",
|
|
42
|
+
"import": "./dist/types/editor.js"
|
|
43
|
+
},
|
|
36
44
|
"./vite": {
|
|
37
45
|
"types": "./dist/vite.d.ts",
|
|
38
46
|
"import": "./dist/vite.js"
|
|
@@ -52,6 +60,10 @@
|
|
|
52
60
|
"./file-mask": {
|
|
53
61
|
"types": "./dist/fileMask.d.ts",
|
|
54
62
|
"import": "./dist/fileMask.js"
|
|
63
|
+
},
|
|
64
|
+
"./git-operation-log": {
|
|
65
|
+
"types": "./dist/gitOperationLog.d.ts",
|
|
66
|
+
"import": "./dist/gitOperationLog.js"
|
|
55
67
|
}
|
|
56
68
|
},
|
|
57
69
|
"files": [
|