@astryxdesign/cli 0.7.12-canary.ccb4a6d → 0.7.12-canary.da3dbdc

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 (85) hide show
  1. package/api/component/__fixtures__/resolutions/modules.txt +5 -5
  2. package/api/docs/_adapter.d.mts +22 -0
  3. package/api/docs/_adapter.mjs +81 -3
  4. package/api/docs/docs.doc.mjs +5 -5
  5. package/api/docs/docs.type.d.mts +21 -0
  6. package/api/docs/docs.type.mjs +11 -0
  7. package/api/docs/list/list.d.mts +5 -1
  8. package/api/docs/list/list.mjs +72 -6
  9. package/api/docs/node/node.mjs +2 -40
  10. package/api/index.d.mts +1 -0
  11. package/api/index.mjs +1 -0
  12. package/api/render/_adapter.mjs +9 -0
  13. package/api/render/render.doc.mjs +2 -2
  14. package/api/search/_adapter.d.mts +22 -1
  15. package/api/search/_adapter.mjs +37 -20
  16. package/api/search/exportSearchIndex.doc.d.mts +10 -0
  17. package/api/search/exportSearchIndex.doc.mjs +58 -0
  18. package/api/search/rankSearchIndex.doc.d.mts +10 -0
  19. package/api/search/rankSearchIndex.doc.mjs +79 -0
  20. package/api/search/search-index.d.mts +44 -0
  21. package/api/search/search-index.mjs +270 -0
  22. package/api/search/search.type.d.mts +28 -0
  23. package/api/search/search.type.mjs +17 -0
  24. package/api/swizzle/_moduleGraph.d.mts +7 -0
  25. package/api/swizzle/_moduleGraph.mjs +191 -83
  26. package/api/swizzle/copy/copy.mjs +100 -9
  27. package/api/swizzle/swizzle.doc.mjs +7 -3
  28. package/api/swizzle/swizzle.type.d.mts +5 -0
  29. package/api/swizzle/swizzle.type.mjs +1 -0
  30. package/api/template/template.mjs +1 -0
  31. package/assets/discover/official-packages.json +16 -0
  32. package/assets/docs/browser-support.doc.mjs +2 -2
  33. package/assets/docs/styling-overview.doc.mjs +6 -2
  34. package/assets/docs/tree/render-pages.doc.mjs +2 -2
  35. package/assets/docs/tree/tailwind.doc.mjs +41 -1
  36. package/assets/docs/tree/type-scale.doc.mjs +19 -2
  37. package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.doc.mjs +14 -0
  38. package/assets/templates/blocks/components/Calendar/useCalendarConstraintsHookUsage.tsx +146 -0
  39. package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.doc.mjs +14 -0
  40. package/assets/templates/blocks/components/Calendar/useCalendarNavigationHookUsage.tsx +173 -0
  41. package/assets/templates/blocks/components/Hooks/useInputStatusIconHookUsage.tsx +1 -0
  42. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.doc.mjs +14 -0
  43. package/assets/templates/blocks/components/Hooks/useInteractiveRoleHookUsage.tsx +56 -0
  44. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.doc.mjs +2 -2
  45. package/assets/templates/blocks/components/Hooks/useStreamingTextHookUsage.tsx +66 -10
  46. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.doc.mjs +14 -0
  47. package/assets/templates/blocks/components/Link/useLinkifyHookUsage.tsx +39 -0
  48. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.doc.mjs +14 -0
  49. package/assets/templates/blocks/components/PowerSearch/usePowerSearchConfigHookUsage.tsx +83 -0
  50. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.doc.mjs +14 -0
  51. package/assets/templates/blocks/components/Table/RowsVirtualizationHookUsage.tsx +35 -0
  52. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.doc.mjs +14 -0
  53. package/assets/templates/blocks/components/Table/useTableColumnSettingsStateHookUsage.tsx +97 -0
  54. package/assets/templates/pages/table-filter/page.tsx +15 -23
  55. package/assets/templates/pages/table-inbox/page.tsx +3 -6
  56. package/assets/templates/themes/butter/butterTheme.ts +2 -2
  57. package/assets/templates/themes/stone/stoneTheme.ts +1 -1
  58. package/assets/templates/themes/y2k/y2kTheme.ts +1 -1
  59. package/clients/cli/commands/docs.doc.mjs +5 -1
  60. package/clients/cli/commands/docs.mjs +30 -2
  61. package/clients/cli/commands/swizzle.doc.mjs +4 -0
  62. package/clients/cli/commands/swizzle.mjs +12 -0
  63. package/clients/cli/lib/resolve-theme.mjs +16 -7
  64. package/foundation/discovery/hook-discovery.mjs +31 -8
  65. package/foundation/doc-compiler/import.d.mts +5 -1
  66. package/foundation/doc-compiler/import.mjs +40 -6
  67. package/foundation/fs/import-url.d.mts +20 -0
  68. package/foundation/fs/import-url.mjs +22 -0
  69. package/foundation/fs/module-loader.d.mts +24 -3
  70. package/foundation/fs/module-loader.mjs +69 -47
  71. package/foundation/render/charts.d.mts +59 -0
  72. package/foundation/render/charts.mjs +216 -0
  73. package/foundation/render/embedded-imports.d.mts +7 -7
  74. package/foundation/render/embedded-imports.mjs +63 -54
  75. package/foundation/render/page-worker.mjs +339 -52
  76. package/foundation/render/render-page.mjs +26 -10
  77. package/foundation/render/ts-paths.d.mts +63 -0
  78. package/foundation/render/ts-paths.mjs +138 -0
  79. package/foundation/response/error-codes.doc.mjs +2 -2
  80. package/foundation/response/error-codes.mjs +3 -2
  81. package/foundation/text/levenshtein.d.mts +6 -1
  82. package/foundation/text/levenshtein.mjs +25 -9
  83. package/foundation/text/suggest-order.d.mts +8 -1
  84. package/foundation/text/suggest-order.mjs +72 -6
  85. package/package.json +7 -6
