slackmark 0.5.0 → 0.6.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 (67) hide show
  1. package/README.md +29 -17
  2. package/dist/adapters/block-to-plain-text.d.ts +24 -0
  3. package/dist/adapters/block-to-plain-text.d.ts.map +1 -0
  4. package/dist/adapters/blockquote-adapter.d.ts.map +1 -1
  5. package/dist/adapters/code-fence-adapter.d.ts +1 -1
  6. package/dist/adapters/code-fence-adapter.d.ts.map +1 -1
  7. package/dist/adapters/inline.d.ts.map +1 -1
  8. package/dist/adapters/list-adapter.d.ts.map +1 -1
  9. package/dist/adapters/math-block-adapter.d.ts +1 -1
  10. package/dist/adapters/math-block-adapter.d.ts.map +1 -1
  11. package/dist/adapters/mermaid-fence-adapter.d.ts.map +1 -1
  12. package/dist/adapters/table-adapter.d.ts.map +1 -1
  13. package/dist/adapters/task-list-adapter.d.ts.map +1 -1
  14. package/dist/adapters/try-render-image.d.ts +9 -0
  15. package/dist/adapters/try-render-image.d.ts.map +1 -0
  16. package/dist/assemble/message-assembler.d.ts +10 -0
  17. package/dist/assemble/message-assembler.d.ts.map +1 -1
  18. package/dist/budget/conversion-context.d.ts +5 -1
  19. package/dist/budget/conversion-context.d.ts.map +1 -1
  20. package/dist/budget/operation-scope.d.ts +9 -0
  21. package/dist/budget/operation-scope.d.ts.map +1 -0
  22. package/dist/budget/recording-receipt-collector.d.ts +13 -0
  23. package/dist/budget/recording-receipt-collector.d.ts.map +1 -0
  24. package/dist/capabilities/capabilities.d.ts.map +1 -1
  25. package/dist/constants.d.ts +14 -0
  26. package/dist/constants.d.ts.map +1 -1
  27. package/dist/converter/create-converter.d.ts +1 -1
  28. package/dist/converter/create-converter.d.ts.map +1 -1
  29. package/dist/converter/resolve-config.d.ts +4 -2
  30. package/dist/converter/resolve-config.d.ts.map +1 -1
  31. package/dist/converter/slackmark-converter.d.ts +11 -1
  32. package/dist/converter/slackmark-converter.d.ts.map +1 -1
  33. package/dist/errors.d.ts +81 -3
  34. package/dist/errors.d.ts.map +1 -1
  35. package/dist/index.d.ts +6 -2
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +5080 -3585
  38. package/dist/index.js.map +1 -1
  39. package/dist/receipts.d.ts +37 -0
  40. package/dist/receipts.d.ts.map +1 -0
  41. package/dist/renderers/codecogs-renderer.d.ts +5 -5
  42. package/dist/renderers/codecogs-renderer.d.ts.map +1 -1
  43. package/dist/renderers/kroki-renderer.d.ts +5 -5
  44. package/dist/renderers/kroki-renderer.d.ts.map +1 -1
  45. package/dist/renderers/mermaid-ink-renderer.d.ts +5 -5
  46. package/dist/renderers/mermaid-ink-renderer.d.ts.map +1 -1
  47. package/dist/renderers/quickchart-renderer.d.ts +5 -5
  48. package/dist/renderers/quickchart-renderer.d.ts.map +1 -1
  49. package/dist/renderers/renderer-options.d.ts +4 -0
  50. package/dist/renderers/renderer-options.d.ts.map +1 -0
  51. package/dist/renderers/url-utils.d.ts.map +1 -1
  52. package/dist/renderers.d.ts +1 -0
  53. package/dist/renderers.d.ts.map +1 -1
  54. package/dist/renderers.js +412 -248
  55. package/dist/renderers.js.map +1 -1
  56. package/dist/slack/fetch-slack-uploader.d.ts +11 -2
  57. package/dist/slack/fetch-slack-uploader.d.ts.map +1 -1
  58. package/dist/slack/slack-upload-error.d.ts +18 -2
  59. package/dist/slack/slack-upload-error.d.ts.map +1 -1
  60. package/dist/slack.d.ts +2 -1
  61. package/dist/slack.d.ts.map +1 -1
  62. package/dist/slack.js +753 -230
  63. package/dist/slack.js.map +1 -1
  64. package/dist/types.d.ts +93 -11
  65. package/dist/types.d.ts.map +1 -1
  66. package/dist/validate/validate-blocks.d.ts.map +1 -1
  67. package/package.json +5 -4
