@dooc-dev/dooc 0.0.0-stage → 0.0.1

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 (213) hide show
  1. package/README.md +26 -2
  2. package/dist/cli.d.ts +2 -0
  3. package/dist/cli.js +244 -0
  4. package/dist/code.global.js +285 -0
  5. package/dist/compiler/bundle-urls.d.ts +3 -0
  6. package/dist/compiler/bundle-urls.js +294 -0
  7. package/dist/compiler/bundle.d.ts +2 -0
  8. package/dist/compiler/bundle.js +100 -0
  9. package/dist/compiler/check-prose.d.ts +11 -0
  10. package/dist/compiler/check-prose.js +138 -0
  11. package/dist/compiler/check-types.d.ts +44 -0
  12. package/dist/compiler/check-types.js +1 -0
  13. package/dist/compiler/check.d.ts +5 -0
  14. package/dist/compiler/check.js +450 -0
  15. package/dist/compiler/code-blocks.d.ts +17 -0
  16. package/dist/compiler/code-blocks.js +198 -0
  17. package/dist/compiler/code-languages.d.ts +10 -0
  18. package/dist/compiler/code-languages.js +109 -0
  19. package/dist/compiler/compile.d.ts +95 -0
  20. package/dist/compiler/compile.js +410 -0
  21. package/dist/compiler/config.d.ts +7 -0
  22. package/dist/compiler/config.js +52 -0
  23. package/dist/compiler/dialect.d.ts +24 -0
  24. package/dist/compiler/dialect.js +198 -0
  25. package/dist/compiler/drawio.d.ts +26 -0
  26. package/dist/compiler/drawio.js +176 -0
  27. package/dist/compiler/figures.d.ts +14 -0
  28. package/dist/compiler/figures.js +221 -0
  29. package/dist/compiler/file-cache.d.ts +14 -0
  30. package/dist/compiler/file-cache.js +61 -0
  31. package/dist/compiler/files.d.ts +29 -0
  32. package/dist/compiler/files.js +431 -0
  33. package/dist/compiler/fonts.d.ts +10 -0
  34. package/dist/compiler/fonts.js +54 -0
  35. package/dist/compiler/format.d.ts +1 -0
  36. package/dist/compiler/format.js +42 -0
  37. package/dist/compiler/from-mdast.d.ts +16 -0
  38. package/dist/compiler/from-mdast.js +142 -0
  39. package/dist/compiler/icons.d.ts +14 -0
  40. package/dist/compiler/icons.js +85 -0
  41. package/dist/compiler/language-codes.d.ts +7 -0
  42. package/dist/compiler/language-codes.js +17 -0
  43. package/dist/compiler/locales.d.ts +7 -0
  44. package/dist/compiler/locales.js +67 -0
  45. package/dist/compiler/manifest.d.ts +6 -0
  46. package/dist/compiler/manifest.js +30 -0
  47. package/dist/compiler/ops.d.ts +44 -0
  48. package/dist/compiler/ops.js +362 -0
  49. package/dist/compiler/public-file.d.ts +2 -0
  50. package/dist/compiler/public-file.js +93 -0
  51. package/dist/compiler/serialize.d.ts +3 -0
  52. package/dist/compiler/serialize.js +16 -0
  53. package/dist/compiler/terms.d.ts +9 -0
  54. package/dist/compiler/terms.js +44 -0
  55. package/dist/compiler/textfile-remote.d.ts +12 -0
  56. package/dist/compiler/textfile-remote.js +124 -0
  57. package/dist/compiler/textfile.d.ts +9 -0
  58. package/dist/compiler/textfile.js +113 -0
  59. package/dist/compiler/theme-variants.d.ts +4 -0
  60. package/dist/compiler/theme-variants.js +22 -0
  61. package/dist/compiler/zip.d.ts +6 -0
  62. package/dist/compiler/zip.js +48 -0
  63. package/dist/core/blocks.d.ts +99 -0
  64. package/dist/core/blocks.js +29 -0
  65. package/dist/core/code-file.d.ts +12 -0
  66. package/dist/core/code-file.js +68 -0
  67. package/dist/core/content-metadata.d.ts +48 -0
  68. package/dist/core/content-metadata.js +264 -0
  69. package/dist/core/content-types.d.ts +45 -0
  70. package/dist/core/content-types.js +1 -0
  71. package/dist/core/figure-frame.d.ts +12 -0
  72. package/dist/core/figure-frame.js +108 -0
  73. package/dist/core/file-embed.d.ts +13 -0
  74. package/dist/core/file-embed.js +43 -0
  75. package/dist/core/files.d.ts +15 -0
  76. package/dist/core/files.js +59 -0
  77. package/dist/core/flow-base-css.generated.d.ts +1 -0
  78. package/dist/core/flow-base-css.generated.js +4 -0
  79. package/dist/core/flow-css.d.ts +9 -0
  80. package/dist/core/flow-css.js +54 -0
  81. package/dist/core/graph-common.d.ts +32 -0
  82. package/dist/core/graph-common.js +113 -0
  83. package/dist/core/graph.d.ts +117 -0
  84. package/dist/core/graph.js +480 -0
  85. package/dist/core/html.d.ts +29 -0
  86. package/dist/core/html.js +444 -0
  87. package/dist/core/icons.d.ts +1 -0
  88. package/dist/core/icons.js +1 -0
  89. package/dist/core/inline.d.ts +27 -0
  90. package/dist/core/inline.js +42 -0
  91. package/dist/core/language.d.ts +41 -0
  92. package/dist/core/language.js +44 -0
  93. package/dist/core/languages/ja.d.ts +2 -0
  94. package/dist/core/languages/ja.js +5 -0
  95. package/dist/core/markdown.d.ts +27 -0
  96. package/dist/core/markdown.js +154 -0
  97. package/dist/core/previewers.d.ts +37 -0
  98. package/dist/core/previewers.js +103 -0
  99. package/dist/core/routing.d.ts +20 -0
  100. package/dist/core/routing.js +64 -0
  101. package/dist/core/schema-comments.d.ts +14 -0
  102. package/dist/core/schema-comments.js +52 -0
  103. package/dist/core/schema-graph-flow.d.ts +9 -0
  104. package/dist/core/schema-graph-flow.js +379 -0
  105. package/dist/core/schema-graph-mount.d.ts +9 -0
  106. package/dist/core/schema-graph-mount.js +105 -0
  107. package/dist/core/schema-graph.d.ts +254 -0
  108. package/dist/core/schema-graph.js +981 -0
  109. package/dist/core/table-ui.d.ts +2 -0
  110. package/dist/core/table-ui.js +180 -0
  111. package/dist/core/taxonomy.d.ts +12 -0
  112. package/dist/core/taxonomy.js +49 -0
  113. package/dist/core/term-reference.d.ts +5 -0
  114. package/dist/core/term-reference.js +19 -0
  115. package/dist/core/terms-ui.d.ts +6 -0
  116. package/dist/core/terms-ui.js +22 -0
  117. package/dist/core/textfile.d.ts +10 -0
  118. package/dist/core/textfile.js +39 -0
  119. package/dist/core/theme-variants.d.ts +3 -0
  120. package/dist/core/theme-variants.js +37 -0
  121. package/dist/core/toc-ui.d.ts +2 -0
  122. package/dist/core/toc-ui.js +161 -0
  123. package/dist/core/video-ui.d.ts +2 -0
  124. package/dist/core/video-ui.js +253 -0
  125. package/dist/core/workspace.d.ts +15 -0
  126. package/dist/core/workspace.js +28 -0
  127. package/dist/edit.global.js +305 -0
  128. package/dist/flow.global.js +4959 -0
  129. package/dist/index.d.ts +17 -0
  130. package/dist/index.js +31 -0
  131. package/dist/mcp.d.ts +24 -0
  132. package/dist/mcp.js +467 -0
  133. package/dist/plugins/actions.d.ts +45 -0
  134. package/dist/plugins/actions.js +125 -0
  135. package/dist/plugins/blocks.d.ts +92 -0
  136. package/dist/plugins/blocks.js +99 -0
  137. package/dist/plugins/index.d.ts +4 -0
  138. package/dist/plugins/index.js +1 -0
  139. package/dist/plugins/oauth.d.ts +14 -0
  140. package/dist/plugins/oauth.js +164 -0
  141. package/dist/plugins/runtime.d.ts +24 -0
  142. package/dist/plugins/runtime.js +339 -0
  143. package/dist/plugins/types.d.ts +56 -0
  144. package/dist/plugins/types.js +1 -0
  145. package/dist/plugins/view.d.ts +29 -0
  146. package/dist/plugins/view.js +60 -0
  147. package/dist/ui.global.js +77 -0
  148. package/dist/viewer/assets.d.ts +2 -0
  149. package/dist/viewer/assets.js +3 -0
  150. package/dist/viewer/build.d.ts +12 -0
  151. package/dist/viewer/build.js +55 -0
  152. package/dist/viewer/bundle-network.d.ts +2 -0
  153. package/dist/viewer/bundle-network.js +88 -0
  154. package/dist/viewer/code-file.d.ts +20 -0
  155. package/dist/viewer/code-file.js +42 -0
  156. package/dist/viewer/editor-tools.d.ts +38 -0
  157. package/dist/viewer/editor-tools.js +211 -0
  158. package/dist/viewer/file-references.d.ts +14 -0
  159. package/dist/viewer/file-references.js +38 -0
  160. package/dist/viewer/font-picker.d.ts +4 -0
  161. package/dist/viewer/font-picker.js +145 -0
  162. package/dist/viewer/font-settings.d.ts +11 -0
  163. package/dist/viewer/font-settings.js +50 -0
  164. package/dist/viewer/fonts/LICENSE.txt +119 -0
  165. package/dist/viewer/fonts/udev-gothic-regular.woff2 +0 -0
  166. package/dist/viewer/fonts.d.ts +2 -0
  167. package/dist/viewer/fonts.js +13 -0
  168. package/dist/viewer/git-diff.d.ts +3 -0
  169. package/dist/viewer/git-diff.js +33 -0
  170. package/dist/viewer/git-types.d.ts +73 -0
  171. package/dist/viewer/git-types.js +1 -0
  172. package/dist/viewer/git.d.ts +5 -0
  173. package/dist/viewer/git.js +337 -0
  174. package/dist/viewer/hmr.d.ts +53 -0
  175. package/dist/viewer/hmr.js +335 -0
  176. package/dist/viewer/i18n.d.ts +6 -0
  177. package/dist/viewer/i18n.js +65 -0
  178. package/dist/viewer/ja-lint.d.ts +3 -0
  179. package/dist/viewer/ja-lint.js +44 -0
  180. package/dist/viewer/language-picker.d.ts +2 -0
  181. package/dist/viewer/language-picker.js +233 -0
  182. package/dist/viewer/languages.d.ts +11 -0
  183. package/dist/viewer/languages.js +20 -0
  184. package/dist/viewer/locales/eng.json +294 -0
  185. package/dist/viewer/locales/jpn.json +294 -0
  186. package/dist/viewer/locales/zho.json +294 -0
  187. package/dist/viewer/nav-focus.d.ts +2 -0
  188. package/dist/viewer/nav-focus.js +64 -0
  189. package/dist/viewer/plugin-blocks.d.ts +3 -0
  190. package/dist/viewer/plugin-blocks.js +115 -0
  191. package/dist/viewer/plugin-config.d.ts +35 -0
  192. package/dist/viewer/plugin-config.js +57 -0
  193. package/dist/viewer/plugin-view.d.ts +3 -0
  194. package/dist/viewer/plugin-view.js +28 -0
  195. package/dist/viewer/plugins.d.ts +8 -0
  196. package/dist/viewer/plugins.js +214 -0
  197. package/dist/viewer/resize.d.ts +4 -0
  198. package/dist/viewer/resize.js +238 -0
  199. package/dist/viewer/resource-library.d.ts +29 -0
  200. package/dist/viewer/resource-library.js +104 -0
  201. package/dist/viewer/search.d.ts +16 -0
  202. package/dist/viewer/search.js +358 -0
  203. package/dist/viewer/serve.d.ts +206 -0
  204. package/dist/viewer/serve.js +3233 -0
  205. package/dist/viewer/site-icon.d.ts +8 -0
  206. package/dist/viewer/site-icon.js +56 -0
  207. package/dist/viewer/textfile.d.ts +9 -0
  208. package/dist/viewer/textfile.js +50 -0
  209. package/dist/viewer/theme-picker.d.ts +2 -0
  210. package/dist/viewer/theme-picker.js +135 -0
  211. package/dist/viewer/workspace.d.ts +4 -0
  212. package/dist/viewer/workspace.js +47 -0
  213. package/package.json +127 -5
