satteri 0.9.0 → 0.9.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.
package/README.md CHANGED
@@ -182,9 +182,25 @@ const { html, data } = markdownToHtml("# A\n\n# B", { mdastPlugins: [collectHead
182
182
  console.log(data?.headings); // ["A", "B"]
183
183
  ```
184
184
 
185
- The bag lives entirely on the JS side, so any value is allowed, including functions, class instances, and `Map`/`Set`. References are preserved across plugins and across the mdast→hast boundary. A fresh, empty bag is created for every compile.
185
+ The bag lives entirely on the JS side, so any value is allowed, including functions, class instances, and `Map`/`Set`. References are preserved across plugins and across the mdast→hast boundary. A fresh, empty bag is created for every compile unless you seed one with the `data` option.
186
186
 
187
- By default keys are typed as `unknown`. To give a key a type, augment the `DataMap` interface, much like `vfile`'s `DataMap`:
187
+ To seed data into the bag before plugins run, pass an object to the `data` option. The same object is available in all plugins and returned as `result.data` after compilation, so you can pre-populate it with values or methods that plugins can read and update.
188
+
189
+ ```ts
190
+ const data = { title: "Hello" };
191
+ const rewriteTitle = defineMdastPlugin({
192
+ name: "rewrite-title",
193
+ heading(_node, ctx) {
194
+ ctx.data.title = "Updated";
195
+ },
196
+ });
197
+
198
+ const result = markdownToHtml("# A", { mdastPlugins: [rewriteTitle], data });
199
+ console.log(result.data === data); // true, the seeded object is returned
200
+ console.log(data.title); // "Updated"
201
+ ```
202
+
203
+ By default keys are typed as `unknown`. To give a key a type, augment the `DataMap` interface:
188
204
 
189
205
  ```ts
190
206
  declare module "satteri" {
@@ -334,6 +350,7 @@ interface CompileOptions {
334
350
  hastPlugins?: HastPluginDefinition[];
335
351
  features?: Features;
336
352
  fileURL?: URL;
353
+ data?: Data;
337
354
  }
338
355
 
339
356
  // mdxToJs accepts MdxCompileOptions, which extends CompileOptions
@@ -344,6 +361,8 @@ interface MdxCompileOptions extends CompileOptions {
344
361
 
345
362
  `fileURL` is the `URL` of the document being processed, surfaced to plugins as `ctx.fileURL` (a `URL`, or `undefined` when omitted). Pass a file URL such as Astro's `fileURL`, or convert a filesystem path with Node's `pathToFileURL`; read it back with `fileURLToPath(ctx.fileURL)`.
346
363
 
364
+ `data` seeds the document-level data bag before plugins run, surfaced as `ctx.data` and returned as `result.data`. See [Sharing data between plugins](#sharing-data-between-plugins).
365
+
347
366
  `Features` controls the Markdown extensions Sätteri's parser recognizes: `gfm`, `frontmatter`, `math`, `directive`, `smartPunctuation`, etc. `gfm`, `math`, and `smartPunctuation` also accept granular options. For example, `math: { singleDollarTextMath: false }` keeps single `$` as literal text. See the [Features reference](https://satteri.dev/docs/features/) for the full list.
348
367
 
349
368
  ## License
package/dist/compile.d.ts CHANGED
@@ -130,6 +130,14 @@ export interface CompileOptions {
130
130
  * `pathToFileURL` before passing it.
131
131
  */
132
132
  fileURL?: URL;
133
+ /**
134
+ * Initial document-level data bag, seeding `ctx.data` before any plugin runs.
135
+ * It is the same object plugins mutate and the caller reads back as
136
+ * `result.data`, so values flow both into and out of a compile. Defaults to a
137
+ * fresh empty object. Used by reference and mutated in place, so pass a
138
+ * throwaway object per compile rather than a shared one.
139
+ */
140
+ data?: Data;
133
141
  }
134
142
  /**
135
143
  * MDX-only compile options.
@@ -200,7 +208,7 @@ export interface MarkdownToHtmlResult {
200
208
  html: string;
201
209
  /** Frontmatter block at the start of the document, or `null` if none. */
202
210
  frontmatter: Frontmatter | null;
203
- /** Document-level data bag populated by plugins via `ctx.data`. Empty `{}` if untouched. */
211
+ /** Document-level data bag shared with plugins via `ctx.data`; the seeded `data` option if provided, else a fresh `{}`. */
204
212
  data: Data;
205
213
  }
206
214
  /** Result of {@link mdxToJs}. */
@@ -209,7 +217,7 @@ export interface MdxToJsResult {
209
217
  code: string;
210
218
  /** Frontmatter block at the start of the document, or `null` if none. */
211
219
  frontmatter: Frontmatter | null;
212
- /** Document-level data bag populated by plugins via `ctx.data`. Empty `{}` if untouched. */
220
+ /** Document-level data bag shared with plugins via `ctx.data`; the seeded `data` option if provided, else a fresh `{}`. */
213
221
  data: Data;
214
222
  }
215
223
  type AnyFn = (...args: any[]) => unknown;
package/dist/compile.js CHANGED
@@ -75,11 +75,12 @@ function featuresToNative(features) {
75
75
  }
76
76
  return { features: result, convertOptions };
77
77
  }
78
- /** Drop a handle after bumping its epoch: the arena dies with the handle, so a
79
- * child stub retained past the pipeline must hit the designed retention error,
80
- * not a cryptic RangeError from snapshotting the freed arena. */
81
- function disposeHandle(handle) {
82
- markHandleMutated(handle);
78
+ /** Free a handle's arena. A plugin's visitor can hand child stubs to user code;
79
+ * pass `invalidateStubs` so the epoch bump makes any stub retained past this
80
+ * point hit the designed retention error instead of snapshotting the freed arena. */
81
+ function releaseHandle(handle, invalidateStubs) {
82
+ if (invalidateStubs)
83
+ markHandleMutated(handle);
83
84
  dropHandle(handle);
84
85
  }
85
86
  function warnDroppedTransforms(plugin, dropped, kind) {
@@ -190,9 +191,9 @@ function mdxOptionsToNative(opts) {
190
191
  return result;
191
192
  }
192
193
  export function markdownToHtml(source, options = {}) {
193
- const { mdastPlugins = [], hastPlugins = [], features, fileURL } = options;
194
+ const { mdastPlugins = [], hastPlugins = [], features, fileURL, data = {} } = options;
195
+ const hastMayHaveStubs = hastPlugins.length > 0;
194
196
  const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
195
- const data = {};
196
197
  const result = createHastHandleFromMdast(source, mdastPlugins, false, fileURL, nativeFeatures, nativeConvertOptions, data);
197
198
  const renderAndDrop = (h, frontmatter) => {
198
199
  try {
@@ -200,7 +201,7 @@ export function markdownToHtml(source, options = {}) {
200
201
  return { html, frontmatter, data };
201
202
  }
202
203
  finally {
203
- disposeHandle(h);
204
+ releaseHandle(h, hastMayHaveStubs);
204
205
  }
205
206
  };
206
207
  const runHastThenRender = (r) => {
@@ -209,12 +210,12 @@ export function markdownToHtml(source, options = {}) {
209
210
  hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL, data);
210
211
  }
211
212
  catch (err) {
212
- disposeHandle(r.hastHandle);
213
+ releaseHandle(r.hastHandle, hastMayHaveStubs);
213
214
  throw err;
214
215
  }
215
216
  if (hastResult instanceof Promise) {
216
217
  return hastResult.then(() => renderAndDrop(r.hastHandle, r.frontmatter), (err) => {
217
- disposeHandle(r.hastHandle);
218
+ releaseHandle(r.hastHandle, hastMayHaveStubs);
218
219
  throw err;
219
220
  });
220
221
  }
@@ -225,10 +226,10 @@ export function markdownToHtml(source, options = {}) {
225
226
  return runHastThenRender(result);
226
227
  }
227
228
  export function mdxToJs(source, options = {}) {
228
- const { mdastPlugins = [], hastPlugins = [], features, fileURL, ...mdxFields } = options;
229
+ const { mdastPlugins = [], hastPlugins = [], features, fileURL, data = {}, ...mdxFields } = options;
230
+ const hastMayHaveStubs = hastPlugins.length > 0;
229
231
  const mdxOptions = mdxOptionsToNative(mdxFields);
230
232
  const { features: nativeFeatures, convertOptions: nativeConvertOptions } = featuresToNative(features);
231
- const data = {};
232
233
  const result = createHastHandleFromMdast(source, mdastPlugins, true, fileURL, nativeFeatures, nativeConvertOptions, data);
233
234
  const compileAndDrop = (h, frontmatter) => {
234
235
  try {
@@ -236,7 +237,7 @@ export function mdxToJs(source, options = {}) {
236
237
  return { code, frontmatter, data };
237
238
  }
238
239
  finally {
239
- disposeHandle(h);
240
+ releaseHandle(h, hastMayHaveStubs);
240
241
  }
241
242
  };
242
243
  const runHastThenCompile = (r) => {
@@ -245,12 +246,12 @@ export function mdxToJs(source, options = {}) {
245
246
  hastResult = runHastPluginsOnHandle(r.hastHandle, hastPlugins, source, fileURL, data);
246
247
  }
247
248
  catch (err) {
248
- disposeHandle(r.hastHandle);
249
+ releaseHandle(r.hastHandle, hastMayHaveStubs);
249
250
  throw err;
250
251
  }
251
252
  if (hastResult instanceof Promise) {
252
253
  return hastResult.then(() => compileAndDrop(r.hastHandle, r.frontmatter), (err) => {
253
- disposeHandle(r.hastHandle);
254
+ releaseHandle(r.hastHandle, hastMayHaveStubs);
254
255
  throw err;
255
256
  });
256
257
  }
@@ -295,19 +296,20 @@ nativeConvertOptions, data) {
295
296
  const mdastHandle = mdx
296
297
  ? createMdxMdastHandle(source, nativeFeatures)
297
298
  : createMdastHandle(source, nativeFeatures);
298
- // finally{dispose} is intentional: convertMdastToHastHandle empties the arena
299
+ const mdastMayHaveStubs = mdastPlugins.length > 0;
300
+ // finally{release} is intentional: convertMdastToHastHandle empties the arena
299
301
  // on success, but if any step here throws the handle would otherwise leak.
300
302
  const finalize = (r) => {
301
303
  try {
302
304
  const frontmatter = readFrontmatter(r.handle);
303
- // The conversion empties the mdast arena; bump the epoch so a stub
304
- // retained past the mdast pass fails with the retention error.
305
- markHandleMutated(r.handle);
305
+ // convert empties the mdast arena, so invalidate any stub held past this point.
306
+ if (mdastMayHaveStubs)
307
+ markHandleMutated(r.handle);
306
308
  const hastHandle = convertMdastToHastHandle(r.handle, nativeConvertOptions);
307
309
  return { hastHandle, frontmatter };
308
310
  }
309
311
  finally {
310
- disposeHandle(r.handle);
312
+ releaseHandle(r.handle, mdastMayHaveStubs);
311
313
  }
312
314
  };
313
315
  try {
@@ -317,14 +319,14 @@ nativeConvertOptions, data) {
317
319
  const mdastResult = runMdastPluginsOnHandle(mdastHandle, mdastPlugins, fileURL, data);
318
320
  if (mdastResult instanceof Promise) {
319
321
  return mdastResult.then(finalize, (err) => {
320
- disposeHandle(mdastHandle);
322
+ releaseHandle(mdastHandle, mdastMayHaveStubs);
321
323
  throw err;
322
324
  });
323
325
  }
324
326
  return finalize(mdastResult);
325
327
  }
326
328
  catch (err) {
327
- disposeHandle(mdastHandle);
329
+ releaseHandle(mdastHandle, mdastMayHaveStubs);
328
330
  throw err;
329
331
  }
330
332
  }
@@ -336,7 +338,7 @@ export function markdownToMdast(source, options = {}) {
336
338
  return materializeMdastTree(new MdastReader(serializeHandle(handle)));
337
339
  }
338
340
  finally {
339
- disposeHandle(handle);
341
+ releaseHandle(handle, true);
340
342
  }
341
343
  }
342
344
  /** Parse MDX source into a materialized mdast tree. */
@@ -346,7 +348,7 @@ export function mdxToMdast(source, options = {}) {
346
348
  return materializeMdastTree(new MdastReader(serializeHandle(handle)));
347
349
  }
348
350
  finally {
349
- disposeHandle(handle);
351
+ releaseHandle(handle, true);
350
352
  }
351
353
  }
352
354
  /** Convert Markdown source to a materialized hast tree. */
@@ -357,7 +359,7 @@ export function markdownToHast(source, options = {}) {
357
359
  return materializeHastTree(new HastReader(serializeHandle(handle)));
358
360
  }
359
361
  finally {
360
- disposeHandle(handle);
362
+ releaseHandle(handle, true);
361
363
  }
362
364
  }
363
365
  /** Convert MDX source to a materialized hast tree. */
@@ -368,6 +370,6 @@ export function mdxToHast(source, options = {}) {
368
370
  return materializeHastTree(new HastReader(serializeHandle(handle)));
369
371
  }
370
372
  finally {
371
- disposeHandle(handle);
373
+ releaseHandle(handle, true);
372
374
  }
373
375
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "satteri",
3
- "version": "0.9.0",
3
+ "version": "0.9.1",
4
4
  "description": "High-performance Markdown and MDX processing",
5
5
  "repository": {
6
6
  "type": "git",
@@ -80,11 +80,11 @@
80
80
  ]
81
81
  },
82
82
  "optionalDependencies": {
83
- "@bruits/satteri-linux-x64-gnu": "0.9.0",
84
- "@bruits/satteri-darwin-x64": "0.9.0",
85
- "@bruits/satteri-darwin-arm64": "0.9.0",
86
- "@bruits/satteri-win32-x64-msvc": "0.9.0",
87
- "@bruits/satteri-wasm32-wasi": "0.9.0"
83
+ "@bruits/satteri-linux-x64-gnu": "0.9.1",
84
+ "@bruits/satteri-darwin-x64": "0.9.1",
85
+ "@bruits/satteri-darwin-arm64": "0.9.1",
86
+ "@bruits/satteri-win32-x64-msvc": "0.9.1",
87
+ "@bruits/satteri-wasm32-wasi": "0.9.1"
88
88
  },
89
89
  "scripts": {
90
90
  "build:binary:wasm": "napi build --manifest-path ../../crates/satteri-napi-binding/Cargo.toml --output-dir . --platform --release --esm --strip --target wasm32-wasip1-threads",