package/dist/renderers.js CHANGED
@@ -1,269 +1,433 @@
1
- // src/constants.ts
2
- var IMAGE_URL_MAX_CHARS = 3e3;
3
-
4
- // src/renderers/encoding.ts
5
- var BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
1
+ //#region src/errors.ts
2
+ const CONFIGURATION_ERROR_BRAND = Symbol.for("slackmark.error.configuration");
3
+ const OPERATION_ERROR_BRAND = Symbol.for("slackmark.error.operation");
4
+ /**
5
+ * Marks a class prototype so instances from any bundled copy answer to one identity.
6
+ * Subclasses inherit the brand, and instances carry no extra own property.
7
+ */
8
+ function brandErrorPrototype(prototype, brand) {
9
+ Object.defineProperty(prototype, brand, {
10
+ configurable: false,
11
+ enumerable: false,
12
+ value: true,
13
+ writable: false
14
+ });
15
+ }
16
+ /**
17
+ * Reads a brand without trusting the value: a hostile getter must not escape a guard.
18
+ */
19
+ function hasErrorBrand(value, brand) {
20
+ if (typeof value !== "object" || value === null) return false;
21
+ try {
22
+ return Reflect.get(value, brand) === true;
23
+ } catch {
24
+ return false;
25
+ }
26
+ }
27
+ var ConfigurationError = class extends Error {
28
+ static {
29
+ brandErrorPrototype(this.prototype, CONFIGURATION_ERROR_BRAND);
30
+ }
31
+ name;
32
+ code;
33
+ stage;
34
+ retryable;
35
+ receipts;
36
+ settledReceipts;
37
+ constructor(message, options = {}) {
38
+ super(message, options?.cause !== void 0 ? { cause: options.cause } : void 0);
39
+ this.name = "ConfigurationError";
40
+ this.code = options.code ?? "invalid_configuration";
41
+ this.stage = options.stage;
42
+ this.retryable = false;
43
+ this.receipts = freezeReceipts(options.receipts ?? []);
44
+ this.settledReceipts = options.settledReceipts ?? Promise.resolve(this.receipts);
45
+ }
46
+ };
47
+ (class extends Error {
48
+ static {
49
+ brandErrorPrototype(this.prototype, OPERATION_ERROR_BRAND);
50
+ }
51
+ name;
52
+ code;
53
+ stage;
54
+ retryable;
55
+ receipts;
56
+ settledReceipts;
57
+ constructor(message, code, stage, retryable, options = {}) {
58
+ super(message, options.cause !== void 0 ? { cause: options.cause } : void 0);
59
+ this.name = "OperationError";
60
+ this.code = code;
61
+ this.stage = stage;
62
+ this.retryable = retryable;
63
+ this.receipts = freezeReceipts(options.receipts ?? []);
64
+ this.settledReceipts = options.settledReceipts ?? Promise.resolve(this.receipts);
65
+ }
66
+ });
67
+ /**
68
+ * Identifies a {@link ConfigurationError} from any entry of this package.
69
+ *
70
+ * Use this instead of `instanceof` when the error may have been thrown by a different
71
+ * entry — for example a `ConfigurationError` from `slackmark/renderers` caught by code
72
+ * that imported the class from `slackmark`.
73
+ */
74
+ function isConfigurationError(value) {
75
+ return hasErrorBrand(value, CONFIGURATION_ERROR_BRAND);
76
+ }
77
+ /**
78
+ * Identifies an {@link OperationError} — abort, deadline, or shutdown — from any entry of
79
+ * this package, including its subclasses.
80
+ *
81
+ * Use this instead of `instanceof` when the error may have been thrown by a different
82
+ * entry — for example an abort raised inside `FetchSlackUploader` from `slackmark/slack`
83
+ * caught by code that imported the class from `slackmark`. Narrow further with `code`
84
+ * against {@link OperationErrorCode} rather than testing a subclass with `instanceof`.
85
+ */
86
+ function isOperationError(value) {
87
+ return hasErrorBrand(value, OPERATION_ERROR_BRAND);
88
+ }
89
+ function freezeReceipts(receipts) {
90
+ return Object.freeze(receipts.map((receipt) => Object.freeze({ ...receipt })));
91
+ }
92
+ //#endregion
93
+ //#region src/renderers/encoding.ts
94
+ const BASE64URL_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789-_";
95
+ /**
96
+ * Kroki and mermaid.ink both accept zlib-deflated, unpadded base64url payloads.
97
+ */
6
98
  async function deflateBase64Url(value) {
7
- const stream = new CompressionStream("deflate");
8
- const compressed = readAll(stream.readable);
9
- const writer = stream.writable.getWriter();
10
- await writer.write(new TextEncoder().encode(value));
11
- await writer.close();
12
- writer.releaseLock();
13
- return encodeBase64Url(await compressed);
99
+ const stream = new CompressionStream("deflate");
100
+ const compressed = readAll(stream.readable);
101
+ const writer = stream.writable.getWriter();
102
+ await writer.write(new TextEncoder().encode(value));
103
+ await writer.close();
104
+ writer.releaseLock();
105
+ return encodeBase64Url(await compressed);
14
106
  }