@@ -0,0 +1,125 @@
1
+ import { PluginError } from "./oauth.js";
2
+ export const validatePluginActions = (input) => {
3
+ if (new TextEncoder().encode(JSON.stringify(input)).length > 2 * 1024 * 1024 ||
4
+ !Array.isArray(input) ||
5
+ input.length > 500)
6
+ throw new Error("Invalid plugin actions");
7
+ const ids = new Set();
8
+ const string = (v) => typeof v === "string" && v.length <= 60_000;
9
+ const optionalBoolean = (v) => v === undefined || typeof v === "boolean";
10
+ for (const action of input) {
11
+ if (!action ||
12
+ !string(action.id) ||
13
+ !action.id ||
14
+ ids.has(action.id) ||
15
+ !string(action.label) ||
16
+ !string(action.target) ||
17
+ !Array.isArray(action.fields) ||
18
+ action.fields.length > 40 ||
19
+ !optionalBoolean(action.destructive) ||
20
+ !optionalBoolean(action.confirm) ||
21
+ !optionalBoolean(action.partial) ||
22
+ (action.version !== undefined && !string(action.version)))
23
+ throw new Error("Invalid plugin actions");
24
+ ids.add(action.id);
25
+ const names = new Set();
26
+ for (const field of action.fields) {
27
+ if (!field ||
28
+ !string(field.name) ||
29
+ !/^[a-zA-Z][\w.:-]{0,199}$/.test(field.name) ||
30
+ names.has(field.name) ||
31
+ !string(field.label) ||
32
+ !["text", "textarea", "select", "multiselect", "date", "number"].includes(field.type) ||
33
+ !optionalBoolean(field.required) ||
34
+ (field.value !== undefined && !string(field.value)) ||
35
+ (field.maxLength !== undefined &&
36
+ (!Number.isInteger(field.maxLength) ||
37
+ field.maxLength < 1 ||
38
+ field.maxLength > 60_000)) ||
39
+ [field.min, field.max].some((v) => v !== undefined && (typeof v !== "number" || !Number.isFinite(v))) ||
40
+ (field.min !== undefined && field.max !== undefined && field.min > field.max) ||
41
+ (["select", "multiselect"].includes(field.type) &&
42
+ (!Array.isArray(field.options) ||
43
+ field.options.length > 1000 ||
44
+ field.options.some((o) => !o || !string(o.value) || !string(o.label)))))
45
+ throw new Error("Invalid plugin action field");
46
+ if (field.type === "multiselect" && field.value !== undefined) {
47
+ let values;
48
+ try {
49
+ values = JSON.parse(field.value);
50
+ }
51
+ catch {
52
+ throw new Error("Invalid plugin action selection");
53
+ }
54
+ if (!Array.isArray(values) || values.some((v) => typeof v !== "string"))
55
+ throw new Error("Invalid plugin action selection");
56
+ }
57
+ names.add(field.name);
58
+ }
59
+ }
60
+ return input;
61
+ };
62
+ export const validatePluginActionRequest = (input) => {
63
+ const request = input;
64
+ if (!request ||
65
+ typeof request.id !== "string" ||
66
+ !request.id ||
67
+ request.id.length > 1000 ||
68
+ !request.values ||
69
+ typeof request.values !== "object" ||
70
+ Array.isArray(request.values) ||
71
+ Object.keys(request.values).length > 40 ||
72
+ Object.values(request.values).some((v) => typeof v !== "string" || v.length > 60_000) ||
73
+ (request.version !== undefined &&
74
+ (typeof request.version !== "string" || request.version.length > 1000)) ||
75
+ (request.confirmed !== undefined && typeof request.confirmed !== "boolean"))
76
+ throw new PluginError(400, "Invalid plugin operation");
77
+ return request;
78
+ };
79
+ export const validatePluginActionValues = (action, input) => {
80
+ if ((action.destructive || action.confirm) && input.confirmed !== true)
81
+ throw new PluginError(400, "Confirm the operation target");
82
+ if (action.version !== undefined && input.version !== action.version)
83
+ throw new PluginError(409, "Content changed. Reload before saving.");
84
+ if (Object.keys(input.values).some((key) => !action.fields.some((field) => field.name === key)) ||
85
+ (action.partial && !Object.keys(input.values).length))
86
+ throw new PluginError(400, "Invalid operation fields");
87
+ for (const field of action.fields) {
88
+ if (action.partial && !Object.hasOwn(input.values, field.name))
89
+ continue;
90
+ const value = input.values[field.name] ?? "";
91
+ const invalid = () => {
92
+ throw new PluginError(400, `Invalid value: ${field.label}`);
93
+ };
94
+ if (value.length > (field.maxLength ?? 4000) || (field.required && !value.trim()))
95
+ invalid();
96
+ if (field.type === "select" && !field.options?.some((o) => o.value === value))
97
+ invalid();
98
+ if (field.type === "multiselect") {
99
+ let selected;
100
+ try {
101
+ selected = JSON.parse(value);
102
+ }
103
+ catch {
104
+ invalid();
105
+ }
106
+ if (!Array.isArray(selected) ||
107
+ (field.required && !selected.length) ||
108
+ new Set(selected).size !== selected.length ||
109
+ selected.some((v) => !field.options?.some((o) => o.value === v)))
110
+ invalid();
111
+ }
112
+ if (value &&
113
+ field.type === "date" &&
114
+ (!/^\d{4}-\d{2}-\d{2}$/.test(value) ||
115
+ !Number.isFinite(Date.parse(value)) ||
116
+ new Date(value).toISOString().slice(0, 10) !== value))
117
+ invalid();
118
+ if (value &&
119
+ field.type === "number" &&
120
+ (!Number.isFinite(Number(value)) ||
121
+ Number(value) < (field.min ?? -Infinity) ||
122
+ Number(value) > (field.max ?? Infinity)))
123
+ invalid();
124
+ }
125
+ };
@@ -0,0 +1,92 @@
1
+ export type PluginTone = "neutral" | "accent" | "success" | "warning" | "danger";
2
+ export type PluginInline = string | {
3
+ text: string;
4
+ url?: string;
5
+ code?: boolean;
6
+ strong?: boolean;
7
+ tone?: PluginTone;
8
+ copy?: string;
9
+ };
10
+ export interface PluginEdit {
11
+ action: string;
12
+ field?: string;
13
+ }
14
+ export interface PluginCell {
15
+ items: PluginInline[];
16
+ edit?: PluginEdit;
17
+ }
18
+ export type PluginBlock = {
19
+ type: "heading" | "text";
20
+ text: string;
21
+ markdown?: boolean;
22
+ edit?: PluginEdit;
23
+ } | {
24
+ type: "context";
25
+ items: PluginInline[];
26
+ } | {
27
+ type: "fields";
28
+ items: (PluginCell & {
29
+ label: string;
30
+ })[];
31
+ } | {
32
+ type: "section";
33
+ title: string;
34
+ blocks: PluginBlock[];
35
+ collapsed?: boolean;
36
+ note?: string;
37
+ } | {
38
+ type: "columns";
39
+ main: PluginBlock[];
40
+ aside: PluginBlock[];
41
+ } | {
42
+ type: "table";
43
+ headers: string[];
44
+ rows: {
45
+ cells: PluginCell[];
46
+ actions?: string[];
47
+ }[];
48
+ } | {
49
+ type: "code";
50
+ text: string;
51
+ diff?: boolean;
52
+ edit?: PluginEdit;
53
+ } | {
54
+ type: "message";
55
+ author: string;
56
+ text: string;
57
+ time?: string;
58
+ edit?: PluginEdit;
59
+ actions?: string[];
60
+ reactions?: {
61
+ label: string;
62
+ count: number;
63
+ active?: boolean;
64
+ action?: string;
65
+ values?: Record<string, string>;
66
+ }[];
67
+ } | {
68
+ type: "actions";
69
+ ids: string[];
70
+ composer?: boolean;
71
+ } | {
72
+ type: "progress";
73
+ label: string;
74
+ value: number;
75
+ } | {
76
+ type: "divider";
77
+ };
78
+ export interface PluginBlockContent {
79
+ format: "blocks";
80
+ header?: {
81
+ title: string;
82
+ context?: string;
83
+ reference?: string;
84
+ icon?: {
85
+ light: string;
86
+ dark?: string;
87
+ };
88
+ };
89
+ blocks: PluginBlock[];
90
+ }
91
+ export declare const pluginLink: (value: unknown) => value is string;
92
+ export declare const validPluginBlocks: (value: unknown) => value is PluginBlockContent;
@@ -0,0 +1,99 @@
1
+ const record = (value) => !!value && typeof value === "object" && !Array.isArray(value);
2
+ const text = (value) => typeof value === "string" && value.length <= 60_000;
3
+ const optional = (value, check) => value === undefined || check(value);
4
+ const flag = (value) => typeof value === "boolean";
5
+ const array = (value, check, limit = 200) => Array.isArray(value) && value.length <= limit && value.every(check);
6
+ export const pluginLink = (value) => {
7
+ if (typeof value !== "string" || value.length > 4096 || /[\s\\\p{Cc}]/u.test(value))
8
+ return false;
9
+ try {
10
+ const url = new URL(value);
11
+ return /^https?:$/.test(url.protocol) && !url.username && !url.password;
12
+ }
13
+ catch {
14
+ return false;
15
+ }
16
+ };
17
+ const icon = (value) => typeof value === "string" &&
18
+ value.length <= 100_000 &&
19
+ /^data:image\/(?:svg\+xml|png|webp);base64,[A-Za-z0-9+/]+={0,2}$/.test(value);
20
+ const edit = (value) => record(value) && text(value.action) && optional(value.field, text);
21
+ const inlines = (value) => array(value, (item) => text(item) ||
22
+ (record(item) &&
23
+ text(item.text) &&
24
+ optional(item.url, pluginLink) &&
25
+ optional(item.code, flag) &&
26
+ optional(item.strong, flag) &&
27
+ optional(item.copy, text) &&
28
+ optional(item.tone, (v) => ["neutral", "accent", "success", "warning", "danger"].includes(String(v)))));
29
+ const cell = (value) => record(value) && inlines(value.items) && optional(value.edit, edit);
30
+ export const validPluginBlocks = (value) => {
31
+ let count = 0;
32
+ const blocks = (value, depth) => depth <= 8 &&
33
+ array(value, (item) => {
34
+ if (++count > 2000 || !record(item))
35
+ return false;
36
+ if (!optional(item.edit, edit))
37
+ return false;
38
+ switch (item.type) {
39
+ case "heading":
40
+ case "text":
41
+ return text(item.text) && optional(item.markdown, flag);
42
+ case "context":
43
+ return inlines(item.items);
44
+ case "fields":
45
+ return array(item.items, (v) => cell(v) && record(v) && text(v.label));
46
+ case "section":
47
+ return (text(item.title) &&
48
+ blocks(item.blocks, depth + 1) &&
49
+ optional(item.collapsed, flag) &&
50
+ optional(item.note, text));
51
+ case "columns":
52
+ return blocks(item.main, depth + 1) && blocks(item.aside, depth + 1);
53
+ case "table":
54
+ return (array(item.headers, text, 40) &&
55
+ array(item.rows, (v) => record(v) &&
56
+ array(v.cells, cell, 40) &&
57
+ v.cells.length ===
58
+ item.headers.length &&
59
+ optional(v.actions, (v) => array(v, text))));
60
+ case "code":
61
+ return text(item.text) && optional(item.diff, flag);
62
+ case "message":
63
+ return (text(item.author) &&
64
+ text(item.text) &&
65
+ optional(item.time, text) &&
66
+ optional(item.actions, (v) => array(v, text)) &&
67
+ optional(item.reactions, (v) => array(v, (r) => record(r) &&
68
+ text(r.label) &&
69
+ typeof r.count === "number" &&
70
+ Number.isInteger(r.count) &&
71
+ r.count >= 0 &&
72
+ optional(r.active, flag) &&
73
+ optional(r.action, text) &&
74
+ optional(r.values, (v) => record(v) &&
75
+ Object.keys(v).length <= 40 &&
76
+ Object.values(v).every(text)))));
77
+ case "actions":
78
+ return array(item.ids, text) && optional(item.composer, flag);
79
+ case "progress":
80
+ return (text(item.label) &&
81
+ typeof item.value === "number" &&
82
+ Number.isFinite(item.value) &&
83
+ item.value >= 0 &&
84
+ item.value <= 100);
85
+ case "divider":
86
+ return true;
87
+ default:
88
+ return false;
89
+ }
90
+ });
91
+ return (record(value) &&
92
+ value.format === "blocks" &&
93
+ blocks(value.blocks, 0) &&
94
+ optional(value.header, (h) => record(h) &&
95
+ text(h.title) &&
96
+ optional(h.context, text) &&
97
+ optional(h.reference, text) &&
98
+ optional(h.icon, (v) => record(v) && icon(v.light) && optional(v.dark, icon))));
99
+ };
@@ -0,0 +1,4 @@
1
+ export type { PluginAction, PluginActionContext, PluginActionField, PluginActionRequest, PluginActionResult, } from "./actions.js";
2
+ export type { PluginBlock, PluginBlockContent, PluginCell, PluginEdit, PluginInline, PluginTone, } from "./blocks.js";
3
+ export { PluginError, parseOAuthToken } from "./oauth.js";
4
+ export type { DataPlugin, OAuthToken, PluginContent, PluginContext, PluginOAuth, PluginUser, PluginView, } from "./types.js";
@@ -0,0 +1 @@
1
+ export { PluginError, parseOAuthToken } from "./oauth.js";
@@ -0,0 +1,14 @@
1
+ import type { DataPlugin, OAuthToken } from "./types.js";
2
+ export declare class PluginError extends Error {
3
+ readonly status: number;
4
+ readonly name = "DoocPluginError";
5
+ constructor(status: number, message: string);
6
+ }
7
+ /** A plugin may load a separate installed copy of the SDK. */
8
+ export declare const isPluginError: (error: unknown) => error is PluginError;
9
+ export declare const httpsUrl: (source: string) => URL;
10
+ /** Use after selecting a provider's user-token object in parseToken. */
11
+ export declare const parseOAuthToken: (input: unknown) => OAuthToken;
12
+ /** Bound both transfer time and decoded size; never include remote error bodies in errors. */
13
+ export declare const fetchPluginJson: (url: string, init: RequestInit, tokenExchange?: boolean, transport?: typeof fetch) => Promise<unknown>;
14
+ export declare const validatePlugins: (input: unknown) => DataPlugin[];
@@ -0,0 +1,164 @@
1
+ export class PluginError extends Error {
2
+ status;
3
+ name = "DoocPluginError";
4
+ constructor(status, message) {
5
+ super(message);
6
+ this.status = status;
7
+ }
8
+ }
9
+ /** A plugin may load a separate installed copy of the SDK. */
10
+ export const isPluginError = (error) => error instanceof Error &&
11
+ error.name === "DoocPluginError" &&
12
+ "status" in error &&
13
+ typeof error.status === "number" &&
14
+ Number.isInteger(error.status) &&
15
+ error.status >= 400 &&
16
+ error.status <= 599;
17
+ export const httpsUrl = (source) => {
18
+ const url = new URL(source);
19
+ if (url.protocol !== "https:" || url.username || url.password || url.hash)
20
+ throw new Error("Plugin endpoints must use HTTPS without credentials or fragments");
21
+ return url;
22
+ };
23
+ /** Use after selecting a provider's user-token object in parseToken. */
24
+ export const parseOAuthToken = (input) => {
25
+ const token = input;
26
+ if (!token ||
27
+ typeof token !== "object" ||
28
+ typeof token.access_token !== "string" ||
29
+ !token.access_token ||
30
+ /[\s\p{Cc}]/u.test(token.access_token) ||
31
+ (token.token_type !== undefined &&
32
+ token.token_type !== "user" &&
33
+ (typeof token.token_type !== "string" ||
34
+ token.token_type.toLowerCase() !== "bearer")) ||
35
+ (token.refresh_token !== undefined &&
36
+ (typeof token.refresh_token !== "string" || !token.refresh_token)) ||
37
+ (token.expires_in !== undefined &&
38
+ (typeof token.expires_in !== "number" ||
39
+ !Number.isFinite(token.expires_in) ||
40
+ token.expires_in <= 0)) ||
41
+ (token.scope !== undefined &&
42
+ typeof token.scope !== "string" &&
43
+ (!Array.isArray(token.scope) || token.scope.some((s) => typeof s !== "string"))))
44
+ throw new PluginError(502, "Invalid OAuth token response");
45
+ return {
46
+ access_token: token.access_token,
47
+ ...(token.refresh_token === undefined ? {} : { refresh_token: token.refresh_token }),
48
+ ...(token.expires_in === undefined ? {} : { expires_in: token.expires_in }),
49
+ ...(token.scope === undefined ? {} : { scope: token.scope }),
50
+ };
51
+ };
52
+ /** Bound both transfer time and decoded size; never include remote error bodies in errors. */
53
+ export const fetchPluginJson = async (url, init, tokenExchange = false, transport = fetch) => {
54
+ try {
55
+ const response = await transport(url, {
56
+ ...init,
57
+ redirect: "error",
58
+ cache: "no-store",
59
+ signal: AbortSignal.timeout(15_000),
60
+ });
61
+ if (!response.ok) {
62
+ await response.body?.cancel();
63
+ throw new PluginError(response.status === 400 && tokenExchange
64
+ ? 401
65
+ : [400, 401, 403, 404, 409, 422, 429].includes(response.status)
66
+ ? response.status
67
+ : 502, response.status === 401 || (response.status === 400 && tokenExchange)
68
+ ? "Connect this plugin again"
69
+ : response.status === 403
70
+ ? "Access denied by the service"
71
+ : "Plugin service request failed");
72
+ }
73
+ if (response.status === 204)
74
+ return null;
75
+ const reader = response.body?.getReader();
76
+ if (!reader)
77
+ return null;
78
+ const chunks = [];
79
+ let size = 0;
80
+ try {
81
+ while (true) {
82
+ const { done, value } = await reader.read();
83
+ if (done)
84
+ break;
85
+ size += value.byteLength;
86
+ if (size > 2 * 1024 * 1024)
87
+ throw new Error("Response too large");
88
+ chunks.push(value);
89
+ }
90
+ }
91
+ finally {
92
+ await reader.cancel();
93
+ }
94
+ return size === 0 ? null : JSON.parse(Buffer.concat(chunks).toString("utf8"));
95
+ }
96
+ catch (error) {
97
+ if (isPluginError(error))
98
+ throw error;
99
+ throw new PluginError(502, "Plugin service request failed");
100
+ }
101
+ };
102
+ export const validatePlugins = (input) => {
103
+ if (!Array.isArray(input))
104
+ throw new Error("Viewer plugins: plugins must be an array");
105
+ const ids = new Set();
106
+ return input.map((plugin) => {
107
+ if (!plugin ||
108
+ typeof plugin.id !== "string" ||
109
+ !/^[a-z][a-z0-9-]{0,63}$/.test(plugin.id) ||
110
+ ids.has(plugin.id))
111
+ throw new Error("Viewer plugins: plugin IDs must be unique lowercase names");
112
+ ids.add(plugin.id);
113
+ const oauth = plugin.oauth;
114
+ if (!oauth ||
115
+ typeof oauth.clientId !== "string" ||
116
+ !oauth.clientId.trim() ||
117
+ (oauth.clientSecret !== undefined && typeof oauth.clientSecret !== "string") ||
118
+ !Array.isArray(oauth.scopes) ||
119
+ oauth.scopes.some((s) => typeof s !== "string" || !s.trim()) ||
120
+ typeof plugin.identifyUser !== "function" ||
121
+ typeof plugin.matches !== "function" ||
122
+ typeof plugin.read !== "function" ||
123
+ (plugin.actions !== undefined && typeof plugin.actions !== "function") ||
124
+ (plugin.act !== undefined && typeof plugin.act !== "function") ||
125
+ Boolean(plugin.actions) !== Boolean(plugin.act) ||
126
+ !Array.isArray(plugin.apiOrigins) ||
127
+ !plugin.apiOrigins.length ||
128
+ (oauth.parseToken !== undefined && typeof oauth.parseToken !== "function"))
129
+ throw new Error(`Viewer plugins: invalid plugin ${plugin.id}`);
130
+ for (const endpoint of [oauth.authorizationUrl, oauth.tokenUrl]) {
131
+ if (typeof endpoint !== "string" || httpsUrl(endpoint).search)
132
+ throw new Error("OAuth endpoints must not contain query parameters");
133
+ }
134
+ for (const origin of plugin.apiOrigins)
135
+ if (typeof origin !== "string" || httpsUrl(origin).origin !== origin)
136
+ throw new Error("apiOrigins must contain exact HTTPS origins");
137
+ const reserved = new Set([
138
+ "client_id",
139
+ "client_secret",
140
+ "redirect_uri",
141
+ "response_type",
142
+ "state",
143
+ "code_challenge",
144
+ "code_challenge_method",
145
+ ]);
146
+ if (oauth.scopeParameter !== undefined &&
147
+ (!/^[a-z_]+$/.test(oauth.scopeParameter) || reserved.has(oauth.scopeParameter)))
148
+ throw new Error("Invalid OAuth scope parameter");
149
+ if (oauth.scopeSeparator !== undefined && ![" ", ","].includes(oauth.scopeSeparator))
150
+ throw new Error("OAuth scope separator must be a space or comma");
151
+ if (oauth.authorizationParams !== undefined) {
152
+ if (!oauth.authorizationParams ||
153
+ typeof oauth.authorizationParams !== "object" ||
154
+ Array.isArray(oauth.authorizationParams))
155
+ throw new Error("Invalid OAuth authorization parameters");
156
+ for (const [key, value] of Object.entries(oauth.authorizationParams))
157
+ if (reserved.has(key) ||
158
+ key === (oauth.scopeParameter ?? "scope") ||
159
+ typeof value !== "string")
160
+ throw new Error("OAuth authorization parameters cannot override security parameters");
161
+ }
162
+ return plugin;
163
+ });
164
+ };
@@ -0,0 +1,24 @@
1
+ import { type PluginActionResult } from "./actions.js";
2
+ import type { DataPlugin, PluginUser } from "./types.js";
3
+ export declare const createPluginRuntime: (definitions: readonly DataPlugin[], transport?: typeof fetch) => {
4
+ resolve(source: unknown): string | undefined;
5
+ register(plugin: DataPlugin): void;
6
+ unregister: (pluginId: string) => void;
7
+ createSession(): string;
8
+ hasSession(id: string): boolean;
9
+ connect(id: string, pluginId: string, redirectUri: string): string;
10
+ callback(id: string, pluginId: string, redirectUri: string, params: URLSearchParams): Promise<void>;
11
+ disconnect(id: string, pluginId: string): void;
12
+ read(id: string, pluginId: string, source: unknown): Promise<{
13
+ user: PluginUser;
14
+ view: import("./view.js").PluginView;
15
+ }>;
16
+ actions(id: string, pluginId: string, source: unknown): Promise<{
17
+ user: PluginUser;
18
+ actions: import("./actions.js").PluginAction[];
19
+ }>;
20
+ act(id: string, pluginId: string, source: unknown, input: unknown, requestId: unknown): Promise<{
21
+ user: PluginUser;
22
+ result: PluginActionResult;
23
+ }>;
24
+ };