@impetik/xeer-mcp 0.2.19 → 0.2.21

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.
@@ -16,6 +16,7 @@
16
16
  * `"react-jsx"`, `lib` without the `lib.`/`.d.ts` affixes), which is what
17
17
  * `ts.convertCompilerOptionsFromJson` reads.
18
18
  */
19
+ import { NORMALIZED_CLIENT_RUNTIME, clientImportPolicy, isImportableClassification, } from './import-policy.js';
19
20
  export const PLATFORM_TYPE_CHECK_COMPILER_OPTIONS = Object.freeze({
20
21
  allowJs: true,
21
22
  allowArbitraryExtensions: true,
@@ -35,21 +36,148 @@ export const PLATFORM_TYPE_CHECK_COMPILER_OPTIONS = Object.freeze({
35
36
  verbatimModuleSyntax: true,
36
37
  });
37
38
  /**
38
- * The React-family editor mappings every scaffolded and template `tsconfig.json` carries. Each
39
- * target is a shim inside the installed platform package that re-exports the platform's
40
- * `preact/compat` surface, so the mapping resolves under npm hoisting and pnpm strictness alike.
39
+ * Where the platform package keeps the editor shims of one provider, relative to `dist/`.
40
+ *
41
+ * Published layout, not an implementation detail: every scaffolded `tsconfig.json` names these
42
+ * directories, so renaming one rewrites every application's file. `compat` is the Preact provider's
43
+ * because that is where the React-family compatibility shims have always lived.
44
+ */
45
+ const PROVIDER_SHIM_DIRECTORIES = Object.freeze({
46
+ preact: 'compat',
47
+ react: 'react',
48
+ });
49
+ /**
50
+ * The module inside the platform package each provider's client adapter is published as, relative to
51
+ * `dist/` and without an extension — the target of the `./client/<provider>` export.
52
+ */
53
+ const PROVIDER_CLIENT_ADAPTER_MODULES = Object.freeze({
54
+ preact: 'client',
55
+ react: 'client-react',
56
+ });
57
+ /** How a `paths` row names a module inside the installed platform package. */
58
+ function installedPlatformModule(module) {
59
+ return `./node_modules/@impetik/xeer/dist/${module}`;
60
+ }
61
+ /**
62
+ * The shim file name one specifier's row points at. Derived from the specifier so that the table and
63
+ * the generator agree by construction rather than by two lists matching: `react-dom/server.browser`
64
+ * is `react-dom-server-browser`, and nothing anywhere else decides that.
65
+ */
66
+ function shimModuleName(specifier) {
67
+ return specifier.replaceAll('/', '-').replaceAll('.', '-');
68
+ }
69
+ /**
70
+ * Every renderer mapping one provider's editor needs, derived from the family descriptor.
71
+ *
72
+ * Two rules, and both are the descriptor's rather than anyone's memory. **Completeness**: every
73
+ * specifier the import policy admits from the renderer family gets a row, alias keys included,
74
+ * because the policy is what decides whether an application may write the import and this table is
75
+ * what decides whether its editor can read it. Five hand-kept rows against twelve admitted
76
+ * specifiers is how `react-dom/server` came to be a clean `xeer check` beside a TS2307 in the same
77
+ * file, and ten unmapped `preact/*` entrypoints were the same defect one provider over (#277).
78
+ * **Honesty**: an entrypoint whose declarations exist nowhere gets no row, so both tools refuse it
79
+ * by construction — `scheduler` and `react-dom/profiling` under React, `preact/compat/scheduler` and
80
+ * the two `compat/server` spellings under Preact. A row inventing types for one of those would make
81
+ * an editor accept what `xeer check` rejects, which is the same divergence pointed the other way.
82
+ */
83
+ export function rendererEditorRows(provider) {
84
+ const directory = PROVIDER_SHIM_DIRECTORIES[provider];
85
+ const rows = [];
86
+ const add = (specifier, entrypoint, family) => {
87
+ const described = family.entrypoints.get(entrypoint);
88
+ if (!described || !isImportableClassification(described.classification))
89
+ return;
90
+ if (described.types.kind === 'none')
91
+ return;
92
+ rows.push({
93
+ specifier,
94
+ entrypoint,
95
+ types: described.types,
96
+ module: `${directory}/${shimModuleName(specifier)}`,
97
+ });
98
+ };
99
+ for (const family of clientImportPolicy(provider).managedFamilies) {
100
+ for (const specifier of family.entrypoints.keys())
101
+ add(specifier, specifier, family);
102
+ for (const [alias, target] of family.compatAliases)
103
+ add(alias, target, family);
104
+ }
105
+ return rows;
106
+ }
107
+ /**
108
+ * The `@impetik/xeer/*` subpaths whose *module* the provider chooses, mapped for the editor.
109
+ *
110
+ * The platform package publishes one module for each of these under every provider, and it is the
111
+ * bundler that picks which renderer they mean: the `client` facade, and the JSX runtime with its dev
112
+ * twin. The package's own `exports` map answers with the default provider's module, so only a
113
+ * non-default provider needs rows — which is also why an untouched Preact application's file is
114
+ * byte-for-byte what it was. Without them a React application's editor would type every element
115
+ * against Preact's JSX namespace and its `mount`/`useQuery` against the Preact adapter while the
116
+ * artifact ran React: green on the wrong evidence, the exact failure this profile exists to prevent
117
+ * (#248).
118
+ *
119
+ * The facade row names a published module rather than a generated shim — `./client/<provider>` is a
120
+ * real export, refused as an application import so that no source file decides the renderer. The JSX
121
+ * rows point at the very shim the family's own JSX entrypoints resolve to, so one name cannot mean
122
+ * two files.
123
+ */
124
+ function providerFacadeRows(provider) {
125
+ const rows = new Map();
126
+ if (provider === NORMALIZED_CLIENT_RUNTIME.provider)
127
+ return rows;
128
+ rows.set('@impetik/xeer/client', PROVIDER_CLIENT_ADAPTER_MODULES[provider]);
129
+ const shims = new Map(rendererEditorRows(provider).map((row) => [row.specifier, row.module]));
130
+ for (const family of clientImportPolicy(provider).managedFamilies) {
131
+ for (const [subpath, entrypoint] of [
132
+ ['@impetik/xeer/jsx-runtime', family.jsx.runtime],
133
+ ['@impetik/xeer/jsx-dev-runtime', family.jsx.devRuntime],
134
+ ]) {
135
+ const shim = shims.get(entrypoint);
136
+ if (shim)
137
+ rows.set(subpath, shim);
138
+ }
139
+ }
140
+ return rows;
141
+ }
142
+ /**
143
+ * The editor's resolution table for one UI provider — the `paths` block of a scaffolded
144
+ * `tsconfig.json`.
41
145
  *
42
146
  * Not part of {@link PLATFORM_TYPE_CHECK_COMPILER_OPTIONS}: the platform check answers the same
43
147
  * question through its module-resolution host instead, so this is the editor's half of one rule
44
148
  * rather than a second rule.
149
+ *
150
+ * Every target is a module *inside* the installed platform package, because both renderers are
151
+ * platform-sourced: applications never declare them, and only a path inside the installed package
152
+ * resolves under npm hoisting and pnpm strictness alike. One rule binds a row to the file it names —
153
+ * a mapping rewrites *every* matching specifier in the program, including the ones written in the
154
+ * file it points at, so the file a row names may never import that row's own specifier. It would
155
+ * resolve back onto itself and answer with an empty module: an editor with no renderer types at all.
156
+ * The generator in the CLI package is what keeps that true, and `editor-resolution.test.ts` there
157
+ * type-checks a scaffolded project through its own `tsconfig.json` to prove it.
158
+ *
159
+ * Keys are sorted, so the file's bytes depend on which rows exist rather than on the order two
160
+ * declarations happen to be written in.
45
161
  */
46
- export const REACT_COMPAT_TSCONFIG_PATHS = Object.freeze({
47
- 'react': ['./node_modules/@impetik/xeer/dist/compat/react'],
48
- 'react-dom': ['./node_modules/@impetik/xeer/dist/compat/react-dom'],
49
- 'react-dom/client': ['./node_modules/@impetik/xeer/dist/compat/react-dom-client'],
50
- 'react/jsx-runtime': ['./node_modules/@impetik/xeer/dist/compat/react-jsx-runtime'],
51
- 'react/jsx-dev-runtime': ['./node_modules/@impetik/xeer/dist/compat/react-jsx-dev-runtime'],
52
- });
162
+ export function tsconfigPathsFor(provider) {
163
+ const rows = new Map(providerFacadeRows(provider));
164
+ for (const row of rendererEditorRows(provider))
165
+ rows.set(row.specifier, row.module);
166
+ return Object.freeze(Object.fromEntries([...rows.keys()].sort()
167
+ .map((specifier) => [specifier, Object.freeze([installedPlatformModule(rows.get(specifier))])])));
168
+ }
169
+ /**
170
+ * The editor mappings a `tsconfig.json` carries under the **Preact** provider: the React-family
171
+ * names an application may write, aliased onto the platform's `preact/compat` surface, and Preact's
172
+ * own admitted entrypoints, which an application may equally write and whose editor rows were
173
+ * missing until #277.
174
+ */
175
+ export const REACT_COMPAT_TSCONFIG_PATHS = tsconfigPathsFor('preact');
176
+ /**
177
+ * The same mappings under the **React** provider, pointed at the renderer itself rather than at a
178
+ * compatibility layer — which is the whole point of selecting the provider.
179
+ */
180
+ export const REACT_PROVIDER_TSCONFIG_PATHS = tsconfigPathsFor('react');
53
181
  /**
54
182
  * The files an application's `tsconfig.json` type-checks: sources, TypeScript tests, and the
55
183
  * generated contract.
@@ -69,10 +197,14 @@ export const APPLICATION_TSCONFIG_INCLUDE = Object.freeze(['src', 'tests/**/*.ts
69
197
  * bytes. Serialized here rather than returned as an object because "the file the editor reads" is
70
198
  * what has to match, and two callers stringifying the same object with different options would
71
199
  * produce two different files.
200
+ *
201
+ * The provider changes `paths` and nothing else. Every rule the platform enforces is identical
202
+ * across providers — the choice is about which renderer a name resolves to, never about how strictly
203
+ * the code is judged — and the default keeps a Preact application's file byte-for-byte what it was.
72
204
  */
73
- export function applicationTsconfigDocument() {
205
+ export function applicationTsconfigDocument(provider = 'preact') {
74
206
  return `${JSON.stringify({
75
- compilerOptions: { ...PLATFORM_TYPE_CHECK_COMPILER_OPTIONS, paths: REACT_COMPAT_TSCONFIG_PATHS },
207
+ compilerOptions: { ...PLATFORM_TYPE_CHECK_COMPILER_OPTIONS, paths: tsconfigPathsFor(provider) },
76
208
  include: APPLICATION_TSCONFIG_INCLUDE,
77
209
  }, null, 2)}\n`;
78
210
  }
@@ -1,3 +1,4 @@
1
+ import type { ClientRuntime, ClientRuntimeProvider, ClientRuntimeSource } from './import-policy.js';
1
2
  import type { PublicAssetsV0 } from './public-assets.js';
2
3
  export declare const SOURCE_FORMAT: "xeer.application-source.v0";
3
4
  export declare const ARTIFACT_FORMAT: "xeer.application.v0";
@@ -31,6 +32,26 @@ export interface Diagnostic {
31
32
  */
32
33
  causedBy?: string;
33
34
  }
35
+ /**
36
+ * Whether a set of diagnostics refuses the command that produced it.
37
+ *
38
+ * `ok` is "no error", never "no diagnostic": a warning is the platform telling a builder something
39
+ * while still doing the work, and a command that failed on one would make every advisory a breaking
40
+ * change. The rule lives here because `check`, `build`, and `doctor` each decide it about the same
41
+ * diagnostics, and three copies of it is how one of them ends up refusing a warning the other two
42
+ * report and keep going past.
43
+ */
44
+ export declare function refusesCompletion(diagnostics: readonly Diagnostic[]): boolean;
45
+ /**
46
+ * The members of a set that refuse it — the same rule as {@link refusesCompletion}, asked for the
47
+ * subset rather than the verdict.
48
+ *
49
+ * A consumer that *reports* a refusal needs the boolean; a consumer that *shows* one needs to know
50
+ * which diagnostics it is showing, because a set that mixes a refusal with an advisory is the normal
51
+ * case rather than the odd one. Deriving the verdict from the subset is what stops the two answers
52
+ * from ever disagreeing about the same set.
53
+ */
54
+ export declare function refusingDiagnostics(diagnostics: readonly Diagnostic[]): readonly Diagnostic[];
34
55
  /**
35
56
  * `ref` is a scalar in the only sense that matters here: it is one TEXT column holding one row id.
36
57
  * What distinguishes it is that the column carries a FOREIGN KEY to the declared table it names, so
@@ -182,15 +203,25 @@ export interface ApplicationManifestV0 {
182
203
  storage?: StorageConfigV0;
183
204
  capabilities?: Capability[];
184
205
  /**
185
- * The client runtime selection (#212). Optional, and closed: only implemented provider/source
186
- * combinations are accepted, and omission normalizes to `{ provider: "preact", source:
187
- * "platform" }` with identical semantics and build output. Declaring it is a statement of the
188
- * default, not a choice — a future app-owned source would be `"application"`, not `"user"`.
206
+ * Installed dependencies acknowledged as shipping no TypeScript declarations; see the schema
207
+ * module, where the rule and its reasoning live. Package names, one per package: the acknowledgment
208
+ * covers every subpath of the package it names.
209
+ */
210
+ untypedDependencies?: string[];
211
+ /**
212
+ * The client runtime selection (#212, #214). Optional, and closed: only implemented
213
+ * provider/source combinations are accepted, and omission normalizes to `{ provider: "preact",
214
+ * source: "platform" }` with identical semantics and build output.
215
+ *
216
+ * The provider is the one line that differs between a Preact application and a React one:
217
+ * `jsxImportSource` stays `@impetik/xeer` and the sources stay byte-identical, because the import
218
+ * policy — not the application — decides what the platform's JSX runtime and client facade
219
+ * resolve to. A future app-owned renderer would be `source: "application"`, not `"user"`.
189
220
  */
190
221
  client?: {
191
222
  runtime?: {
192
- provider: 'preact';
193
- source?: 'platform';
223
+ provider: ClientRuntimeProvider;
224
+ source?: ClientRuntimeSource;
194
225
  };
195
226
  };
196
227
  budgets?: {
@@ -243,12 +274,19 @@ export interface NormalizedApplicationManifestV0 {
243
274
  */
244
275
  storage: NormalizedStorageConfigV0 | null;
245
276
  capabilities: Capability[];
246
- /** Always present once normalized: omission and the explicit default are the same statement. */
277
+ /**
278
+ * Sorted and deduplicated, `[]` when the manifest declares none. Deliberately absent from the
279
+ * artifact payload: an acknowledgment is type-space only, so it must not move an `artifactId`.
280
+ */
281
+ untypedDependencies: string[];
282
+ /**
283
+ * Always present once normalized: omission and the explicit default are the same statement. What
284
+ * is *declared*, however, is carried through rather than defaulted away — this block is where the
285
+ * import policy, the type-resolution profile, and the artifact's provenance all read the
286
+ * application's provider from.
287
+ */
247
288
  client: {
248
- runtime: {
249
- provider: 'preact';
250
- source: 'platform';
251
- };
289
+ runtime: ClientRuntime;
252
290
  };
253
291
  budgets: {
254
292
  queryRows: number;
@@ -2,6 +2,30 @@ export const SOURCE_FORMAT = 'xeer.application-source.v0';
2
2
  export const ARTIFACT_FORMAT = 'xeer.application.v0';
3
3
  export const DEV_PROTOCOL = 'xeer.dev.v0';
4
4
  export const INSPECT_PROTOCOL = 'xeer.inspect.v0';
5
+ /**
6
+ * Whether a set of diagnostics refuses the command that produced it.
7
+ *
8
+ * `ok` is "no error", never "no diagnostic": a warning is the platform telling a builder something
9
+ * while still doing the work, and a command that failed on one would make every advisory a breaking
10
+ * change. The rule lives here because `check`, `build`, and `doctor` each decide it about the same
11
+ * diagnostics, and three copies of it is how one of them ends up refusing a warning the other two
12
+ * report and keep going past.
13
+ */
14
+ export function refusesCompletion(diagnostics) {
15
+ return refusingDiagnostics(diagnostics).length !== 0;
16
+ }
17
+ /**
18
+ * The members of a set that refuse it — the same rule as {@link refusesCompletion}, asked for the
19
+ * subset rather than the verdict.
20
+ *
21
+ * A consumer that *reports* a refusal needs the boolean; a consumer that *shows* one needs to know
22
+ * which diagnostics it is showing, because a set that mixes a refusal with an advisory is the normal
23
+ * case rather than the odd one. Deriving the verdict from the subset is what stops the two answers
24
+ * from ever disagreeing about the same set.
25
+ */
26
+ export function refusingDiagnostics(diagnostics) {
27
+ return diagnostics.filter((diagnostic) => diagnostic.severity === 'error');
28
+ }
5
29
  /**
6
30
  * The capabilities an application may declare. A capability name in `capabilities[]` and its config
7
31
  * block are one declaration in two halves: either both are present or neither is, checked in both