15
107
  async function readAll(stream) {
16
- const reader = stream.getReader();
17
- const chunks = [];
18
- let length = 0;
19
- while (true) {
20
- const result = await reader.read();
21
- if (result.done) {
22
- break;
23
- }
24
- if (result.value !== void 0) {
25
- chunks.push(result.value);
26
- length += result.value.byteLength;
27
- }
28
- }
29
- reader.releaseLock();
30
- const output = new Uint8Array(length);
31
- let offset = 0;
32
- for (const chunk of chunks) {
33
- output.set(chunk, offset);
34
- offset += chunk.byteLength;
35
- }
36
- return output;
108
+ const reader = stream.getReader();
109
+ const chunks = [];
110
+ let length = 0;
111
+ while (true) {
112
+ const result = await reader.read();
113
+ if (result.done) break;
114
+ if (result.value !== void 0) {
115
+ chunks.push(result.value);
116
+ length += result.value.byteLength;
117
+ }
118
+ }
119
+ reader.releaseLock();
120
+ const output = new Uint8Array(length);
121
+ let offset = 0;
122
+ for (const chunk of chunks) {
123
+ output.set(chunk, offset);
124
+ offset += chunk.byteLength;
125
+ }
126
+ return output;
37
127
  }
38
128
  function encodeBase64Url(bytes) {
39
- const output = [];
40
- for (let index = 0; index < bytes.length; index += 3) {
41
- const first = bytes[index] ?? 0;
42
- const second = bytes[index + 1];
43
- const third = bytes[index + 2];
44
- output.push(
45
- BASE64URL_ALPHABET[first >> 2] ?? "",
46
- BASE64URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4] ?? ""
47
- );
48
- if (second !== void 0) {
49
- output.push(BASE64URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6] ?? "");
50
- }
51
- if (third !== void 0) {
52
- output.push(BASE64URL_ALPHABET[third & 63] ?? "");
53
- }
54
- }
55
- return output.join("");
129
+ const output = [];
130
+ for (let index = 0; index < bytes.length; index += 3) {
131
+ const first = bytes[index] ?? 0;
132
+ const second = bytes[index + 1];
133
+ const third = bytes[index + 2];
134
+ output.push(BASE64URL_ALPHABET[first >> 2] ?? "", BASE64URL_ALPHABET[(first & 3) << 4 | (second ?? 0) >> 4] ?? "");
135
+ if (second !== void 0) output.push(BASE64URL_ALPHABET[(second & 15) << 2 | (third ?? 0) >> 6] ?? "");
136
+ if (third !== void 0) output.push(BASE64URL_ALPHABET[third & 63] ?? "");
137
+ }
138
+ return output.join("");
56
139
  }