@@ -73,11 +73,22 @@ function extractTheme(mod) {
73
73
  return null;
74
74
  }
75
75
 
76
+ /**
77
+ * Print a theme warning as one inert line. The configured theme value and the
78
+ * generated record come from the checkout, so a newline, an escape sequence or
79
+ * a bidi override in them must not rewrite the terminal. Control and format
80
+ * characters print as U+FFFD.
81
+ * @param {string} message
82
+ */
83
+ function warnTheme(message) {
84
+ console.warn(message.replace(/[\p{Cc}\p{Cf}]/gu, '\uFFFD'));
85
+ }
86
+
76
87
  /** @param {unknown} mod @param {string} specifier */
77
88
  function resolvedThemeData(mod, specifier) {
78
89
  const theme = extractTheme(mod);
79
90
  if (!theme) {
80
- console.warn(
91
+ warnTheme(
81
92
  `⚠ theme: loaded "${specifier}" but could not find a theme object`,
82
93
  );
83
94
  return null;
@@ -100,7 +111,7 @@ export async function resolveTheme(cwd = process.cwd()) {
100
111
  const loaded = await resolveRecordedTheme(cwd);
101
112
  if (loaded.configured) {
102
113
  if (!loaded.module) {
103
- console.warn(
114
+ warnTheme(
104
115
  `⚠ theme: ${loaded.problem ?? `could not resolve the generated default theme (${loaded.specifier})`}`,
105
116
  );
106
117
  return null;
@@ -130,15 +141,13 @@ export async function resolveTheme(cwd = process.cwd()) {
130
141
  if (specifier.startsWith('.') || specifier.startsWith('/')) {
131
142
  mod = tryLoadLegacyModule(specifier, cwd);
132
143
  if (!mod) {
133
- console.warn(
134
- `⚠ theme: could not resolve file "${specifier}" from ${cwd}`,
135
- );
144
+ warnTheme(`⚠ theme: could not resolve file "${specifier}" from ${cwd}`);
136
145
  return null;
137
146
  }
138
147
  } else if (specifier.startsWith('@')) {
139
148
  mod = tryLoadLegacyModule(specifier, cwd);
140
149
  if (!mod) {
141
- console.warn(`⚠ theme: could not resolve package "${specifier}"`);
150
+ warnTheme(`⚠ theme: could not resolve package "${specifier}"`);
142
151
  return null;
143
152
  }
144
153
  } else {
@@ -147,7 +156,7 @@ export async function resolveTheme(cwd = process.cwd()) {
147
156
  tryLoadLegacyModule(conventional, cwd) ??
148
157
  tryLoadLegacyModule(specifier, cwd);
149
158
  if (!mod) {
150
- console.warn(
159
+ warnTheme(
151
160
  `⚠ theme: could not resolve "${specifier}" (tried ${conventional} and ${specifier})`,
152
161
  );
153
162
  return null;
@@ -12,6 +12,13 @@ import {levenshteinDistance} from '../text/string-utils.mjs';
12
12
 
13
13
  const SKIP_DIRS = new Set(['utils', '__tests__', 'node_modules']);
14
14
 
15
+ // A hook's doc outside src/hooks/: a `use…` hook, or a `create…` factory
16
+ // documented as one (its doc typed `HookDoc`, such as createReorderable).
17
+ const HOOK_DOC_FILE_RE = /^(?:use|create)[A-Z]\w*\.doc\.mjs$/;
18
+
19
+ // Matches the top-level `relatedHooks: [...]` field in a hook .doc.mjs file.
20
+ const RELATED_HOOKS_RE = /(?:^|\n) {0,4}relatedHooks:\s*\[([^\]]*)\]/;
21
+
15
22
  // Matches the top-level `category: 'categoryName'` field in a hook .doc.mjs file.
16
23
  const CATEGORY_RE = /(?:^|\n) {0,4}category:\s*['"]([^'"]+)['"]/;
17
24
 
@@ -32,6 +39,25 @@ function readHookMeta(docPath) {
32
39
  }
33
40
  }
34
41
 
42
+ /**
43
+ * Whether a `create…` factory's doc names its own `use…` hook in
44
+ * `relatedHooks`, as createAnimator's names useAnimator. Such a factory is
45
+ * listed beside that hook, not on its own.
46
+ * @param {string} docPath
47
+ * @param {string} hookName
48
+ * @returns {boolean}
49
+ */
50
+ function isBesideOwnHook(docPath, hookName) {
51
+ if (!hookName.startsWith('create')) return false;
52
+ const ownHook = `use${hookName.slice('create'.length)}`;
53
+ try {
54
+ const related = RELATED_HOOKS_RE.exec(fs.readFileSync(docPath, 'utf-8'));
55
+ return related != null && related[1].includes(`'${ownHook}'`);
56
+ } catch {
57
+ return false;
58
+ }
59
+ }
60
+
35
61
  /**
36
62
  * Extract the hook name from a doc filename.
37
63
  * e.g. 'useFocusTrap.doc.mjs' → 'useFocusTrap'
@@ -70,7 +96,8 @@ export function discoverHooks(coreDir) {
70
96
  }
71
97
  }
72
98
 
73
- // 2. Scan ALL component directories for files matching use*.doc.mjs
99
+ // 2. Scan ALL component directories for hook docs: use*.doc.mjs, and
100
+ // create*.doc.mjs for a factory documented as a hook (createReorderable)
74
101
  if (pathExists(srcDir)) {
75
102
  const topEntries = listDirEntries(srcDir);
76
103
  for (const entry of topEntries) {
@@ -120,14 +147,10 @@ function scanDirForHookDocs(dirPath, hookCategories) {
120
147
  for (const entry of entries) {
121
148
  if (entry.isDirectory() && !SKIP_DIRS.has(entry.name)) {
122
149
  scanDirForHookDocs(path.join(dirPath, entry.name), hookCategories);
123
- } else if (
124
- entry.isFile() &&
125
- entry.name.startsWith('use') &&
126
- entry.name.endsWith('.doc.mjs')
127
- ) {
150
+ } else if (entry.isFile() && HOOK_DOC_FILE_RE.test(entry.name)) {
128
151
  const hookName = hookNameFromFile(entry.name);
129
- if (!hookCategories.has(hookName)) {
130
- const docPath = path.join(dirPath, entry.name);
152
+ const docPath = path.join(dirPath, entry.name);
153
+ if (!hookCategories.has(hookName) && !isBesideOwnHook(docPath, hookName)) {
131
154
  const {category} = readHookMeta(docPath);
132
155
  hookCategories.set(hookName, category || 'Other');
133
156
  }
@@ -17,7 +17,11 @@ export function importDocModule(file: string): Promise<any>;
17
17
  export function importTemplateModule(file: string): Promise<any>;
18
18
  /**
19
19
  * Import a module natively, by file URL: how the docs API, search, and the
20
- * unchecked component and hook readers have always imported docs.
20
+ * unchecked component and hook readers have always imported docs. A `.ts`
21
+ * file goes through jiti, as {@link importDocModule} imports it: Node cannot
22
+ * strip types from a file under node_modules, and below 22.18 it does not
23
+ * strip types at all, so a plain import of an installed or linked
24
+ * integration's `.doc.ts` fails.
21
25
  * @param {string} file
22
26
  * @returns {Promise<any>}
23
27
  */
@@ -24,7 +24,12 @@
24
24
 
25
25
  import {pathToFileURL} from 'node:url';
26
26
  import {createJiti} from 'jiti';
27
- import {importUserModule} from '../fs/module-loader.mjs';
27
+ import {importUrl} from '../fs/import-url.mjs';
28
+ import {
29
+ importUserModule,
30
+ readFreshInTurn,
31
+ readInTurn,
32
+ } from '../fs/module-loader.mjs';
28
33
  import {fileStamp, recordContentRead} from '../fs/read-record.mjs';
29
34
 
30
35
  /** @type {ReturnType<typeof createJiti> | undefined} */
@@ -78,6 +83,28 @@ function freshUrl(file) {
78
83
  return url.href;
79
84
  }
80
85
 
86
+ /**
87
+ * Import `file` from `url`, in its turn with every other read of the file in
88
+ * this process (`readInTurn`, ../fs/module-loader.mjs).
89
+ * @param {string} file
90
+ * @param {string} url
91
+ * @returns {Promise<any>}
92
+ */
93
+ function importInTurn(file, url) {
94
+ return readInTurn(file, () => importUrl(url));
95
+ }
96
+
97
+ /**
98
+ * Import `file` again, for a change, as a new module. Node chooses its format
99
+ * as for the first import. The read takes its turn without the file's
100
+ * CommonJS cache entry (`readFreshInTurn`, ../fs/module-loader.mjs).
101
+ * @param {string} file
102
+ * @returns {Promise<any>}
103
+ */
104
+ function importFresh(file) {
105
+ return readFreshInTurn(file, () => importUrl(freshUrl(file)));
106
+ }
107
+
81
108
  /**
82
109
  * Import a component, hook, topic, or self-doc module: `.ts` through jiti,
83
110
  * anything else natively.
@@ -105,28 +132,35 @@ export function importTemplateModule(file) {
105
132
  jsxJiti ??= createJiti(import.meta.url, {jsx: true});
106
133
  return jsxJiti.import(first);
107
134
  }
108
- return import(`file://${first}`);
135
+ return importInTurn(first, `file://${first}`);
109
136
  },
110
137
  changed =>
111
138
  changed.endsWith('.ts')
112
139
  ? createJiti(import.meta.url, {jsx: true, moduleCache: false}).import(
113
140
  changed,
114
141
  )
115
- : import(freshUrl(changed)),
142
+ : importFresh(changed),
116
143
  );
117
144
  }
118
145
 
119
146
  /**
120
147
  * Import a module natively, by file URL: how the docs API, search, and the
121
- * unchecked component and hook readers have always imported docs.
148
+ * unchecked component and hook readers have always imported docs. A `.ts`
149
+ * file goes through jiti, as {@link importDocModule} imports it: Node cannot
150
+ * strip types from a file under node_modules, and below 22.18 it does not
151
+ * strip types at all, so a plain import of an installed or linked
152
+ * integration's `.doc.ts` fails.
122
153
  * @param {string} file
123
154
  * @returns {Promise<any>}
124
155
  */
125
156
  export function importNativeModule(file) {
157
+ if (file.endsWith('.ts')) {
158
+ return importDocModule(file);
159
+ }
126
160
  return importCurrent(
127
161
  file,
128
162
  'native',
129
- first => import(pathToFileURL(first).href),
130
- changed => import(freshUrl(changed)),
163
+ first => importInTurn(first, pathToFileURL(first).href),
164
+ changed => importFresh(changed),
131
165
  );
132
166
  }
@@ -0,0 +1,20 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * @file The dynamic import of an authored module by its file URL.
6
+ *
7
+ * @input An absolute `file:` URL, with any query its caller adds to name a
8
+ * fresh module.
9
+ * @output The module's namespace object, as a plain `import()` gives it.
10
+ * @position Called by the doc importers (../doc-compiler/import.mjs). The
11
+ * node test project loads this file natively, as the CLI does
12
+ * (vitest.config.ts). The doc files that a catalog command imports then load
13
+ * natively as well, without a request to the test runner for each of them.
14
+ */
15
+ /**
16
+ * Import the module at `url`.
17
+ * @param {string} url an absolute `file:` URL
18
+ * @returns {Promise<any>}
19
+ */
20
+ export function importUrl(url: string): Promise<any>;
@@ -0,0 +1,22 @@
1
+ // Copyright (c) Meta Platforms, Inc. and affiliates.
2
+
3
+ /**
4
+ * @file The dynamic import of an authored module by its file URL.
5
+ *
6
+ * @input An absolute `file:` URL, with any query its caller adds to name a
7
+ * fresh module.
8
+ * @output The module's namespace object, as a plain `import()` gives it.
9
+ * @position Called by the doc importers (../doc-compiler/import.mjs). The
10
+ * node test project loads this file natively, as the CLI does
11
+ * (vitest.config.ts). The doc files that a catalog command imports then load
12
+ * natively as well, without a request to the test runner for each of them.
13
+ */
14
+
15
+ /**
16
+ * Import the module at `url`.
17
+ * @param {string} url an absolute `file:` URL
18
+ * @returns {Promise<any>}
19
+ */
20
+ export function importUrl(url) {
21
+ return import(url);
22
+ }
@@ -4,9 +4,9 @@
4
4
  /**
5
5
  * Import a user-authored module. `.ts` is loaded via jiti; `.mjs`/`.js` use
6
6
  * native dynamic import for ordinary cached reads. A fresh `.ts` read uses a
7
- * no-cache jiti instance. A fresh CommonJS `.js` read evicts and reloads through
8
- * `require`; an ESM `.js` or `.mjs` read uses a cache-busted file URL. The
9
- * nearest package.json `type` decides `.js`, matching Node's package scopes.
7
+ * no-cache jiti instance. Any other fresh read drops the module from the
8
+ * require cache and imports a cache-busted file URL. Node then chooses its
9
+ * format as for the first read, so a fresh read gives what a first read gives.
10
10
  * Normal loads retain module caching. `fresh` rereads a file that changed
11
11
  * during this process: one a codemod rewrote, or an edited doc file (the doc
12
12
  * importers in ../doc-compiler/import.mjs read each change once).
@@ -29,6 +29,27 @@ export function importUserModule(file: string, { fresh }?: {
29
29
  * @returns {Promise<Record<string, unknown>>}
30
30
  */
31
31
  export function importThemeArtifact(specifier: string, cwd: string): Promise<Record<string, unknown>>;
32
+ /**
33
+ * Run `read`, an import of `file`, once the imports of `file` started before
34
+ * it have ended. Imports of other files do not wait. A fresh read removes the
35
+ * file from Node's CommonJS cache. A CommonJS import still in progress needs
36
+ * that entry until it ends.
37
+ * @template T
38
+ * @param {string} file absolute path
39
+ * @param {() => Promise<T>} read
40
+ * @returns {Promise<T>}
41
+ */
42
+ export function readInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
43
+ /**
44
+ * Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
45
+ * once the file is gone from Node's CommonJS cache. A new URL names a new ES
46
+ * module only, so a CommonJS module also needs its cache entry gone.
47
+ * @template T
48
+ * @param {string} file absolute path
49
+ * @param {() => Promise<T>} read
50
+ * @returns {Promise<T>}
51
+ */
52
+ export function readFreshInTurn<T>(file: string, read: () => Promise<T>): Promise<T>;
32
53
  /**
33
54
  * Import a user-authored module, take its default export, and validate it
34
55
  * through an authoring parser. This is the single load/validation boundary for
@@ -40,30 +40,6 @@ function getJiti() {
40
40
  return jitiInstance;
41
41
  }
42
42
 
43
- /**
44
- * Node treats `.js` as ESM only inside the nearest package scope whose
45
- * package.json declares `"type": "module"`; no package.json defaults to
46
- * CommonJS.
47
- * @param {string} file
48
- * @returns {boolean}
49
- */
50
- function isCommonJsFile(file) {
51
- let dir = path.dirname(file);
52
- for (;;) {
53
- const packageJson = path.join(dir, 'package.json');
54
- if (fs.existsSync(packageJson)) {
55
- try {
56
- return JSON.parse(fs.readFileSync(packageJson, 'utf-8')).type !== 'module';
57
- } catch {
58
- return true;
59
- }
60
- }
61
- const parent = path.dirname(dir);
62
- if (parent === dir) return true;
63
- dir = parent;
64
- }
65
- }
66
-
67
43
  let loadsInFlight = 0;
68
44
  /** @type {typeof process.stdout.write | undefined} */
69
45
  let realStdoutWrite;
@@ -104,9 +80,9 @@ async function withStdoutOnStderr(load) {
104
80
  /**
105
81
  * Import a user-authored module. `.ts` is loaded via jiti; `.mjs`/`.js` use
106
82
  * native dynamic import for ordinary cached reads. A fresh `.ts` read uses a
107
- * no-cache jiti instance. A fresh CommonJS `.js` read evicts and reloads through
108
- * `require`; an ESM `.js` or `.mjs` read uses a cache-busted file URL. The
109
- * nearest package.json `type` decides `.js`, matching Node's package scopes.
83
+ * no-cache jiti instance. Any other fresh read drops the module from the
84
+ * require cache and imports a cache-busted file URL. Node then chooses its
85
+ * format as for the first read, so a fresh read gives what a first read gives.
110
86
  * Normal loads retain module caching. `fresh` rereads a file that changed
111
87
  * during this process: one a codemod rewrote, or an edited doc file (the doc
112
88
  * importers in ../doc-compiler/import.mjs read each change once).
@@ -251,31 +227,77 @@ async function importModule(file, fresh) {
251
227
  if (fresh && file.endsWith('.ts')) {
252
228
  return await createJiti(import.meta.url, {moduleCache: false}).import(file);
253
229
  }
254
- if (fresh && file.endsWith('.js') && isCommonJsFile(file)) {
255
- const resolved = requireFromHere.resolve(file);
256
- delete requireFromHere.cache[resolved];
257
- const value = requireFromHere(file);
258
- if (
259
- value != null &&
260
- typeof value === 'object' &&
261
- value.__esModule === true &&
262
- Object.prototype.hasOwnProperty.call(value, 'default')
263
- ) {
264
- return value;
265
- }
266
- return {
267
- ...(value != null && typeof value === 'object' ? value : {}),
268
- default: value,
269
- };
270
- }
271
230
  if (file.endsWith('.ts')) {
272
231
  return await getJiti().import(file);
273
232
  }
274
233
  const url = pathToFileURL(file);
275
- if (fresh) {
276
- url.searchParams.set('astryx-fresh', String(++freshImportNonce));
234
+ if (!fresh) return await readInTurn(file, () => import(url.href));
235
+ // Node chooses the format again, as for the first import: a package's
236
+ // "type", the extension, or the source's syntax. The query names a new ES
237
+ // module. A CommonJS module also needs its cache entry gone.
238
+ url.searchParams.set('astryx-fresh', String(++freshImportNonce));
239
+ return await readFreshInTurn(file, () => import(url.href));
240
+ }
241
+
242
+ /**
243
+ * The name Node's CommonJS cache gives `file`: its real path, so a link and
244
+ * its target name one entry. A file that does not resolve keeps its path.
245
+ * @param {string} file absolute path
246
+ * @returns {string}
247
+ */
248
+ function cacheKey(file) {
249
+ try {
250
+ return requireFromHere.resolve(file);
251
+ } catch {
252
+ return file;
277
253
  }
278
- return await import(url.href);
254
+ }
255
+
256
+ /**
257
+ * Each file's latest read, by its cache name, settled once that read ends.
258
+ * @type {Map<string, Promise<void>>}
259
+ */
260
+ const readsInProgress = new Map();
261
+
262
+ /**
263
+ * Run `read`, an import of `file`, once the imports of `file` started before
264
+ * it have ended. Imports of other files do not wait. A fresh read removes the
265
+ * file from Node's CommonJS cache. A CommonJS import still in progress needs
266
+ * that entry until it ends.
267
+ * @template T
268
+ * @param {string} file absolute path
269
+ * @param {() => Promise<T>} read
270
+ * @returns {Promise<T>}
271
+ */
272
+ export function readInTurn(file, read) {
273
+ const key = cacheKey(file);
274
+ const before = readsInProgress.get(key) ?? Promise.resolve();
275
+ const result = before.then(read);
276
+ const ended = result.then(
277
+ () => {},
278
+ () => {},
279
+ );
280
+ readsInProgress.set(key, ended);
281
+ void ended.then(() => {
282
+ if (readsInProgress.get(key) === ended) readsInProgress.delete(key);
283
+ });
284
+ return result;
285
+ }
286
+
287
+ /**
288
+ * Run `read`, a fresh import of `file`, in the file's turn (`readInTurn`),
289
+ * once the file is gone from Node's CommonJS cache. A new URL names a new ES
290
+ * module only, so a CommonJS module also needs its cache entry gone.
291
+ * @template T
292
+ * @param {string} file absolute path
293
+ * @param {() => Promise<T>} read
294
+ * @returns {Promise<T>}
295
+ */
296
+ export function readFreshInTurn(file, read) {
297
+ return readInTurn(file, () => {
298
+ delete requireFromHere.cache[cacheKey(file)];
299
+ return read();
300
+ });
279
301
  }
280
302
 
281
303
 
@@ -0,0 +1,59 @@
1
+ // @generated by scripts/sync-api-types.mjs from the JSDoc in foundation/**/*.mjs.
2
+ // DO NOT EDIT — run `pnpm sync:api-types` to regenerate.
3
+
4
+ /**
5
+ * The size a ResponsiveContainer starts at: its own numbers where it has
6
+ * them. Otherwise the width follows the height: six times it for a
7
+ * sparkline, which sits in a narrow card or a table cell, and two and a half
8
+ * times it for a taller chart. The drawing then scales to the width the
9
+ * page gives it, so its text stays near its own size in most layouts.
10
+ * @param {{width?: unknown, height?: unknown, aspect?: unknown}} props
11
+ * @returns {{width: number, height: number}}
12
+ */
13
+ export function chartSize({ width, height, aspect }: {
14
+ width?: unknown;
15
+ height?: unknown;
16
+ aspect?: unknown;
17
+ }): {
18
+ width: number;
19
+ height: number;
20
+ };
21
+ /**
22
+ * Mark each container chart whose legend is vertical and on one side with
23
+ * that side, and whether it sits at the bottom or the middle, for
24
+ * {@link CHART_CSS}. A vertically centered legend has a `top` near the
25
+ * middle (no room is kept for it in a container chart). A vertical legend
26
+ * centered across takes its own row, as a horizontal one does.
27
+ * @param {any} root the element the charts were drawn in
28
+ */
29
+ export function markLegends(root: any): void;
30
+ /**
31
+ * Give this thread the small document a chart draws in: no layout, no
32
+ * motion, and no frames to wait for.
33
+ * @returns {Promise<void>}
34
+ */
35
+ export function chartDocument(): Promise<void>;
36
+ /**
37
+ * @file Charts in a rendered page. A recharts chart draws only after its
38
+ * effects run, and a ResponsiveContainer measures its parent in a browser, so
39
+ * a server render leaves them empty. The render worker draws each one again
40
+ * in a small document, at the size the container starts at, and puts the
41
+ * drawing in its place.
42
+ *
43
+ * @input The chart elements the server render set aside.
44
+ * @output Their markup, and the CSS that lets a drawn chart fit its width.
45
+ * @position foundation/render — read by page-worker.mjs and render-page.mjs.
46
+ */
47
+ /** The global the render worker installs to set charts aside. SYNC: page-worker.mjs assigns it by this name. */
48
+ export const CHART_GLOBAL: "__astryxRenderChart";
49
+ /** The recharts exports that draw a chart, besides ResponsiveContainer. */
50
+ export const CHART_ROOTS: Set<string>;
51
+ /**
52
+ * A chart drawn at its starting size scales to the width the page gives it
53
+ * and keeps its shape, so it never overflows its card. Its legend stays out
54
+ * of the scaled picture, so its text never covers the chart however narrow
55
+ * the page is. A horizontal legend takes its own row under the picture, or
56
+ * above it when it asks for the top. A vertical legend on one side sits
57
+ * beside the picture, on that side.
58
+ */
59
+ export const CHART_CSS: string;