@impetik/xeer-mcp 0.2.18 → 0.2.20

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.
@@ -15,15 +15,33 @@
15
15
  * source, and vendoring folds it into the published tarball with everything else.
16
16
  */
17
17
  /**
18
- * The normalized client runtime: the only implemented provider/source combination. `client.runtime`
19
- * in the manifest is optional and omission normalizes to exactly this value, so declaring it is a
20
- * statement of the default rather than a choice.
18
+ * The UI providers the platform implements, in the spelling `client.runtime.provider` uses.
19
+ *
20
+ * A provider is a whole renderer family — packages, entrypoint classification, JSX runtimes, and
21
+ * the compat aliases the family does or does not offer — and the client zone's policy is a pure
22
+ * function of which one the manifest declares ({@link clientImportPolicy}). Adding a provider is
23
+ * adding a family and a case, never a flag threaded through the consumers: what a specifier means
24
+ * on the client has exactly one answer per provider, stated here.
25
+ */
26
+ export const CLIENT_RUNTIME_PROVIDERS = Object.freeze(['preact', 'react']);
27
+ /**
28
+ * The default client runtime. `client.runtime` in the manifest is optional and omission normalizes
29
+ * to exactly this value, so an application that says nothing gets the Preact provider it always
30
+ * had — byte-for-byte, down to the artifact identity.
31
+ *
32
+ * The *normalized* runtime of a given application is whatever its manifest declares; this is only
33
+ * the value omission means. Provenance and compiler facts must derive from the manifest's own
34
+ * runtime through {@link clientRuntimeId}, never from this constant.
21
35
  */
22
36
  export const NORMALIZED_CLIENT_RUNTIME = Object.freeze({
23
37
  provider: 'preact',
24
38
  source: 'platform',
25
39
  });
26
- /** The normalized client runtime as compiler facts and artifact provenance spell it. */
40
+ /** How a client runtime is spelled in compiler facts and artifact provenance. */
41
+ export function clientRuntimeId(runtime) {
42
+ return `${runtime.provider}/${runtime.source}`;
43
+ }
44
+ /** The default client runtime as compiler facts and artifact provenance spell it. */
27
45
  export const CLIENT_RUNTIME_ID = `${NORMALIZED_CLIENT_RUNTIME.provider}/${NORMALIZED_CLIENT_RUNTIME.source}`;
28
46
  /**
29
47
  * Default and advisory budgets for the open client bundle, minified pre-gzip bytes of the single
@@ -33,38 +51,57 @@ export const CLIENT_RUNTIME_ID = `${NORMALIZED_CLIENT_RUNTIME.provider}/${NORMAL
33
51
  */
34
52
  export const CLIENT_BUNDLE_ERROR_BUDGET_BYTES = 1024 * 1024;
35
53
  export const CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES = 400 * 1024;
54
+ /** An entrypoint the anchor package ships declarations for, through its own `types` condition. */
55
+ function selfTyped(classification) {
56
+ return { classification, types: { kind: 'anchor' } };
57
+ }
58
+ /** An entrypoint whose declarations a `@types/*` package publishes, at `specifier`. */
59
+ function typedBy(classification, specifier) {
60
+ return { classification, types: { kind: 'types-package', specifier } };
61
+ }
62
+ /** An entrypoint no package publishes declarations for. */
63
+ function untyped(classification) {
64
+ return { classification, types: { kind: 'none' } };
65
+ }
36
66
  /**
37
- * Every public entrypoint of the platform Preact package, classified. The compiler's conformance
38
- * test compares this map against the installed package's `exports` map in both directions, so a
39
- * Preact upgrade that adds, removes, or renames an entrypoint fails until it is deliberately
40
- * classified here.
67
+ * Every public entrypoint of the platform Preact package: what it is for, and where its declarations
68
+ * live. The compiler's conformance test compares this map against the installed package's `exports`
69
+ * map in both directions, so a Preact upgrade that adds, removes, or renames an entrypoint fails
70
+ * until it is deliberately classified here — and a second test resolves every declared types source,
71
+ * so an upgrade that *moves* the declarations fails here rather than silently in an editor.
72
+ *
73
+ * Preact ships its own declarations, so almost every entrypoint is `anchor`. The exceptions are
74
+ * facts about the published package rather than judgements: `compat/server` and `compat/scheduler`
75
+ * publish no `types` condition at all, and `compat/server.browser` publishes one naming
76
+ * `./compat/server.d.ts`, a file the package does not contain. All three are `none`, which is why
77
+ * neither `xeer check` nor an editor answers them — the same refusal, from the same declaration.
41
78
  */