57
-
58
- // src/renderers/url-utils.ts
140
+ //#endregion
141
+ //#region src/renderers/renderer-options.ts
142
+ function snapshotRendererOptions(options, allowed, label) {
143
+ if (typeof options !== "object" || options === null || Array.isArray(options)) throw invalidRendererOptions(`${label} options must be a plain object`);
144
+ let prototype;
145
+ let keys;
146
+ try {
147
+ prototype = Object.getPrototypeOf(options);
148
+ keys = Object.keys(options);
149
+ } catch (cause) {
150
+ throw invalidRendererOptions(`${label} options could not be inspected`, cause);
151
+ }
152
+ if (prototype !== Object.prototype && prototype !== null) throw invalidRendererOptions(`${label} options must be a plain object`);
153
+ const allowedKeys = new Set(allowed);
154
+ const snapshot = {};
155
+ for (const key of keys) {
156
+ if (!allowedKeys.has(key)) throw invalidRendererOptions(`Unknown ${label} option: ${key}`);
157
+ try {
158
+ snapshot[key] = Reflect.get(options, key);
159
+ } catch (cause) {
160
+ throw invalidRendererOptions(`${label}.${key} could not be read`, cause);
161
+ }
162
+ }
163
+ return Object.freeze(snapshot);
164
+ }
165
+ function invalidRendererOptions(message, cause) {
166
+ return new ConfigurationError(message, {
167
+ code: "invalid_renderer_options",
168
+ stage: "configuration",
169
+ cause
170
+ });
171
+ }
172
+ //#endregion
173
+ //#region src/renderers/url-utils.ts
59
174
  function normalizeBaseUrl(baseUrl) {
60
- const normalized = baseUrl.replace(/\/+$/u, "");
61
- if (normalized.length === 0) {
62
- throw new TypeError("baseUrl must not be empty");
63
- }
64
- return normalized;
175
+ if (typeof baseUrl !== "string" || baseUrl.length === 0 || baseUrl !== baseUrl.trim()) throw invalidRendererOptions("baseUrl must be a nonempty trimmed string");
176
+ const normalized = baseUrl.replace(/\/+$/u, "");
177
+ try {
178
+ const parsed = new URL(normalized);
179
+ if (parsed.protocol !== "https:" || parsed.username.length > 0 || parsed.password.length > 0) throw invalidRendererOptions("baseUrl must be credential-free absolute HTTPS");
180
+ } catch (cause) {
181
+ if (isConfigurationError(cause)) throw cause;
182
+ throw invalidRendererOptions("baseUrl must be credential-free absolute HTTPS", cause);
183
+ }
184
+ return normalized;
65
185
  }
66
186
  function normalizeFenceLanguage(lang) {
67
- return lang.trim().toLowerCase();
187
+ return lang.trim().toLowerCase();
68
188
  }
