@moloodev/editor-sdk 0.0.0-stage → 0.2.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.
Files changed (110) hide show
  1. package/package.json +62 -3
  2. package/src/channel.ts +287 -0
  3. package/src/chrome-manifest.ts +111 -0
  4. package/src/commands/clipboard/router.ts +99 -0
  5. package/src/commands/clipboard/types.ts +20 -0
  6. package/src/commands/history/stack.ts +53 -0
  7. package/src/commands/icons/catalog.ts +172 -0
  8. package/src/commands/index.ts +100 -0
  9. package/src/commands/keys/display.ts +16 -0
  10. package/src/commands/keys/normalize.ts +55 -0
  11. package/src/commands/keys/reserved.ts +62 -0
  12. package/src/commands/keys/validate.ts +77 -0
  13. package/src/commands/manifest/configuration.ts +59 -0
  14. package/src/commands/manifest/contribution-manifest-schema.ts +56 -0
  15. package/src/commands/manifest/editor-document.ts +501 -0
  16. package/src/commands/manifest/ids.ts +2 -0
  17. package/src/commands/manifest/merge.ts +215 -0
  18. package/src/commands/manifest/types.ts +113 -0
  19. package/src/commands/manifest/validate-manifest.ts +45 -0
  20. package/src/commands/react/context.tsx +249 -0
  21. package/src/commands/react/hotkey-defaults.ts +11 -0
  22. package/src/commands/react/scope-context.tsx +31 -0
  23. package/src/commands/react/scope-provider.tsx +123 -0
  24. package/src/commands/react/store.ts +182 -0
  25. package/src/commands/registry/create.ts +55 -0
  26. package/src/commands/registry/types.ts +12 -0
  27. package/src/commands/when/evaluate.ts +80 -0
  28. package/src/commands/when/index.ts +19 -0
  29. package/src/commands/when/parse.ts +276 -0
  30. package/src/commands/when/types.ts +24 -0
  31. package/src/commands/when/validate.ts +20 -0
  32. package/src/crdt/awareness.ts +29 -0
  33. package/src/crdt/bytes.ts +26 -0
  34. package/src/crdt/connect.ts +285 -0
  35. package/src/crdt/diff.ts +86 -0
  36. package/src/crdt/doc.ts +67 -0
  37. package/src/crdt/front-matter.ts +45 -0
  38. package/src/crdt/index.ts +76 -0
  39. package/src/crdt/persist.ts +40 -0
  40. package/src/crdt/protocol.ts +119 -0
  41. package/src/crdt/shared.ts +104 -0
  42. package/src/crdt/text-body.ts +125 -0
  43. package/src/crdt/undo.ts +59 -0
  44. package/src/crdt/vite-plugin.ts +42 -0
  45. package/src/crdt/yjs.ts +16 -0
  46. package/src/editor-common.ts +160 -0
  47. package/src/editor-id.ts +56 -0
  48. package/src/frame-attributes.ts +9 -0
  49. package/src/index.ts +229 -0
  50. package/src/json-schema.ts +13 -0
  51. package/src/layout/bind-toolbar.ts +60 -0
  52. package/src/layout/catalog.ts +82 -0
  53. package/src/layout/chrome/command-affordances.ts +47 -0
  54. package/src/layout/chrome/command-layout-state.ts +42 -0
  55. package/src/layout/chrome/editor-menu.tsx +358 -0
  56. package/src/layout/chrome/editor-shortcut-keys.tsx +27 -0
  57. package/src/layout/chrome/menu-styles.ts +3 -0
  58. package/src/layout/chrome/mid-menu-nodes.ts +27 -0
  59. package/src/layout/chrome/use-editor-command-affordances.ts +33 -0
  60. package/src/layout/chrome/visible-menu-leaves.ts +15 -0
  61. package/src/layout/content-error-notice.tsx +31 -0
  62. package/src/layout/define-editor-layout.ts +60 -0
  63. package/src/layout/define-editor-layout.types.ts +57 -0
  64. package/src/layout/doc-undo-bridge.tsx +22 -0
  65. package/src/layout/dock-zones.tsx +61 -0
  66. package/src/layout/drop.ts +39 -0
  67. package/src/layout/editor-layout.tsx +287 -0
  68. package/src/layout/editor-loading.tsx +23 -0
  69. package/src/layout/frame-layout-binding.tsx +74 -0
  70. package/src/layout/frame.ts +23 -0
  71. package/src/layout/hide.ts +103 -0
  72. package/src/layout/index.ts +14 -0
  73. package/src/layout/layout-binding.tsx +42 -0
  74. package/src/layout/layout-storage-key.ts +6 -0
  75. package/src/layout/layout-values.ts +48 -0
  76. package/src/layout/mutate.ts +486 -0
  77. package/src/layout/nesting-context.tsx +20 -0
  78. package/src/layout/panel-header-action.tsx +29 -0
  79. package/src/layout/panel-header.tsx +168 -0
  80. package/src/layout/panel-hide-menu.tsx +6 -0
  81. package/src/layout/panel-menu.tsx +85 -0
  82. package/src/layout/panel-section.tsx +114 -0
  83. package/src/layout/panel-size.ts +94 -0
  84. package/src/layout/panel-title.tsx +5 -0
  85. package/src/layout/registry.tsx +333 -0
  86. package/src/layout/restore-panel.ts +26 -0
  87. package/src/layout/slots-context.tsx +189 -0
  88. package/src/layout/tab-button.tsx +60 -0
  89. package/src/layout/toolbar-scroll.ts +22 -0
  90. package/src/layout/toolbar.tsx +168 -0
  91. package/src/layout/types.ts +41 -0
  92. package/src/layout/use-collapsed-sections.ts +50 -0
  93. package/src/layout/validate-layout-spec.ts +143 -0
  94. package/src/message-samples.ts +377 -0
  95. package/src/origin-headers.ts +57 -0
  96. package/src/print/index.ts +10 -0
  97. package/src/print/inspect.ts +16 -0
  98. package/src/print/schema.ts +122 -0
  99. package/src/protocol-entry.ts +19 -0
  100. package/src/protocol.ts +471 -0
  101. package/src/render-result.ts +25 -0
  102. package/src/view/editor-chrome.ts +28 -0
  103. package/src/view/hooks.ts +260 -0
  104. package/src/view/mount-editor-view.ts +36 -0
  105. package/src/view/port-binding.ts +127 -0
  106. package/src/view/port-commands.ts +36 -0
  107. package/src/view/port.ts +35 -0
  108. package/src/view/theme-class.ts +11 -0
  109. package/src/view.ts +313 -0
  110. package/README.md +0 -3
