@jami-studio/core 0.92.28 → 0.92.30
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +33 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/client/route-warmup.tsx +565 -543
- package/corpus/core/src/collab/param.ts +24 -0
- package/corpus/core/src/collab/routes.ts +187 -191
- package/corpus/core/src/collab/struct-routes.ts +134 -133
- package/corpus/core/src/deploy/build.ts +64 -0
- package/dist/client/route-warmup.d.ts +10 -0
- package/dist/client/route-warmup.d.ts.map +1 -1
- package/dist/client/route-warmup.js +18 -1
- package/dist/client/route-warmup.js.map +1 -1
- package/dist/collab/param.d.ts +3 -0
- package/dist/collab/param.d.ts.map +1 -0
- package/dist/collab/param.js +26 -0
- package/dist/collab/param.js.map +1 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/routes.d.ts.map +1 -1
- package/dist/collab/routes.js +6 -5
- package/dist/collab/routes.js.map +1 -1
- package/dist/collab/struct-routes.d.ts.map +1 -1
- package/dist/collab/struct-routes.js +5 -4
- package/dist/collab/struct-routes.js.map +1 -1
- package/dist/deploy/build.d.ts +21 -0
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +55 -0
- package/dist/deploy/build.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/progress/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +2 -2
- package/package.json +1 -1
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Router-param helper for collab routes.
|
|
3
|
+
*
|
|
4
|
+
* Collab docIds are structured (`plan:<id>:<block>`) and go into the URL
|
|
5
|
+
* path. Well-behaved HTTP clients percent-encode path segments
|
|
6
|
+
* (`encodeURIComponent(docId)` → `plan%3A...`), but h3 router params are NOT
|
|
7
|
+
* decoded by default — resolvers then see the encoded form, prefix checks
|
|
8
|
+
* (`docId.startsWith("plan:")`) fail, and the request 404s even though the
|
|
9
|
+
* raw-colon form works. Decode the param, tolerating docIds that contain a
|
|
10
|
+
* literal `%` (malformed escape sequences fall back to the raw value).
|
|
11
|
+
*/
|
|
12
|
+
import { getRouterParam } from "h3";
|
|
13
|
+
import type { H3Event } from "h3";
|
|
14
|
+
|
|
15
|
+
export function getCollabDocIdParam(event: H3Event): string | undefined {
|
|
16
|
+
const raw = getRouterParam(event, "docId");
|
|
17
|
+
if (!raw) return raw ?? undefined;
|
|
18
|
+
if (!raw.includes("%")) return raw;
|
|
19
|
+
try {
|
|
20
|
+
return decodeURIComponent(raw);
|
|
21
|
+
} catch {
|
|
22
|
+
return raw;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
@@ -1,191 +1,187 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTTP route handlers for collaborative editing.
|
|
3
|
-
*
|
|
4
|
-
* Mounted under /_agent-native/collab/ by the collab plugin.
|
|
5
|
-
*/
|
|
6
|
-
|
|
7
|
-
import {
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
} from "
|
|
13
|
-
import
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
state
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
*
|
|
78
|
-
*
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
const
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
*
|
|
113
|
-
*
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
const
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
return { ok: true, found: result.found };
|
|
190
|
-
},
|
|
191
|
-
);
|
|
1
|
+
/**
|
|
2
|
+
* HTTP route handlers for collaborative editing.
|
|
3
|
+
*
|
|
4
|
+
* Mounted under /_agent-native/collab/ by the collab plugin.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { defineEventHandler, setResponseStatus, getQuery } from "h3";
|
|
8
|
+
import type { H3Event } from "h3";
|
|
9
|
+
|
|
10
|
+
import { readBody } from "../server/h3-helpers.js";
|
|
11
|
+
import { getCollabDocIdParam } from "./param.js";
|
|
12
|
+
import { uint8ArrayToBase64, base64ToUint8Array } from "./storage.js";
|
|
13
|
+
import * as manager from "./ydoc-manager.js";
|
|
14
|
+
import { searchAndReplace as doSearchAndReplace } from "./ydoc-manager.js";
|
|
15
|
+
|
|
16
|
+
/** Default maximum payload size (2 MB). Overridden by plugin via event.context. */
|
|
17
|
+
const DEFAULT_MAX_BYTES = 2 * 1024 * 1024;
|
|
18
|
+
|
|
19
|
+
function getMaxPayloadBytes(event: H3Event): number {
|
|
20
|
+
return (event.context as any)?._collabMaxPayloadBytes ?? DEFAULT_MAX_BYTES;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Check the serialized body length against the configured limit.
|
|
25
|
+
* Returns true if within limits; sets 413 status and returns false otherwise.
|
|
26
|
+
*/
|
|
27
|
+
function enforcePayloadLimit(event: H3Event, body: unknown): boolean {
|
|
28
|
+
const maxBytes = getMaxPayloadBytes(event);
|
|
29
|
+
const encoded = typeof body === "string" ? body : JSON.stringify(body ?? "");
|
|
30
|
+
if (encoded.length > maxBytes) {
|
|
31
|
+
setResponseStatus(event, 413);
|
|
32
|
+
return false;
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
/**
|
|
38
|
+
* GET /_agent-native/collab/:docId/state
|
|
39
|
+
*
|
|
40
|
+
* Returns full Yjs document state as base64 for initial client load.
|
|
41
|
+
*/
|
|
42
|
+
export const getCollabState = defineEventHandler(async (event: H3Event) => {
|
|
43
|
+
const docId = getCollabDocIdParam(event);
|
|
44
|
+
if (!docId) {
|
|
45
|
+
setResponseStatus(event, 400);
|
|
46
|
+
return { error: "docId required" };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const query = getQuery(event);
|
|
50
|
+
const encodedStateVector =
|
|
51
|
+
typeof query.stateVector === "string" ? query.stateVector : null;
|
|
52
|
+
let state: Uint8Array;
|
|
53
|
+
if (encodedStateVector) {
|
|
54
|
+
try {
|
|
55
|
+
state = await manager.getIncUpdate(
|
|
56
|
+
docId,
|
|
57
|
+
base64ToUint8Array(encodedStateVector),
|
|
58
|
+
);
|
|
59
|
+
} catch {
|
|
60
|
+
setResponseStatus(event, 400);
|
|
61
|
+
return { error: "stateVector must be base64-encoded" };
|
|
62
|
+
}
|
|
63
|
+
} else {
|
|
64
|
+
state = await manager.getState(docId);
|
|
65
|
+
}
|
|
66
|
+
return {
|
|
67
|
+
docId,
|
|
68
|
+
state: uint8ArrayToBase64(state),
|
|
69
|
+
};
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* POST /_agent-native/collab/:docId/update
|
|
74
|
+
*
|
|
75
|
+
* Client sends a Yjs update (base64). Server applies it, persists, and
|
|
76
|
+
* emits a change event so other clients pick it up via polling.
|
|
77
|
+
*
|
|
78
|
+
* Body: { update: string (base64), requestSource?: string }
|
|
79
|
+
*/
|
|
80
|
+
export const postCollabUpdate = defineEventHandler(async (event: H3Event) => {
|
|
81
|
+
const docId = getCollabDocIdParam(event);
|
|
82
|
+
if (!docId) {
|
|
83
|
+
setResponseStatus(event, 400);
|
|
84
|
+
return { error: "docId required" };
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
const rawBody = await readBody(event);
|
|
88
|
+
if (!enforcePayloadLimit(event, rawBody)) {
|
|
89
|
+
return { error: "Payload too large" };
|
|
90
|
+
}
|
|
91
|
+
const { update, requestSource } = rawBody as {
|
|
92
|
+
update?: string;
|
|
93
|
+
requestSource?: string;
|
|
94
|
+
};
|
|
95
|
+
|
|
96
|
+
if (!update) {
|
|
97
|
+
setResponseStatus(event, 400);
|
|
98
|
+
return { error: "update (base64) required" };
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const binary = base64ToUint8Array(update);
|
|
102
|
+
await manager.applyUpdate(docId, binary, requestSource);
|
|
103
|
+
|
|
104
|
+
return { ok: true };
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* POST /_agent-native/collab/:docId/text
|
|
109
|
+
*
|
|
110
|
+
* Agent sends full text content. Server computes diff against current
|
|
111
|
+
* Yjs state and applies minimal operations.
|
|
112
|
+
*
|
|
113
|
+
* Body: { text: string, fieldName?: string, requestSource?: string }
|
|
114
|
+
*/
|
|
115
|
+
export const postCollabText = defineEventHandler(async (event: H3Event) => {
|
|
116
|
+
const docId = getCollabDocIdParam(event);
|
|
117
|
+
if (!docId) {
|
|
118
|
+
setResponseStatus(event, 400);
|
|
119
|
+
return { error: "docId required" };
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const rawBody = await readBody(event);
|
|
123
|
+
if (!enforcePayloadLimit(event, rawBody)) {
|
|
124
|
+
return { error: "Payload too large" };
|
|
125
|
+
}
|
|
126
|
+
const { text, fieldName, requestSource } = rawBody as {
|
|
127
|
+
text?: string;
|
|
128
|
+
fieldName?: string;
|
|
129
|
+
requestSource?: string;
|
|
130
|
+
};
|
|
131
|
+
|
|
132
|
+
if (text === undefined) {
|
|
133
|
+
setResponseStatus(event, 400);
|
|
134
|
+
return { error: "text required" };
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
const result = await manager.applyText(
|
|
138
|
+
docId,
|
|
139
|
+
text,
|
|
140
|
+
fieldName ?? "content",
|
|
141
|
+
requestSource ?? "agent",
|
|
142
|
+
);
|
|
143
|
+
|
|
144
|
+
return { ok: true, text: result };
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
/**
|
|
148
|
+
* POST /_agent-native/collab/:docId/search-replace
|
|
149
|
+
*
|
|
150
|
+
* Search-and-replace text in the Y.XmlFragment (ProseMirror tree).
|
|
151
|
+
* Produces minimal Yjs operations for cursor-preserving updates.
|
|
152
|
+
*
|
|
153
|
+
* Body: { find: string, replace: string, requestSource?: string }
|
|
154
|
+
*/
|
|
155
|
+
export const postCollabSearchReplace = defineEventHandler(
|
|
156
|
+
async (event: H3Event) => {
|
|
157
|
+
const docId = getCollabDocIdParam(event);
|
|
158
|
+
if (!docId) {
|
|
159
|
+
setResponseStatus(event, 400);
|
|
160
|
+
return { error: "docId required" };
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const rawBody = await readBody(event);
|
|
164
|
+
if (!enforcePayloadLimit(event, rawBody)) {
|
|
165
|
+
return { error: "Payload too large" };
|
|
166
|
+
}
|
|
167
|
+
const { find, replace, requestSource } = rawBody as {
|
|
168
|
+
find?: string;
|
|
169
|
+
replace?: string;
|
|
170
|
+
requestSource?: string;
|
|
171
|
+
};
|
|
172
|
+
|
|
173
|
+
if (!find) {
|
|
174
|
+
setResponseStatus(event, 400);
|
|
175
|
+
return { error: "find required" };
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
const result = await doSearchAndReplace(
|
|
179
|
+
docId,
|
|
180
|
+
find,
|
|
181
|
+
replace ?? "",
|
|
182
|
+
requestSource ?? "agent",
|
|
183
|
+
);
|
|
184
|
+
|
|
185
|
+
return { ok: true, found: result.found };
|
|
186
|
+
},
|
|
187
|
+
);
|