69
-
70
- // src/renderers/kroki-renderer.ts
71
- var DEFAULT_KROKI_BASE_URL = "https://kroki.io";
72
- var KROKI_DIAGRAM_TYPES = Object.freeze({
73
- blockdiag: "blockdiag",
74
- bpmn: "bpmn",
75
- bytefield: "bytefield",
76
- c4: "c4plantuml",
77
- c4plantuml: "c4plantuml",
78
- d2: "d2",
79
- dbml: "dbml",
80
- ditaa: "ditaa",
81
- dot: "graphviz",
82
- erd: "erd",
83
- excalidraw: "excalidraw",
84
- graphviz: "graphviz",
85
- mermaid: "mermaid",
86
- nomnoml: "nomnoml",
87
- pikchr: "pikchr",
88
- plantuml: "plantuml",
89
- puml: "plantuml",
90
- structurizr: "structurizr",
91
- svgbob: "svgbob",
92
- symbolator: "symbolator",
93
- tikz: "tikz",
94
- umlet: "umlet",
95
- vega: "vega",
96
- "vega-lite": "vegalite",
97
- vegalite: "vegalite",
98
- wavedrom: "wavedrom",
99
- wireviz: "wireviz"
189
+ //#endregion
190
+ //#region src/renderers/kroki-renderer.ts
191
+ const DEFAULT_KROKI_BASE_URL = "https://kroki.io";
192
+ /**
193
+ * Fence-language aliases for Kroki's supported diagram types.
194
+ *
195
+ * @see https://docs.kroki.io/kroki/setup/usage/
196
+ */
197
+ const KROKI_DIAGRAM_TYPES = Object.freeze({
198
+ blockdiag: "blockdiag",
199
+ bpmn: "bpmn",
200
+ bytefield: "bytefield",
201
+ c4: "c4plantuml",
202
+ c4plantuml: "c4plantuml",
203
+ d2: "d2",
204
+ dbml: "dbml",
205
+ ditaa: "ditaa",
206
+ dot: "graphviz",
207
+ erd: "erd",
208
+ excalidraw: "excalidraw",
209
+ graphviz: "graphviz",
210
+ mermaid: "mermaid",
211
+ nomnoml: "nomnoml",
212
+ pikchr: "pikchr",
213
+ plantuml: "plantuml",
214
+ puml: "plantuml",
215
+ structurizr: "structurizr",
216
+ svgbob: "svgbob",
217
+ symbolator: "symbolator",
218
+ tikz: "tikz",
219
+ umlet: "umlet",
220
+ vega: "vega",
221
+ "vega-lite": "vegalite",
222
+ vegalite: "vegalite",
223
+ wavedrom: "wavedrom",
224
+ wireviz: "wireviz"
100
225
  });
226
+ /**
227
+ * Produces durable Kroki GET URLs without performing network I/O.
228
+ */
101
229
  var KrokiRenderer = class {
102
- baseUrl;
103
- diagramTypes;
104
- constructor(options = {}) {
105
- this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_KROKI_BASE_URL);
106
- this.diagramTypes = Object.freeze({
107
- ...KROKI_DIAGRAM_TYPES,
108
- ...options.diagramTypes
109
- });
110
- }
111
- canRender(req) {
112
- return this.diagramTypes[normalizeFenceLanguage(req.lang)] !== void 0;
113
- }
114
- async render(req) {
115
- const diagramType = this.diagramTypes[normalizeFenceLanguage(req.lang)];
116
- if (diagramType === void 0) {
117
- return null;
118
- }
119
- const encoded = await deflateBase64Url(req.source);
120
- const imageUrl = `${this.baseUrl}/${encodeURIComponent(diagramType)}/png/${encoded}`;
121
- if (imageUrl.length > IMAGE_URL_MAX_CHARS) {
122
- return null;
123
- }
124
- return {
125
- kind: "url",
126
- imageUrl,
127
- altText: req.altText
128
- };
129
- }
230
+ id;
231
+ output;
232
+ baseUrl;
233
+ diagramTypes;
234
+ constructor(options = {}) {
235
+ const validated = snapshotRendererOptions(options, ["baseUrl", "diagramTypes"], "KrokiRenderer");
236
+ this.id = "kroki";
237
+ this.output = "url";
238
+ this.baseUrl = normalizeBaseUrl(validated.baseUrl === void 0 ? DEFAULT_KROKI_BASE_URL : validated.baseUrl);
239
+ this.diagramTypes = Object.freeze({
240
+ ...KROKI_DIAGRAM_TYPES,
241
+ ...validateDiagramTypes(validated.diagramTypes)
242
+ });
243
+ }
244
+ canRender(req) {
245
+ return this.diagramTypes[normalizeFenceLanguage(req.lang)] !== void 0;
246
+ }
247
+ async render(req, _context) {
248
+ const diagramType = this.diagramTypes[normalizeFenceLanguage(req.lang)];
249
+ if (diagramType === void 0) return null;
250
+ const encoded = await deflateBase64Url(req.source);
251
+ const imageUrl = `${this.baseUrl}/${encodeURIComponent(diagramType)}/png/${encoded}`;
252
+ if (imageUrl.length > 3e3) return null;
253
+ return {
254
+ kind: "url",
255
+ imageUrl,
256
+ altText: req.altText
257
+ };
258
+ }
130
259
  };
131
-
132
- // src/renderers/mermaid-ink-renderer.ts
133
- var DEFAULT_MERMAID_INK_BASE_URL = "https://mermaid.ink";
134
- var DEFAULT_MERMAID_INK_BACKGROUND = "!white";
260
+ function validateDiagramTypes(value) {
261
+ if (value === void 0) return {};
262
+ let keys;
263
+ try {
264
+ keys = Object.keys(value);
265
+ } catch (cause) {
266
+ throw invalidRendererOptions("KrokiRenderer.diagramTypes could not be inspected", cause);
267
+ }
268
+ const snapshot = snapshotRendererOptions(value, keys, "KrokiRenderer.diagramTypes");
269
+ for (const [language, diagramType] of Object.entries(snapshot)) if (language.length === 0 || typeof diagramType !== "string" || diagramType.length === 0) throw invalidRendererOptions("KrokiRenderer.diagramTypes values must be nonempty strings");
270
+ return snapshot;
271
+ }
272
+ //#endregion
273
+ //#region src/renderers/mermaid-ink-renderer.ts
274
+ const DEFAULT_MERMAID_INK_BASE_URL = "https://mermaid.ink";
275
+ const DEFAULT_MERMAID_INK_BACKGROUND = "!white";
276
+ /**
277
+ * Produces mermaid.ink pako-state PNG URLs without performing network I/O.
278
+ */
135
279
  var MermaidInkRenderer = class {
136
- baseUrl;
137
- theme;
138
- bgColor;
139
- constructor(options = {}) {
140
- this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_MERMAID_INK_BASE_URL);
141
- this.theme = options.theme ?? "default";
142
- this.bgColor = options.bgColor ?? DEFAULT_MERMAID_INK_BACKGROUND;
143
- }
144
- canRender(req) {
145
- return normalizeFenceLanguage(req.lang) === "mermaid";
146
- }
147
- async render(req) {
148
- if (!this.canRender(req)) {
149
- return null;
150
- }
151
- const state = {
152
- code: req.source,
153
- mermaid: { theme: this.theme }
154
- };
155
- const encoded = await deflateBase64Url(JSON.stringify(state));
156
- const imageUrl = `${this.baseUrl}/img/pako:${encoded}?type=png&bgColor=${encodeURIComponent(this.bgColor)}`;
157
- if (imageUrl.length > IMAGE_URL_MAX_CHARS) {
158
- return null;
159
- }
160
- return {
161
- kind: "url",
162
- imageUrl,
163
- altText: req.altText
164
- };
165
- }
280
+ id;
281
+ output;
282
+ baseUrl;
283
+ theme;
284
+ bgColor;
285
+ constructor(options = {}) {
286
+ const validated = snapshotRendererOptions(options, [
287
+ "baseUrl",
288
+ "theme",
289
+ "bgColor"
290
+ ], "MermaidInkRenderer");
291
+ this.id = "mermaid-ink";
292
+ this.output = "url";
293
+ this.baseUrl = normalizeBaseUrl(validated.baseUrl === void 0 ? DEFAULT_MERMAID_INK_BASE_URL : validated.baseUrl);
294
+ this.theme = validated.theme === void 0 ? "default" : validated.theme;
295
+ this.bgColor = validated.bgColor === void 0 ? DEFAULT_MERMAID_INK_BACKGROUND : validated.bgColor;
296
+ if (![
297
+ "default",
298
+ "neutral",
299
+ "dark",
300
+ "forest"
301
+ ].includes(this.theme) || typeof this.bgColor !== "string" || this.bgColor.length === 0) throw invalidRendererOptions("MermaidInkRenderer theme or bgColor is invalid");
302
+ }
303
+ canRender(req) {
304
+ return normalizeFenceLanguage(req.lang) === "mermaid";
305
+ }
306
+ async render(req, _context) {
307
+ if (!this.canRender(req)) return null;
308
+ const state = {
309
+ code: req.source,
310
+ mermaid: { theme: this.theme }
311
+ };
312
+ const encoded = await deflateBase64Url(JSON.stringify(state));
313
+ const imageUrl = `${this.baseUrl}/img/pako:${encoded}?type=png&bgColor=${encodeURIComponent(this.bgColor)}`;
314
+ if (imageUrl.length > 3e3) return null;
315
+ return {
316
+ kind: "url",
317
+ imageUrl,
318
+ altText: req.altText
319
+ };
320
+ }
166
321
  };