@@ -0,0 +1,471 @@
1
+ import {
2
+ bundleConfirmSchema,
3
+ compositionSchema,
4
+ publicationSchema,
5
+ } from "@moloodev/platform";
6
+ import { documentSchema } from "./print";
7
+ import { z } from "zod";
8
+ import {
9
+ chromeManifestSchema,
10
+ type ChromeManifest,
11
+ } from "./chrome-manifest";
12
+ import type {
13
+ EditorEvent,
14
+ EditorOperation,
15
+ EditorRequest,
16
+ EditorRequestType,
17
+ EditorResponse,
18
+ EditorSuccess,
19
+ } from "./index";
20
+ import { jsonSchema } from "./json-schema";
21
+
22
+ export const EDITOR_LAYOUT_BYTES_MAX = 8_192;
23
+ export const EDITOR_LAYOUT_PANELS_MAX = 32;
24
+ export const EDITOR_LAYOUT_SECTIONS_MAX = 64;
25
+ export const EDITOR_CLIPBOARD_TEXT_MAX = 1_000_000;
26
+ export const EDITOR_DOWNLOAD_BYTES_MAX = 52_428_800;
27
+ export const EDITOR_IMPORT_BYTES_MAX = 4 * 1024 * 1024;
28
+ export const EDITOR_FILENAME_MAX = 255;
29
+ export const EDITOR_ASSET_FILE_BYTES_MAX = 8 * 1024 * 1024;
30
+
31
+ const layoutRecordKey = z.string().regex(/^[A-Za-z0-9][A-Za-z0-9_-]{0,63}$/);
32
+
33
+ export const layoutValuesSchema = z.strictObject({
34
+ panels: z.record(
35
+ layoutRecordKey,
36
+ z.strictObject({
37
+ visible: z.boolean(),
38
+ size: z.union([
39
+ z.number().int().min(0).max(100_000),
40
+ z.templateLiteral([z.number(), "%"])
41
+ .refine((size) => /^\d{1,3}(\.\d{1,4})?%$/.test(size) && Number.parseFloat(size) <= 100),
42
+ ]).exactOptional(),
43
+ }),
44
+ ).refine((panels) => Object.keys(panels).length <= EDITOR_LAYOUT_PANELS_MAX),
45
+ collapsed: z.record(layoutRecordKey, z.boolean())
46
+ .refine((collapsed) => Object.keys(collapsed).length <= EDITOR_LAYOUT_SECTIONS_MAX)
47
+ .exactOptional(),
48
+ }).refine((layout) => JSON.stringify(layout).length <= EDITOR_LAYOUT_BYTES_MAX);
49
+
50
+ const base64Schema = z.string()
51
+ .max(4 * Math.ceil(EDITOR_DOWNLOAD_BYTES_MAX / 3))
52
+ .refine((data) => {
53
+ if (data.length % 4 !== 0 || /[^A-Za-z0-9+/=]/.test(data)) return false;
54
+ const padding = data.indexOf("=");
55
+ return padding === -1 ||
56
+ (padding === data.length - 1 && data.endsWith("=")) ||
57
+ (padding === data.length - 2 && data.endsWith("=="));
58
+ });
59
+
60
+ export const importBase64Schema = base64Schema.max(4 * Math.ceil(EDITOR_IMPORT_BYTES_MAX / 3));
61
+
62
+ const mimeName = "[A-Za-z0-9][A-Za-z0-9!#$&^_.+\\-]{0,126}";
63
+ const mimeToken = "[A-Za-z0-9!#$%&'*+.^_`|~\\-]+";
64
+ const mimeValue = String.raw`(?:${mimeToken}|"(?:[^"\\\r\n]|\\[^\r\n])*")`;
65
+ const mimePattern = new RegExp(`^${mimeName}/${mimeName}(?:;[ \t]*${mimeToken}=${mimeValue})*$`);
66
+ const mimeTypeSchema = z.string().refine((value) => !/[\r\n]/.test(value) && mimePattern.test(value));
67
+
68
+ const httpsUrlSchema = z.string().refine((value) => {
69
+ try {
70
+ return new URL(value).protocol === "https:";
71
+ } catch {
72
+ return false;
73
+ }
74
+ });
75
+
76
+ const storageModeSchema = z.enum(["json", "text", "blob"]);
77
+
78
+ const assetSchema = z.strictObject({
79
+ id: z.string(),
80
+ name: z.string(),
81
+ type: z.string(),
82
+ storageMode: storageModeSchema.exactOptional(),
83
+ contentType: z.string().nullable().exactOptional(),
84
+ schema: z.string().nullable().exactOptional(),
85
+ path: z.string(),
86
+ epoch: z.number(),
87
+ headRevision: z.number(),
88
+ updatedAt: z.string(),
89
+ contentBytes: z.number(),
90
+ locked: z.boolean(),
91
+ lockHeld: z.boolean(),
92
+ trashed: z.boolean(),
93
+ });
94
+
95
+ const revisionSchema = z.strictObject({
96
+ id: z.string(),
97
+ assetId: z.string(),
98
+ revision: z.number(),
99
+ message: z.string(),
100
+ contentBytes: z.number(),
101
+ createdAt: z.string(),
102
+ });
103
+
104
+ const emptySchema = z.record(z.string(), z.never());
105
+
106
+ const sessionSchema = z.strictObject({
107
+ asset: z.strictObject({
108
+ id: z.string(),
109
+ type: z.string(),
110
+ name: z.string(),
111
+ storageMode: storageModeSchema.exactOptional(),
112
+ contentType: z.string().nullable().exactOptional(),
113
+ schema: z.string().nullable().exactOptional(),
114
+ }).nullable(),
115
+ mode: z.enum(["idle", "loading", "read", "edit"]),
116
+ data: z.record(z.string(), jsonSchema).nullable(),
117
+ syncStatus: z.enum(["live", "pending", "offline", "error"]),
118
+ });
119
+
120
+ const accessSchema = z.strictObject({
121
+ canManage: z.boolean(),
122
+ });
123
+
124
+ const themeSchema = z.strictObject({
125
+ theme: z.enum(["dark", "light"]),
126
+ });
127
+
128
+ const commandIdSchema = z.string().min(1);
129
+
130
+ const commandInvokeBodySchema = z.strictObject({
131
+ commandId: commandIdSchema,
132
+ });
133
+
134
+ const commandStateBodySchema = z.strictObject({
135
+ id: commandIdSchema,
136
+ visible: z.boolean(),
137
+ disabled: z.boolean(),
138
+ pressed: z.boolean(),
139
+ label: z.string().max(200),
140
+ });
141
+
142
+ const pickBodySchema = z.strictObject({
143
+ acceptedTypes: z.array(z.string()).exactOptional(),
144
+ excludeTypes: z.array(z.string()).exactOptional(),
145
+ excludeIds: z.array(z.string()).exactOptional(),
146
+ });
147
+
148
+ const openBrowserBodySchema = z.strictObject({
149
+ excludeIds: z.array(z.string()).readonly().exactOptional(),
150
+ });
151
+
152
+ const pickSuccessSchema = z.union([
153
+ z.strictObject({
154
+ cancelled: z.literal(false),
155
+ assets: z.array(assetSchema),
156
+ }),
157
+ z.strictObject({
158
+ cancelled: z.literal(true),
159
+ }),
160
+ ]);
161
+
162
+ function requestOf<Type extends EditorRequestType>(
163
+ type: Type,
164
+ body: z.ZodType<EditorOperation[Type]["body"]>,
165
+ ) {
166
+ return z.strictObject({
167
+ v: z.literal(1),
168
+ id: z.string(),
169
+ type: z.literal(type),
170
+ body,
171
+ });
172
+ }
173
+
174
+ function successOf<Body>(body: z.ZodType<Body>) {
175
+ return z.strictObject({
176
+ v: z.literal(1),
177
+ id: z.string(),
178
+ ok: z.literal(true),
179
+ body,
180
+ });
181
+ }
182
+
183
+ export const editorRequestSchema = z.discriminatedUnion("type", [
184
+ requestOf("asset.rename", z.strictObject({ name: z.string().min(1).max(EDITOR_FILENAME_MAX) })),
185
+ requestOf("asset.read", z.strictObject({ id: z.string().min(1) })),
186
+ requestOf("asset.list", z.strictObject({ type: z.string().exactOptional() })),
187
+ requestOf("asset.listRevisions", z.strictObject({ id: z.string() })),
188
+ requestOf("asset.getComposition", z.strictObject({ id: z.string() })),
189
+ requestOf("asset.getPublication", z.strictObject({ id: z.string() })),
190
+ requestOf(
191
+ "asset.confirmBundle",
192
+ z.strictObject({
193
+ id: z.string(),
194
+ message: z.string(),
195
+ reviewed: z.string(),
196
+ }),
197
+ ),
198
+ requestOf(
199
+ "asset.putPublication",
200
+ z.strictObject({
201
+ id: z.string(),
202
+ publication: publicationSchema,
203
+ }),
204
+ ),
205
+ requestOf("host.pick", pickBodySchema),
206
+ requestOf(
207
+ "host.openAsset",
208
+ z.strictObject({ id: z.string(), type: z.string() }),
209
+ ),
210
+ requestOf("host.openInfo", z.strictObject({ id: z.string() })),
211
+ requestOf("host.start", emptySchema),
212
+ requestOf("host.openBrowser", openBrowserBodySchema),
213
+ requestOf("host.clipboardWrite", z.strictObject({
214
+ text: z.string().max(EDITOR_CLIPBOARD_TEXT_MAX),
215
+ })),
216
+ requestOf("host.download", z.union([
217
+ z.strictObject({
218
+ filename: z.string().min(1).max(EDITOR_FILENAME_MAX),
219
+ mimeType: mimeTypeSchema,
220
+ content: z.discriminatedUnion("kind", [
221
+ z.strictObject({ kind: z.literal("text"), text: z.string().max(EDITOR_DOWNLOAD_BYTES_MAX) }),
222
+ z.strictObject({ kind: z.literal("base64"), data: base64Schema }),
223
+ ]),
224
+ }),
225
+ z.strictObject({ url: httpsUrlSchema }),
226
+ ])),
227
+ requestOf("host.openPopup", z.strictObject({ url: httpsUrlSchema })),
228
+ requestOf("host.render", z.strictObject({
229
+ format: z.literal("pdf"),
230
+ document: documentSchema,
231
+ name: z.string().max(EDITOR_FILENAME_MAX),
232
+ })),
233
+ requestOf("doc.append", z.strictObject({ assetId: z.string(), update: z.string() })),
234
+ requestOf("chrome.submit", chromeManifestSchema),
235
+ requestOf("layout.load", emptySchema),
236
+ requestOf("layout.save", z.strictObject({ layout: layoutValuesSchema })),
237
+ ]);
238
+
239
+ const failureSchema = z.strictObject({
240
+ v: z.literal(1),
241
+ id: z.string(),
242
+ ok: z.literal(false),
243
+ code: z.string(),
244
+ });
245
+
246
+ const publicationSuccess = successOf(publicationSchema);
247
+ const emptySuccess = successOf(emptySchema);
248
+
249
+ const successByType: {
250
+ [Type in EditorRequestType]: z.ZodType<EditorSuccess<Type>>;
251
+ } = {
252
+ "asset.rename": successOf(z.strictObject({ name: z.string() })),
253
+ "asset.read": successOf(z.strictObject({
254
+ asset: assetSchema,
255
+ payload: z.record(z.string(), z.json()),
256
+ fileUrl: z.string()
257
+ .max(4 * Math.ceil(EDITOR_ASSET_FILE_BYTES_MAX / 3) + 256)
258
+ .regex(/^data:[a-z0-9.+-]+\/[a-z0-9.+-]+;base64,[A-Za-z0-9+/]*={0,2}$/i)
259
+ .nullable(),
260
+ })),
261
+ "asset.list": successOf(z.array(assetSchema)),
262
+ "asset.listRevisions": successOf(z.array(revisionSchema)),
263
+ "asset.getComposition": successOf(compositionSchema),
264
+ "asset.getPublication": publicationSuccess,
265
+ "asset.confirmBundle": successOf(bundleConfirmSchema),
266
+ "asset.putPublication": publicationSuccess,
267
+ "host.pick": successOf(pickSuccessSchema),
268
+ "host.openAsset": emptySuccess,
269
+ "host.openInfo": emptySuccess,
270
+ "host.start": emptySuccess,
271
+ "host.openBrowser": emptySuccess,
272
+ "host.clipboardWrite": emptySuccess,
273
+ "host.download": emptySuccess,
274
+ "host.openPopup": emptySuccess,
275
+ "host.render": successOf(z.strictObject({ url: httpsUrlSchema })),
276
+ "doc.append": emptySuccess,
277
+ "chrome.submit": emptySuccess,
278
+ "layout.load": successOf(z.strictObject({ layout: layoutValuesSchema.nullable() })),
279
+ "layout.save": emptySuccess,
280
+ };
281
+
282
+ export const editorResponseSchema = z.union([
283
+ successByType["asset.rename"],
284
+ successByType["asset.read"],
285
+ successByType["asset.list"],
286
+ successByType["asset.listRevisions"],
287
+ successByType["asset.getComposition"],
288
+ successByType["asset.getPublication"],
289
+ successByType["asset.confirmBundle"],
290
+ successByType["host.pick"],
291
+ successByType["host.start"],
292
+ successByType["host.render"],
293
+ successByType["layout.load"],
294
+ failureSchema,
295
+ ]);
296
+
297
+ const sessionOpenSchema = z.strictObject({
298
+ v: z.literal(1),
299
+ type: z.literal("session.open"),
300
+ body: z.strictObject({
301
+ editorId: z.string(),
302
+ }),
303
+ });
304
+
305
+ export type SessionOpen = z.infer<typeof sessionOpenSchema>;
306
+
307
+ const handshakeReplySchema = z.discriminatedUnion("ok", [
308
+ z.strictObject({
309
+ v: z.literal(1),
310
+ ok: z.literal(true),
311
+ }),
312
+ z.strictObject({
313
+ v: z.literal(1),
314
+ ok: z.literal(false),
315
+ code: z.enum(["version", "schema", "rejected"]),
316
+ }),
317
+ ]);
318
+
319
+ export type HandshakeReply = z.infer<typeof handshakeReplySchema>;
320
+
321
+ export const editorEventSchema = z.discriminatedUnion("type", [
322
+ z.strictObject({
323
+ v: z.literal(1),
324
+ type: z.literal("file.import"),
325
+ body: z.strictObject({
326
+ name: z.string().min(1).max(EDITOR_FILENAME_MAX),
327
+ data: importBase64Schema,
328
+ }),
329
+ }),
330
+ z.strictObject({
331
+ v: z.literal(1),
332
+ type: z.literal("session"),
333
+ body: sessionSchema,
334
+ }),
335
+ z.strictObject({
336
+ v: z.literal(1),
337
+ type: z.literal("access"),
338
+ body: accessSchema,
339
+ }),
340
+ z.strictObject({
341
+ v: z.literal(1),
342
+ type: z.literal("theme"),
343
+ body: themeSchema,
344
+ }),
345
+ z.strictObject({
346
+ v: z.literal(1),
347
+ type: z.literal("doc.update"),
348
+ body: z.strictObject({ assetId: z.string(), update: z.string() }),
349
+ }),
350
+ z.strictObject({
351
+ v: z.literal(1),
352
+ type: z.literal("host.createRequested"),
353
+ }),
354
+ z.strictObject({
355
+ v: z.literal(1),
356
+ type: z.literal("command.invoke"),
357
+ body: commandInvokeBodySchema,
358
+ }),
359
+ z.strictObject({
360
+ v: z.literal(1),
361
+ type: z.literal("command.shortcut"),
362
+ body: z.strictObject({ key: z.string().min(1), commandId: commandIdSchema }),
363
+ }),
364
+ z.strictObject({
365
+ v: z.literal(1),
366
+ type: z.literal("command.state"),
367
+ body: commandStateBodySchema,
368
+ }),
369
+ ]);
370
+
371
+ export type ProtocolCode = "version" | "schema";
372
+
373
+ export type ProtocolResult<T> =
374
+ | { ok: true; message: T }
375
+ | { ok: false; code: ProtocolCode };
376
+
377
+ function wrongVersion(value: unknown): boolean {
378
+ if (typeof value !== "object" || value === null || Array.isArray(value)) {
379
+ return false;
380
+ }
381
+ if (!Object.hasOwn(value, "v")) return false;
382
+ return Reflect.get(value, "v") !== 1;
383
+ }
384
+
385
+ // Bundle schemas strip unknown keys. A stripped tree is not the wire value.
386
+ export function sameTree(left: unknown, right: unknown): boolean {
387
+ if (Object.is(left, right)) return true;
388
+ if (typeof left !== "object" || typeof right !== "object") return false;
389
+ if (left === null || right === null) return false;
390
+ if (Array.isArray(left) || Array.isArray(right)) {
391
+ if (!Array.isArray(left) || !Array.isArray(right)) return false;
392
+ if (left.length !== right.length) return false;
393
+ return left.every((item, index) => sameTree(item, right[index]));
394
+ }
395
+ const leftKeys = Object.keys(left);
396
+ if (leftKeys.length !== Object.keys(right).length) return false;
397
+ return leftKeys.every(
398
+ (key) =>
399
+ Object.hasOwn(right, key) &&
400
+ sameTree(Reflect.get(left, key), Reflect.get(right, key)),
401
+ );
402
+ }
403
+
404
+ function parseWith<T>(
405
+ schema: z.ZodType<T>,
406
+ value: unknown,
407
+ ): ProtocolResult<T> {
408
+ if (wrongVersion(value)) return { ok: false, code: "version" };
409
+ const parsed = schema.safeParse(value);
410
+ if (!parsed.success || !sameTree(parsed.data, value)) {
411
+ return { ok: false, code: "schema" };
412
+ }
413
+ return { ok: true, message: parsed.data };
414
+ }
415
+
416
+ export function parseEditorRequest(value: unknown): ProtocolResult<EditorRequest> {
417
+ return parseWith(editorRequestSchema, value);
418
+ }
419
+
420
+ export function parseEditorResponse(
421
+ value: unknown,
422
+ ): ProtocolResult<EditorResponse> {
423
+ return parseWith(editorResponseSchema, value);
424
+ }
425
+
426
+ export function parseEditorResponseFor<Type extends EditorRequestType>(
427
+ type: Type,
428
+ value: unknown,
429
+ ): ProtocolResult<EditorResponse<Type>> {
430
+ if (wrongVersion(value)) return { ok: false, code: "version" };
431
+ const failed = failureSchema.safeParse(value);
432
+ if (failed.success && sameTree(failed.data, value)) {
433
+ return { ok: true, message: failed.data };
434
+ }
435
+ return parseWith(successByType[type], value);
436
+ }
437
+
438
+ export function parseSessionOpen(value: unknown): ProtocolResult<SessionOpen> {
439
+ return parseWith(sessionOpenSchema, value);
440
+ }
441
+
442
+ export function parseHandshakeReply(
443
+ value: unknown,
444
+ ): ProtocolResult<HandshakeReply> {
445
+ return parseWith(handshakeReplySchema, value);
446
+ }
447
+
448
+ export function parseEditorEvent(value: unknown): ProtocolResult<EditorEvent> {
449
+ return parseWith(editorEventSchema, value);
450
+ }
451
+
452
+ export { chromeManifestSchema };
453
+ export type { ChromeManifest };
454
+
455
+ export function parseChromeManifest(
456
+ value: unknown,
457
+ ): { ok: true; message: ChromeManifest } | { ok: false; code: "schema" } {
458
+ const parsed = chromeManifestSchema.safeParse(value);
459
+ if (!parsed.success || !sameTree(parsed.data, value)) {
460
+ return { ok: false, code: "schema" };
461
+ }
462
+ return { ok: true, message: parsed.data };
463
+ }
464
+
465
+ export {
466
+ isEditorLabel,
467
+ isPublisherName,
468
+ parseEditorId,
469
+ reservedPublishers,
470
+ } from "./editor-id";
471
+ export type { EditorId } from "./editor-id";
@@ -0,0 +1,25 @@
1
+ const renderCodes = ["forbidden", "invalid", "unsupported", "too-large"] as const;
2
+
3
+ export type RenderCode = (typeof renderCodes)[number];
4
+
5
+ export function renderCode(value: string): RenderCode | undefined {
6
+ for (const code of renderCodes) {
7
+ if (value === code) return code;
8
+ }
9
+ return undefined;
10
+ }
11
+
12
+ export class RenderFailure extends Error {
13
+ readonly code: RenderCode;
14
+
15
+ constructor(code: RenderCode) {
16
+ super(code);
17
+ this.name = "RenderFailure";
18
+ this.code = code;
19
+ }
20
+ }
21
+
22
+ export function renderFailureCode(error: unknown): RenderCode | undefined {
23
+ if (!(error instanceof RenderFailure)) return undefined;
24
+ return error.code;
25
+ }
@@ -0,0 +1,28 @@
1
+ import type { ContributionManifest, EditorManifest } from "../commands";
2
+ import { chromeManifestSchema, type ChromeManifest } from "../protocol";
3
+ import { EDITOR_COMMON_CONTRIBUTES } from "../editor-common";
4
+
5
+ type ChromeLayout = Pick<ChromeManifest, "layout" | "titles" | "panelHeaders">;
6
+
7
+ export function createEditorChrome(manifest: EditorManifest, binding: {
8
+ spec: { readonly root: string; readonly elements: object };
9
+ titles: ChromeLayout["titles"];
10
+ panelHeaders?: ChromeLayout["panelHeaders"];
11
+ }) {
12
+ const chromeManifest: ChromeManifest = chromeManifestSchema.parse({
13
+ contributes: {
14
+ commands: [...EDITOR_COMMON_CONTRIBUTES.commands, ...(manifest.contributes?.commands ?? [])],
15
+ menus: [...EDITOR_COMMON_CONTRIBUTES.menus, ...(manifest.contributes?.menus ?? [])],
16
+ keybindings: [...EDITOR_COMMON_CONTRIBUTES.keybindings, ...(manifest.contributes?.keybindings ?? [])],
17
+ },
18
+ layout: binding.spec,
19
+ titles: binding.titles,
20
+ ...(binding.panelHeaders && Object.keys(binding.panelHeaders).length > 0 ? { panelHeaders: binding.panelHeaders } : {}),
21
+ });
22
+ const contribution: ContributionManifest = {
23
+ id: manifest.id,
24
+ activation: "exact",
25
+ ...chromeManifest.contributes,
26
+ };
27
+ return { chromeManifest, contribution };
28
+ }