42
79
  const PREACT_ENTRYPOINTS = new Map([
43
- ['preact', 'runtime'],
44
- ['preact/hooks', 'runtime'],
45
- ['preact/jsx-runtime', 'runtime'],
46
- ['preact/jsx-dev-runtime', 'runtime'],
47
- ['preact/compat', 'runtime'],
48
- ['preact/compat/client', 'runtime'],
80
+ ['preact', selfTyped('runtime')],
81
+ ['preact/hooks', selfTyped('runtime')],
82
+ ['preact/jsx-runtime', selfTyped('runtime')],
83
+ ['preact/jsx-dev-runtime', selfTyped('runtime')],
84
+ ['preact/compat', selfTyped('runtime')],
85
+ ['preact/compat/client', selfTyped('runtime')],
49
86
  // Browser-safe server rendering: both resolve to the browser build under the client conditions.
50
- ['preact/compat/server', 'runtime'],
51
- ['preact/compat/server.browser', 'runtime'],
52
- ['preact/compat/jsx-runtime', 'runtime'],
53
- ['preact/compat/jsx-dev-runtime', 'runtime'],
54
- ['preact/compat/scheduler', 'runtime'],
87
+ ['preact/compat/server', untyped('runtime')],
88
+ ['preact/compat/server.browser', untyped('runtime')],
89
+ ['preact/compat/jsx-runtime', selfTyped('runtime')],
90
+ ['preact/compat/jsx-dev-runtime', selfTyped('runtime')],
91
+ ['preact/compat/scheduler', untyped('runtime')],
55
92
  // Side-effectful development surfaces: they mutate the shared renderer options, which is exactly
56
93
  // why they must be pinned to the same singleton as everything else.
57
- ['preact/debug', 'development'],
58
- ['preact/devtools', 'development'],
59
- ['preact/test-utils', 'test'],
60
- ['preact/compat/test-utils', 'test'],
61
- ['preact/package.json', 'metadata'],
62
- ['preact/compat/package.json', 'metadata'],
63
- ['preact/debug/package.json', 'metadata'],
64
- ['preact/devtools/package.json', 'metadata'],
65
- ['preact/hooks/package.json', 'metadata'],
66
- ['preact/test-utils/package.json', 'metadata'],
67
- ['preact/jsx-runtime/package.json', 'metadata'],
94
+ ['preact/debug', selfTyped('development')],
95
+ ['preact/devtools', selfTyped('development')],
96
+ ['preact/test-utils', selfTyped('test')],
97
+ ['preact/compat/test-utils', selfTyped('test')],
98
+ ['preact/package.json', untyped('metadata')],
99
+ ['preact/compat/package.json', untyped('metadata')],
100
+ ['preact/debug/package.json', untyped('metadata')],
101
+ ['preact/devtools/package.json', untyped('metadata')],
102
+ ['preact/hooks/package.json', untyped('metadata')],
103
+ ['preact/test-utils/package.json', untyped('metadata')],
104
+ ['preact/jsx-runtime/package.json', untyped('metadata')],
68
105
  ]);
69
106
  /**
70
107
  * The complete per-subpath React-family alias table. Every key is pinned to its target for every
@@ -85,7 +122,8 @@ const REACT_COMPAT_ALIASES = new Map([
85
122
  ]);
86
123
  /** The client renderer family under the Preact provider. */
87
124
  export const PREACT_RENDERER_FAMILY = Object.freeze({
88
- anchorPackage: 'preact',
125
+ provider: 'preact',
126
+ anchorPackages: Object.freeze(['preact']),
89
127
  packages: Object.freeze(['preact', 'react', 'react-dom', 'scheduler']),
90
128
  entrypoints: PREACT_ENTRYPOINTS,
91
129
  compatAliases: REACT_COMPAT_ALIASES,
@@ -95,8 +133,98 @@ export const PREACT_RENDERER_FAMILY = Object.freeze({
95
133
  devRuntime: 'preact/jsx-dev-runtime',
96
134
  }),
97
135
  });