167
-
168
- // src/renderers/codecogs-renderer.ts
169
- var DEFAULT_CODECOGS_BASE_URL = "https://latex.codecogs.com";
170
- var DEFAULT_CODECOGS_DPI = 200;
171
- var MATH_FENCE_LANGUAGES = /* @__PURE__ */ new Set(["katex", "latex", "math", "tex"]);
322
+ //#endregion
323
+ //#region src/renderers/codecogs-renderer.ts
324
+ const DEFAULT_CODECOGS_BASE_URL = "https://latex.codecogs.com";
325
+ const DEFAULT_CODECOGS_DPI = 200;
326
+ const MATH_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
327
+ "katex",
328
+ "latex",
329
+ "math",
330
+ "tex"
331
+ ]);
332
+ /**
333
+ * Produces deterministic CodeCogs `png.image` URLs for display math.
334
+ */
172
335
  var CodeCogsRenderer = class {
173
- baseUrl;
174
- dpi;
175
- constructor(options = {}) {
176
- this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_CODECOGS_BASE_URL);
177
- this.dpi = options.dpi ?? DEFAULT_CODECOGS_DPI;
178
- if (!Number.isInteger(this.dpi) || this.dpi <= 0) {
179
- throw new RangeError("dpi must be a positive integer");
180
- }
181
- }
182
- canRender(req) {
183
- return MATH_FENCE_LANGUAGES.has(normalizeFenceLanguage(req.lang));
184
- }
185
- async render(req) {
186
- if (!this.canRender(req)) {
187
- return null;
188
- }
189
- const formula = `\\dpi{${String(this.dpi)}} ${req.source}`;
190
- const imageUrl = `${this.baseUrl}/png.image?${encodeURIComponent(formula)}`;
191
- if (imageUrl.length > IMAGE_URL_MAX_CHARS) {
192
- return null;
193
- }
194
- return {
195
- kind: "url",
196
- imageUrl,
197
- altText: req.altText
198
- };
199
- }
336
+ id;
337
+ output;
338
+ baseUrl;
339
+ dpi;
340
+ constructor(options = {}) {
341
+ const validated = snapshotRendererOptions(options, ["baseUrl", "dpi"], "CodeCogsRenderer");
342
+ this.id = "codecogs";
343
+ this.output = "url";
344
+ this.baseUrl = normalizeBaseUrl(validated.baseUrl === void 0 ? DEFAULT_CODECOGS_BASE_URL : validated.baseUrl);
345
+ this.dpi = validated.dpi === void 0 ? DEFAULT_CODECOGS_DPI : validated.dpi;
346
+ if (!Number.isInteger(this.dpi) || this.dpi <= 0) throw invalidRendererOptions("dpi must be a positive integer");
347
+ }
348
+ canRender(req) {
349
+ return MATH_FENCE_LANGUAGES.has(normalizeFenceLanguage(req.lang));
350
+ }
351
+ async render(req, _context) {
352
+ if (!this.canRender(req)) return null;
353
+ const formula = `\\dpi{${String(this.dpi)}} ${req.source}`;
354
+ const imageUrl = `${this.baseUrl}/png.image?${encodeURIComponent(formula)}`;
355
+ if (imageUrl.length > 3e3) return null;
356
+ return {
357
+ kind: "url",
358
+ imageUrl,
359
+ altText: req.altText
360
+ };
361
+ }
200
362
  };