136
+ /**
137
+ * Every public entrypoint of the platform React packages, classified — the same hand-maintained
138
+ * statement `PREACT_ENTRYPOINTS` makes, across the three packages React ships its client surface in,
139
+ * and gated by the same bidirectional conformance test per package.
140
+ *
141
+ * Two things are decided here that Preact never had to decide. The first is the `react-server`
142
+ * condition: every subpath publishes one, and the client zone is not a React Server Components
143
+ * environment, so resolution under `browser`/`import`/`module` deliberately never selects it. The
144
+ * second is that React publishes one subpath per *host runtime* — `server.node`, `server.bun`,
145
+ * `server.edge` and their `static` counterparts. Those are real, supported entrypoints holding bytes
146
+ * for a runtime the browser is not, so they are classified `other-runtime` rather than left
147
+ * unclassified: the difference is a diagnostic that explains itself against one that misinforms.
148
+ * The condition-switched `react-dom/server` and `react-dom/static` resolve to the browser build here
149
+ * and are admitted, exactly as `preact/compat/server` is.
150
+ *
151
+ * `scheduler` publishes no `exports` map at all, so nothing gates its classification the way the
152
+ * other two are gated; its conformance test pins the absence instead.
153
+ *
154
+ * React ships no declarations of its own, so every types source here is a `@types/*` subpath — and
155
+ * the two entrypoints DefinitelyTyped does not cover are stated as such rather than left out.
156
+ * `react-dom/profiling` has no `@types/react-dom` subpath and `scheduler` has no types package
157
+ * installed at all, which is why both tools refuse them and both are right.
158
+ */
159
+ const REACT_ENTRYPOINTS = new Map([
160
+ ['react', typedBy('runtime', '@types/react')],
161
+ ['react/jsx-runtime', typedBy('runtime', '@types/react/jsx-runtime')],
162
+ ['react/jsx-dev-runtime', typedBy('runtime', '@types/react/jsx-dev-runtime')],
163
+ // Emitted by the React Compiler in place of the hooks it rewrites; part of the runtime surface
164
+ // whether or not this application runs the compiler, because a dependency's build may have.
165
+ ['react/compiler-runtime', typedBy('runtime', '@types/react/compiler-runtime')],
166
+ ['react-dom', typedBy('runtime', '@types/react-dom')],
167
+ ['react-dom/client', typedBy('runtime', '@types/react-dom/client')],
168
+ // Browser-safe server rendering: both resolve to the browser build under the client conditions.
169
+ ['react-dom/server', typedBy('runtime', '@types/react-dom/server')],
170
+ ['react-dom/server.browser', typedBy('runtime', '@types/react-dom/server.browser')],
171
+ ['react-dom/static', typedBy('runtime', '@types/react-dom/static')],
172
+ ['react-dom/static.browser', typedBy('runtime', '@types/react-dom/static.browser')],
173
+ ['react-dom/server.bun', typedBy('other-runtime', '@types/react-dom/server.bun')],
174
+ ['react-dom/server.edge', typedBy('other-runtime', '@types/react-dom/server.edge')],
175
+ ['react-dom/server.node', typedBy('other-runtime', '@types/react-dom/server.node')],
176
+ ['react-dom/static.edge', typedBy('other-runtime', '@types/react-dom/static.edge')],
177
+ ['react-dom/static.node', typedBy('other-runtime', '@types/react-dom/static.node')],
178
+ // The profiling build swaps in an instrumented reconciler, which is exactly the kind of
179
+ // whole-renderer substitution that must stay pinned to the one instance.
180
+ ['react-dom/profiling', untyped('development')],
181
+ ['react-dom/test-utils', typedBy('test', '@types/react-dom/test-utils')],
182
+ ['scheduler', untyped('runtime')],
183
+ ['react/package.json', untyped('metadata')],
184
+ ['react-dom/package.json', untyped('metadata')],
185
+ ['scheduler/package.json', untyped('metadata')],
186
+ ]);
187
+ /**
188
+ * The client renderer family under the React provider: React itself, not a compatibility layer.
189
+ *
190
+ * No `compatAliases`, and the omission is the point. Under Preact the React-ecosystem specifiers are
191
+ * aliases onto `preact/compat` so a React library and the renderer stay one instance; under React
192
+ * they *are* the renderer, and an alias table would be a second name for the same file.
193
+ */
194
+ export const REACT_RENDERER_FAMILY = Object.freeze({
195
+ provider: 'react',
196
+ anchorPackages: Object.freeze(['react', 'react-dom', 'scheduler']),
197
+ packages: Object.freeze(['react', 'react-dom', 'scheduler']),
198
+ entrypoints: REACT_ENTRYPOINTS,
199
+ compatAliases: new Map(),
200
+ jsx: Object.freeze({
201
+ importSource: '@impetik/xeer',
202
+ runtime: 'react/jsx-runtime',
203
+ devRuntime: 'react/jsx-dev-runtime',
204
+ }),
205
+ });
206
+ /**
207
+ * The client facade, and the provider-specific subpaths it resolves to.
208
+ *
209
+ * The platform publishes one adapter subpath per provider beside `@impetik/xeer/client`, so that
210
+ * the facade has a physical module to resolve to and a packed install can be verified against the
211
+ * adapter directly. `./client/preact` is an alias of `./client` rather than a module of its own —
212
+ * the Preact adapter has always been what the facade resolves to by default, and giving it a name
213
+ * moves nothing about how it resolves or what a build produces. Every provider being addressable is
214
+ * what makes this set derivable from {@link CLIENT_RUNTIME_PROVIDERS} instead of hand-listed, and
215
+ * what keeps the refusal below able to say the adapter exists.
216
+ *
217
+ * Naming an adapter is not an application's business under either provider: it would pin one
218
+ * renderer into sources whose entire contract is that `client.runtime.provider` picks the renderer
219
+ * and the sources do not change. So the targets are published and inadmissible everywhere — but the
220
+ * *reason* they are refused is per zone, because the repair is. Where the facade is admitted, the
221
+ * repair is to write the facade; where it is not, the module is in the wrong zone and is told so,
222
+ * which is the same thing `@impetik/xeer/client` itself is told there.
223
+ */
224
+ const CLIENT_FACADE_MODULE = '@impetik/xeer/client';
225
+ const CLIENT_FACADE_TARGETS = Object.freeze(new Set(CLIENT_RUNTIME_PROVIDERS.map((provider) => `${CLIENT_FACADE_MODULE}/${provider}`)));
98
226
  const CLIENT_PLATFORM_MODULES = Object.freeze([
99
- '@impetik/xeer/client',
227
+ CLIENT_FACADE_MODULE,
100
228
  '@impetik/xeer/client/core',
101
229
  '@impetik/xeer/shared',
102
230
  '@impetik/xeer/jsx-runtime',
@@ -106,21 +234,58 @@ const SERVER_PLATFORM_MODULES = Object.freeze([
106
234
  '@impetik/xeer/server',
107
235
  '@impetik/xeer/shared',
108
236
  ]);
237
+ /**
238
+ * What the client zone admits *besides* the renderer, which is the same under every provider: the
239
+ * open application-dependency set, its resolution conditions, its compile-time defines, and its byte
240
+ * budgets. Shared by identity across the provider policies so that choosing a renderer cannot
241
+ * silently change the byte budget an application is held to.
242
+ */
243
+ const CLIENT_APPLICATION_DEPENDENCIES = Object.freeze({
244
+ mode: 'open',
245
+ conditions: Object.freeze(['browser', 'import']),
246
+ defines: Object.freeze({ 'process.env.NODE_ENV': '"production"' }),
247
+ bundleBudget: Object.freeze({
248
+ errorBytes: CLIENT_BUNDLE_ERROR_BUDGET_BYTES,
249
+ advisoryBytes: CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES,
250
+ }),
251
+ });
109
252
  export const CLIENT_IMPORT_POLICY = Object.freeze({
110
253
  zone: 'client',
254
+ provider: 'preact',
111
255
  platformModules: CLIENT_PLATFORM_MODULES,
112
256
  managedFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
113
257
  foreignFamilies: Object.freeze([]),
114
- applicationDependencies: Object.freeze({
115
- mode: 'open',
116
- conditions: Object.freeze(['browser', 'import']),
117
- defines: Object.freeze({ 'process.env.NODE_ENV': '"production"' }),
118
- bundleBudget: Object.freeze({
119
- errorBytes: CLIENT_BUNDLE_ERROR_BUDGET_BYTES,
120
- advisoryBytes: CLIENT_BUNDLE_ADVISORY_BUDGET_BYTES,
121
- }),
122
- }),
258
+ applicationDependencies: CLIENT_APPLICATION_DEPENDENCIES,
259
+ });
260
+ /**
261
+ * The client zone under the React provider.
262
+ *
263
+ * The Preact family is *foreign* here for the same reason the renderer is foreign on the server: it
264
+ * is declared and installed by the platform whatever the application chose, so nothing but an
265
+ * explicit rule keeps it out of a React bundle — and a bundle holding both renderers has two
266
+ * reconcilers rendering into one tree. The two families name overlapping packages (`react` and
267
+ * friends are Preact's alias keys and React's own entrypoints), which is resolved by precedence
268
+ * rather than by trimming either list: {@link admitBareImport} asks the managed families first, so
269
+ * under this policy `react` is the renderer and only `preact*` is left for the foreign rule to
270
+ * refuse. {@link foreignPackageFamily} is that precedence, shared with every other consumer.
271
+ */
272
+ const REACT_CLIENT_IMPORT_POLICY = Object.freeze({
273
+ zone: 'client',
274
+ provider: 'react',
275
+ platformModules: CLIENT_PLATFORM_MODULES,
276
+ managedFamilies: Object.freeze([REACT_RENDERER_FAMILY]),
277
+ foreignFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
278
+ applicationDependencies: CLIENT_APPLICATION_DEPENDENCIES,
123
279
  });
280
+ /**
281
+ * The client zone's policy under one UI provider — a pure function of the normalized manifest's
282
+ * `client.runtime.provider`, with no state and no first-caller latching. The Preact instance is
283
+ * returned by identity so consumers that still read {@link CLIENT_IMPORT_POLICY} and consumers that
284
+ * ask for the default share one object, and so the resolver caches keyed on it stay shared.
285
+ */
286
+ export function clientImportPolicy(provider) {
287
+ return provider === 'react' ? REACT_CLIENT_IMPORT_POLICY : CLIENT_IMPORT_POLICY;
288
+ }
124
289
  /**
125
290
  * The server zone, open to application dependencies since ADR 0005 was accepted (#244).
126
291
  *
@@ -141,6 +306,7 @@ export const CLIENT_IMPORT_POLICY = Object.freeze({
141
306
  */
142
307
  export const SERVER_IMPORT_POLICY = Object.freeze({
143
308
  zone: 'server',
309
+ provider: null,
144
310
  platformModules: SERVER_PLATFORM_MODULES,
145
311
  managedFamilies: Object.freeze([]),
146
312
  foreignFamilies: Object.freeze([PREACT_RENDERER_FAMILY]),
@@ -151,14 +317,38 @@ export const SERVER_IMPORT_POLICY = Object.freeze({
151
317
  bundleBudget: null,
152
318
  }),
153
319
  });
154
- export function zoneImportPolicy(zone) {
155
- return zone === 'client' ? CLIENT_IMPORT_POLICY : SERVER_IMPORT_POLICY;
320
+ /**
321
+ * One zone's policy. The provider only reaches the client zone — the server has no renderer to
322
+ * choose — and defaults to the manifest default so a caller with no manifest in hand (documentation,
323
+ * the server zone) reads exactly what it read before.
324
+ */
325
+ export function zoneImportPolicy(zone, provider = NORMALIZED_CLIENT_RUNTIME.provider) {
326
+ return zone === 'client' ? clientImportPolicy(provider) : SERVER_IMPORT_POLICY;
156
327
  }
157
328
  /** The bare package name of a specifier: the first segment, or the first two for a scope. */
158
329
  export function packageNameOfSpecifier(specifier) {
159
330
  const segments = specifier.split('/');
160
331
  return specifier.startsWith('@') ? segments.slice(0, 2).join('/') : segments[0];
161
332
  }
333
+ /**
334
+ * DefinitelyTyped's name for a package's declarations.
335
+ *
336
+ * A naming rule rather than a lookup, and one nobody guesses correctly for a scoped package: the
337
+ * scope is *flattened into* the name rather than kept, so `@acme/widget` publishes as
338
+ * `@types/acme__widget`. Computed here, beside the specifier-naming rule it is a sibling of, so the
339
+ * two places that need it — the repair for a package with no declarations, and the check for whether
340
+ * an acknowledgment is still true — cannot disagree.
341
+ *
342
+ * Computed rather than read out of TypeScript's own TS7016 message, which carries the same name in
343
+ * English prose. That prose is not contract and has been reworded across releases; a rule this short
344
+ * is cheaper to state than to parse.
345
+ *
346
+ * Whether the package actually exists on the registry is unknowable offline, and nothing in the
347
+ * compiler contacts one at check time — so a caller quoting this must say it as the convention it is.
348
+ */
349
+ export function definitelyTypedPackage(packageName) {
350
+ return `@types/${packageName.replace(/^@/, '').replace('/', '__')}`;
351
+ }
162
352
  /**
163
353
  * Classifies one bare specifier under a zone's policy. Node builtins and native addons are the
164
354
  * caller's concern (the analyzer already owns those, and they are refused in every zone); everything
@@ -168,33 +358,67 @@ export function packageNameOfSpecifier(specifier) {
168
358
  export function admitBareImport(policy, specifier) {
169
359
  if (policy.platformModules.includes(specifier))
170
360
  return { kind: 'platform' };
361
+ // Only where the facade is admitted is "import the facade instead" the repair; a server module
362
+ // naming a client adapter is in the wrong zone, and gets told that instead.
363
+ if (CLIENT_FACADE_TARGETS.has(specifier) && policy.platformModules.includes(CLIENT_FACADE_MODULE)) {
364
+ return { kind: 'rejected', reason: 'provider-facade' };
365
+ }
171
366
  if (specifier === '@impetik/xeer' || specifier.startsWith('@impetik/xeer/')) {
172
367
  return { kind: 'rejected', reason: 'platform-subpath' };
173
368
  }
174
369
  const packageName = packageNameOfSpecifier(specifier);
175
- for (const family of policy.managedFamilies) {
176
- if (!family.packages.includes(packageName))
177
- continue;
178
- const target = family.compatAliases.get(specifier) ?? specifier;
179
- const classification = family.entrypoints.get(target);
370
+ const managed = managedPackageFamily(policy, packageName);
371
+ if (managed) {
372
+ const target = managed.compatAliases.get(specifier) ?? specifier;
373
+ const classification = managed.entrypoints.get(target)?.classification;
180
374
  if (!classification)
181
375
  return { kind: 'managed-rejected', reason: 'unknown-entrypoint' };
182
376
  if (classification === 'test')
183
377
  return { kind: 'managed-rejected', reason: 'test-only' };
184
378
  if (classification === 'metadata')
185
379
  return { kind: 'managed-rejected', reason: 'metadata' };
380
+ if (classification === 'other-runtime')
381
+ return { kind: 'managed-rejected', reason: 'other-runtime' };
186
382
  return { kind: 'managed', classification, target };
187
383
  }
188
384
  // Before the open set, or an installed renderer would be admitted by the declaration invariant it
189
385
  // satisfies in every application that has a client.
190
- for (const family of policy.foreignFamilies) {
191
- if (family.packages.includes(packageName))
192
- return { kind: 'rejected', reason: 'foreign-family' };
193
- }
386
+ if (foreignPackageFamily(policy, packageName))
387
+ return { kind: 'rejected', reason: 'foreign-family' };
194
388
  if (policy.applicationDependencies.mode === 'open')
195
389
  return { kind: 'open' };
196
390
  return { kind: 'rejected', reason: 'closed' };
197
391
  }
392
+ /** The managed family that owns a bare package name in a zone, or null. */
393
+ export function managedPackageFamily(policy, packageName) {
394
+ return policy.managedFamilies.find((family) => family.packages.includes(packageName)) ?? null;
395
+ }
396
+ /**
397
+ * The foreign family that refuses a bare package name in a zone, or null.
398
+ *
399
+ * Managed ownership wins, and that precedence is the whole reason this is a function rather than a
400
+ * lookup each caller writes: under the React provider the foreign Preact family names `react`,
401
+ * `react-dom`, and `scheduler` among its own compat-alias territory, and those are precisely the
402
+ * packages the managed React family *is*. Asking managed-first is what lets the two descriptors stay
403
+ * complete statements about themselves instead of being trimmed to avoid each other.
404
+ */
405
+ export function foreignPackageFamily(policy, packageName) {
406
+ if (managedPackageFamily(policy, packageName))
407
+ return null;
408
+ return policy.foreignFamilies.find((family) => family.packages.includes(packageName)) ?? null;
409
+ }
410
+ /**
411
+ * The package whose installed root one classified entrypoint resolves from. Derived from the
412
+ * specifier rather than declared a second time, so a family cannot claim an entrypoint it does not
413
+ * anchor — which is what keeps `entrypoints` and `anchorPackages` from drifting apart.
414
+ */
415
+ export function entrypointAnchorPackage(family, specifier) {
416
+ const packageName = packageNameOfSpecifier(specifier);
417
+ if (!family.anchorPackages.includes(packageName)) {
418
+ throw new Error(`${specifier} names no package this renderer family anchors.`);
419
+ }
420
+ return packageName;
421
+ }
198
422
  /**
199
423
  * The admitted managed-family surface of a zone, alias keys included — what bundling and the dev
200
424
  * server must pin to the anchor, and what documentation lists as the renderer surface.
@@ -202,15 +426,19 @@ export function admitBareImport(policy, specifier) {
202
426
  export function managedImportSpecifiers(policy) {
203
427
  const result = new Map();
204
428
  for (const family of policy.managedFamilies) {
205
- for (const [specifier, classification] of family.entrypoints) {
206
- if (classification === 'runtime' || classification === 'development')
429
+ for (const [specifier, entrypoint] of family.entrypoints) {
430
+ if (isImportableClassification(entrypoint.classification))
207
431
  result.set(specifier, specifier);
208
432
  }
209
433
  for (const [alias, target] of family.compatAliases) {
210
- const classification = family.entrypoints.get(target);
211
- if (classification === 'runtime' || classification === 'development')
434
+ const entrypoint = family.entrypoints.get(target);
435
+ if (entrypoint && isImportableClassification(entrypoint.classification))
212
436
  result.set(alias, target);
213
437
  }
214
438
  }
215
439
  return result;
216
440
  }
441
+ /** Whether client application code may import an entrypoint with this classification. */
442
+ export function isImportableClassification(classification) {
443
+ return classification === 'runtime' || classification === 'development';
444
+ }
@@ -20,5 +20,7 @@ export * from './admin-sql.js';
20
20
  export * from './network-policy.js';
21
21
  export * from './review.js';
22
22
  export * from './template.js';
23
+ export * from './template-distribution.js';
23
24
  export * from './tunnel.js';
24
25
  export * from './type-check-profile.js';
26
+ export * from './editor-settings.js';
@@ -20,5 +20,7 @@ export * from './admin-sql.js';
20
20
  export * from './network-policy.js';
21
21
  export * from './review.js';
22
22
  export * from './template.js';
23
+ export * from './template-distribution.js';
23
24
  export * from './tunnel.js';
24
25
  export * from './type-check-profile.js';
26
+ export * from './editor-settings.js';
@@ -159,9 +159,13 @@ export declare const applicationManifestSchema: z.ZodObject<{
159
159
  database: "database";
160
160
  storage: "storage";
161
161
  }>>>;
162
+ untypedDependencies: z.ZodOptional<z.ZodArray<z.ZodString>>;
162
163
  client: z.ZodOptional<z.ZodObject<{
163
164
  runtime: z.ZodOptional<z.ZodObject<{
164
- provider: z.ZodLiteral<"preact">;
165
+ provider: z.ZodEnum<{
166
+ preact: "preact";
167
+ react: "react";
168
+ }>;
165
169
  source: z.ZodOptional<z.ZodLiteral<"platform">>;
166
170
  }, z.core.$strict>>;
167
171
  }, z.core.$strict>>;
@@ -1,5 +1,6 @@
1
1
  import { z } from 'zod';
2
2
  import { CAPABILITIES, SOURCE_FORMAT, } from './types.js';
3
+ import { CLIENT_RUNTIME_PROVIDERS, NORMALIZED_CLIENT_RUNTIME } from './import-policy.js';
3
4
  import { STORAGE_DEFAULT_READ_BYTES, STORAGE_DEFAULT_WRITE_BYTES, STORAGE_MAX_OBJECT_BYTES, STORAGE_MAX_READ_BYTES, STORAGE_MAX_WRITE_BYTES, } from './storage.js';
4
5
  import { SQL_EXPRESSION_MAX_LENGTH } from './sql-expression.js';
5
6
  import { databaseDdl, indexColumns, referenceCycle, RESERVED_TABLE_PREFIXES, reservedTableName } from './table-ddl.js';
@@ -23,6 +24,13 @@ const favicon = z.string().min(2).max(256)
23
24
  * table and manifest refinements below run the real check by generating the DDL.
24
25
  */
25
26
  const expression = z.string().min(1).max(SQL_EXPRESSION_MAX_LENGTH);
27
+ /**
28
+ * An npm package name, as the registry defines one: optionally scoped, lowercase, and with no
29
+ * subpath. Bounded at npm's own 214 characters.
30
+ */
31
+ const packageName = z.string()
32
+ .regex(/^(?:@[a-z0-9-~][a-z0-9-._~]*\/)?[a-z0-9-~][a-z0-9-._~]*$/, 'must be an npm package name')
33
+ .max(214);
26
34
  const collation = z.enum(['binary', 'nocase', 'rtrim']);
27
35
  const referentialAction = z.enum(['noAction', 'restrict', 'cascade', 'setNull', 'setDefault']);
28
36
  const LOWERCASE_HEX = /^(?:[0-9a-f]{2})*$/;
@@ -275,13 +283,34 @@ export const applicationManifestSchema = z.strictObject({
275
283
  }).optional(),
276
284
  capabilities: z.array(z.enum(CAPABILITIES)).optional(),
277
285
  /**
278
- * The client runtime selection (#212). Closed: only implemented provider/source combinations are
279
- * accepted, and omitting any level of it normalizes to the platform-managed Preact runtime with
280
- * identical semantics and build output.
286
+ * Dependencies the application accepts having no TypeScript declarations for.
287
+ *
288
+ * The one legal thing the open client zone could not express. A package that is declared,
289
+ * installed, and simply ships no declarations is not a mistake — plenty of working JavaScript never
290
+ * grew types — but it stops `xeer check` with a type error the application did not cause, and the
291
+ * repair used to be a hand-written declaration file plus a triple-slash reference, per package and
292
+ * per subpath. Naming the package here is that whole ritual: the compiler derives the ambient
293
+ * declarations, subpaths included, into the generated contract both tools read.
294
+ *
295
+ * Explicit and checked in, deliberately. What it buys is that the named package's exports are
296
+ * `any`, which is a hole in the guarantee every other rule here exists to make — so it belongs in a
297
+ * diff someone reviews, not in an inference. The compiler warns about an entry whose package does
298
+ * ship declarations, because an acknowledgment nobody removes is how this list would rot into a
299
+ * standing exemption.
300
+ *
301
+ * Package names rather than specifiers: one entry covers the package and everything under it, so
302
+ * there is nothing to add when an import reaches a new subpath.
303
+ */
304
+ untypedDependencies: z.array(packageName).optional(),
305
+ /**
306
+ * The client runtime selection (#212, #214). Closed: only implemented provider/source
307
+ * combinations are accepted, and omitting any level of it normalizes to the platform-managed
308
+ * Preact runtime with identical semantics and build output. A provider the platform has no
309
+ * renderer family for is refused here rather than surfacing later as an unresolvable JSX runtime.
281
310
  */
282
311
  client: z.strictObject({
283
312
  runtime: z.strictObject({
284
- provider: z.literal('preact'),
313
+ provider: z.enum(CLIENT_RUNTIME_PROVIDERS),
285
314
  source: z.literal('platform').optional(),
286
315
  }).optional(),
287
316
  }).optional(),
@@ -363,6 +392,12 @@ export const applicationManifestJsonSchema = Object.freeze({
363
392
  ...describeProperty('capabilities', 'Powers granted to the application. Duplicate names are invalid.'),
364
393
  uniqueItems: true,
365
394
  },
395
+ untypedDependencies: {
396
+ ...describeProperty('untypedDependencies', 'Installed dependencies that ship no TypeScript declarations, acknowledged by name. Xeer '
397
+ + 'generates ambient declarations for each one and its subpaths, typed as any. Duplicate '
398
+ + 'names are invalid.'),
399
+ uniqueItems: true,
400
+ },
366
401
  budgets: describeProperty('budgets', 'Per-operation resource ceilings, each lowerable from platform defaults.'),
367
402
  },
368
403
  allOf: [
@@ -418,9 +453,25 @@ export function normalizeApplicationManifest(manifest) {
418
453
  }
419
454
  : null,
420
455
  capabilities: [...(manifest.capabilities ?? [])].sort(),
456
+ // Total, like `capabilities` and unlike `storage`: sorted, deduplicated, and `[]` when the
457
+ // manifest declares nothing, so every reader gets an array rather than a `?? []` of its own.
458
+ //
459
+ // The present-only-when-declared shape next door exists to protect `artifactId`, and that
460
+ // concern does not reach here: the artifact payload enumerates the manifest fields it carries
461
+ // rather than spreading the manifest, and this one is deliberately not among them. An
462
+ // acknowledgment is type-space only — it changes no byte of the bundle — so moving the artifact
463
+ // id of a bundle-identical application would be a plain lie about what shipped.
464
+ untypedDependencies: [...new Set(manifest.untypedDependencies ?? [])].sort(),
421
465
  // Always normalized: omission and the explicit default are the same statement, and everything
422
- // downstream (compiler facts, artifact provenance) reads one canonical value.
423
- client: { runtime: { provider: 'preact', source: 'platform' } },
466
+ // downstream (import policy, type resolution, artifact provenance) reads one canonical value.
467
+ // The declared provider is *carried*, not defaulted away — returning the default unconditionally
468
+ // is what let a declared React application build as Preact with no diagnostic anywhere (#214).
469
+ client: {
470
+ runtime: {
471
+ provider: manifest.client?.runtime?.provider ?? NORMALIZED_CLIENT_RUNTIME.provider,
472
+ source: manifest.client?.runtime?.source ?? NORMALIZED_CLIENT_RUNTIME.source,
473
+ },
474
+ },
424
475
  budgets: {
425
476
  queryRows: manifest.budgets?.queryRows ?? 1_000,
426
477
  mutationWrites: manifest.budgets?.mutationWrites ?? 100,