201
-
202
- // src/renderers/quickchart-renderer.ts
203
- var DEFAULT_QUICKCHART_BASE_URL = "https://quickchart.io";
204
- var DEFAULT_QUICKCHART_WIDTH = 500;
205
- var DEFAULT_QUICKCHART_HEIGHT = 300;
206
- var DEFAULT_QUICKCHART_BACKGROUND = "white";
207
- var DEFAULT_QUICKCHART_DEVICE_PIXEL_RATIO = 2;
208
- var CHART_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
209
- "chart",
210
- "chart.js",
211
- "chartjs",
212
- "quickchart"
363
+ //#endregion
364
+ //#region src/renderers/quickchart-renderer.ts
365
+ const DEFAULT_QUICKCHART_BASE_URL = "https://quickchart.io";
366
+ const DEFAULT_QUICKCHART_WIDTH = 500;
367
+ const DEFAULT_QUICKCHART_HEIGHT = 300;
368
+ const DEFAULT_QUICKCHART_BACKGROUND = "white";
369
+ const DEFAULT_QUICKCHART_DEVICE_PIXEL_RATIO = 2;
370
+ const CHART_FENCE_LANGUAGES = /* @__PURE__ */ new Set([
371
+ "chart",
372
+ "chart.js",
373
+ "chartjs",
374
+ "quickchart"
213
375
  ]);
376
+ /**
377
+ * Produces full, durable QuickChart GET URLs. It never uses the expiring
378
+ * `/chart/create` short-URL API.
379
+ */
214
380
  var QuickChartRenderer = class {
215
- baseUrl;
216
- width;
217
- height;
218
- backgroundColor;
219
- devicePixelRatio;
220
- constructor(options = {}) {
221
- this.baseUrl = normalizeBaseUrl(options.baseUrl ?? DEFAULT_QUICKCHART_BASE_URL);
222
- this.width = options.width ?? DEFAULT_QUICKCHART_WIDTH;
223
- this.height = options.height ?? DEFAULT_QUICKCHART_HEIGHT;
224
- this.backgroundColor = options.backgroundColor ?? DEFAULT_QUICKCHART_BACKGROUND;
225
- this.devicePixelRatio = options.devicePixelRatio ?? DEFAULT_QUICKCHART_DEVICE_PIXEL_RATIO;
226
- if (!Number.isInteger(this.width) || this.width <= 0) {
227
- throw new RangeError("width must be a positive integer");
228
- }
229
- if (!Number.isInteger(this.height) || this.height <= 0) {
230
- throw new RangeError("height must be a positive integer");
231
- }
232
- if (!Number.isFinite(this.devicePixelRatio) || this.devicePixelRatio <= 0) {
233
- throw new RangeError("devicePixelRatio must be positive");
234
- }
235
- }
236
- canRender(req) {
237
- return CHART_FENCE_LANGUAGES.has(normalizeFenceLanguage(req.lang));
238
- }
239
- async render(req) {
240
- if (!this.canRender(req)) {
241
- return null;
242
- }
243
- const query = [
244
- `c=${encodeURIComponent(req.source)}`,
245
- `width=${String(this.width)}`,
246
- `height=${String(this.height)}`,
247
- `backgroundColor=${encodeURIComponent(this.backgroundColor)}`,
248
- `devicePixelRatio=${String(this.devicePixelRatio)}`,
249
- "format=png"
250
- ].join("&");
251
- const imageUrl = `${this.baseUrl}/chart?${query}`;
252
- if (imageUrl.length > IMAGE_URL_MAX_CHARS) {
253
- return null;
254
- }
255
- return {
256
- kind: "url",
257
- imageUrl,
258
- altText: req.altText
259
- };
260
- }
261
- };
262
- export {
263
- CodeCogsRenderer,
264
- KROKI_DIAGRAM_TYPES,
265
- KrokiRenderer,
266
- MermaidInkRenderer,
267
- QuickChartRenderer
381
+ id;
382
+ output;
383
+ baseUrl;
384
+ width;
385
+ height;
386
+ backgroundColor;
387
+ devicePixelRatio;
388
+ constructor(options = {}) {
389
+ const validated = snapshotRendererOptions(options, [
390
+ "baseUrl",
391
+ "width",
392
+ "height",
393
+ "backgroundColor",
394
+ "devicePixelRatio"
395
+ ], "QuickChartRenderer");
396
+ this.id = "quickchart";
397
+ this.output = "url";
398
+ this.baseUrl = normalizeBaseUrl(validated.baseUrl === void 0 ? DEFAULT_QUICKCHART_BASE_URL : validated.baseUrl);
399
+ this.width = validated.width === void 0 ? DEFAULT_QUICKCHART_WIDTH : validated.width;
400
+ this.height = validated.height === void 0 ? DEFAULT_QUICKCHART_HEIGHT : validated.height;
401
+ this.backgroundColor = validated.backgroundColor === void 0 ? DEFAULT_QUICKCHART_BACKGROUND : validated.backgroundColor;
402
+ this.devicePixelRatio = validated.devicePixelRatio === void 0 ? DEFAULT_QUICKCHART_DEVICE_PIXEL_RATIO : validated.devicePixelRatio;
403
+ if (!Number.isInteger(this.width) || this.width <= 0) throw invalidRendererOptions("width must be a positive integer");
404
+ if (!Number.isInteger(this.height) || this.height <= 0) throw invalidRendererOptions("height must be a positive integer");
405
+ if (!Number.isFinite(this.devicePixelRatio) || this.devicePixelRatio <= 0) throw invalidRendererOptions("devicePixelRatio must be positive");
406
+ if (typeof this.backgroundColor !== "string" || this.backgroundColor.length === 0) throw invalidRendererOptions("backgroundColor must be a nonempty string");
407
+ }
408
+ canRender(req) {
409
+ return CHART_FENCE_LANGUAGES.has(normalizeFenceLanguage(req.lang));
410
+ }
411
+ async render(req, _context) {
412
+ if (!this.canRender(req)) return null;
413
+ const query = [
414
+ `c=${encodeURIComponent(req.source)}`,
415
+ `width=${String(this.width)}`,
416
+ `height=${String(this.height)}`,
417
+ `backgroundColor=${encodeURIComponent(this.backgroundColor)}`,
418
+ `devicePixelRatio=${String(this.devicePixelRatio)}`,
419
+ "format=png"
420
+ ].join("&");
421
+ const imageUrl = `${this.baseUrl}/chart?${query}`;
422
+ if (imageUrl.length > 3e3) return null;
423
+ return {
424
+ kind: "url",
425
+ imageUrl,
426
+ altText: req.altText
427
+ };
428
+ }
268
429
  };
430
+ //#endregion
431
+ export { CodeCogsRenderer, KROKI_DIAGRAM_TYPES, KrokiRenderer, MermaidInkRenderer, QuickChartRenderer, isConfigurationError, isOperationError };
432
+
269
433
  //# sourceMappingURL=renderers.js.map