@riducms/plugin 0.2.0 → 0.2.2

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
@@ -259,6 +259,26 @@ tree, and `identity` identifies the existing item. Ridu finds its current positi
259
259
  ordinary fields. Edits immediately enter the parent form; this does not save the document.
260
260
  Removed/malformed items show recovery UI rather than another item's fields.
261
261
 
262
+ For a Go-configured `BlockAdmin.NameField`, `schemaHeader` renders just the editorial
263
+ name input and its feedback:
264
+
265
+ ```svelte
266
+ {#if authoring.schemaHeader !== undefined}
267
+ {@render authoring.schemaHeader({
268
+ treeKey: "widgets",
269
+ identity: selectedIdentity,
270
+ onChange: ({ field, value }) => updateWidgetField(selectedIdentity, field, value),
271
+ })}
272
+ {/if}
273
+ ```
274
+
275
+ Here `updateWidgetField` belongs to your plugin: apply the permitted field change
276
+ to the latest matching item through your editor's history, then serialize with
277
+ `field.set`. The header does not directly change the parent form. Ridu resolves the
278
+ configured child, checks its access and visibility, and disables the card header
279
+ while a draft for that tree and identity is open. The ordinary content renderer
280
+ omits the name field's body placement; draft drawers include it in their own header.
281
+
262
282
  For **Apply/Cancel**, use a temporary embedded draft:
263
283
 
264
284
  1. Call `beginSchemaDraft({ treeKey, identity })` for an existing item, or
package/package.json CHANGED
@@ -3,9 +3,9 @@
3
3
  "url": "https://github.com/riducms/ridu/issues"
4
4
  },
5
5
  "dependencies": {
6
- "@riducms/protocol": "0.2.0",
7
- "@riducms/translations": "0.2.0",
8
- "@riducms/ui": "0.2.0"
6
+ "@riducms/protocol": "0.2.2",
7
+ "@riducms/translations": "0.2.2",
8
+ "@riducms/ui": "0.2.2"
9
9
  },
10
10
  "description": "Public TypeScript contracts for statically registered Ridu admin plugins.",
11
11
  "exports": {
@@ -39,5 +39,5 @@
39
39
  "test": "bun test"
40
40
  },
41
41
  "type": "module",
42
- "version": "0.2.0"
42
+ "version": "0.2.2"
43
43
  }
package/src/admin.ts CHANGED
@@ -1,15 +1,25 @@
1
1
  import { bindSchemaManifest } from "@riducms/protocol";
2
2
  import { resolveBlockTypes } from "@riducms/protocol";
3
- import { validatePluginManifest, validatePluginRegistrations } from "./plugin-registry";
3
+ import {
4
+ resolvePluginFields,
5
+ validateManifestPluginPairs,
6
+ createPluginFieldValidator,
7
+ type ResolvedPluginField,
8
+ } from "./plugin-registry";
4
9
  import { isRegisteredRowLabel, type RegisteredRowLabel } from "./local-row-label";
5
10
  import { localEditorReference } from "./editor/registry";
6
11
  import type { SchemaField } from "@riducms/protocol";
7
12
  import type { SchemaManifest } from "@riducms/protocol";
8
- import type { TranslationLanguage } from "@riducms/translations";
13
+ import { en, type TranslationLanguage } from "@riducms/translations";
9
14
  import type { PluginMessageCatalog } from "./i18n";
10
- import { resolveAdminExtensions, type AdminContributions, type AdminPlugin } from "./plugin";
11
15
  import {
12
- validateAdminEditors,
16
+ resolveAdminExtensions,
17
+ type AdminContributions,
18
+ type AdminPlugin,
19
+ type ResolvedAdminExtensions,
20
+ } from "./plugin";
21
+ import {
22
+ validateFieldEditorSelection,
13
23
  validateFieldEditorRegistrations,
14
24
  type FieldEditorConfig,
15
25
  } from "./editor/registry";
@@ -50,9 +60,8 @@ export interface AdminConfig extends AdminContributions, FieldEditorConfig {
50
60
  * These checks also run when building and starting the admin.
51
61
  *
52
62
  * @param config The installed plugins, custom components, and interface translations.
53
- * @returns The same configuration object after registration checks. The admin
63
+ * @returns The same typed configuration object. The admin
54
64
  * reads it when starting; calling this function does not mount any components.
55
- * @throws If registrations, keys, or replacement locations conflict.
56
65
  * @example
57
66
  * ```ts
58
67
  * import { defineAdmin } from '@riducms/plugin/admin';
@@ -66,18 +75,39 @@ export interface AdminConfig extends AdminContributions, FieldEditorConfig {
66
75
  * ```
67
76
  */
68
77
  export function defineAdmin(config: AdminConfig): AdminConfig {
78
+ return config;
79
+ }
80
+
81
+ /** Static configuration owned by one mounted admin. Changes require remount/HMR. */
82
+ export interface ResolvedAdminConfig {
83
+ readonly plugins: readonly AdminPlugin[];
84
+ readonly fields: readonly ResolvedPluginField[];
85
+ readonly editors: NonNullable<AdminConfig["fields"]>;
86
+ readonly rowLabels: NonNullable<AdminConfig["rowLabels"]>;
87
+ readonly languages: readonly TranslationLanguage[];
88
+ readonly extensions: Readonly<ResolvedAdminExtensions>;
89
+ }
90
+
91
+ /** Normalize and validate static registrations once, independently of schema refresh. */
92
+ export function resolveAdminConfig(config: AdminConfig = {}): ResolvedAdminConfig {
69
93
  if ("fieldPlugins" in config)
70
94
  throw new Error(
71
95
  "Application fieldPlugins are not supported; use a field-owned Editor component or a paired advanced field plugin."
72
96
  );
73
- validatePluginRegistrations(config.plugins ?? []);
74
97
  validateFieldEditorRegistrations(config);
75
98
  for (const [reference, label] of Object.entries(config.rowLabels ?? {})) {
76
99
  if (!localEditorReference.test(reference) || !isRegisteredRowLabel(label))
77
100
  throw new Error(`Row label ${reference} must use an app:name reference and defineRowLabel.`);
78
101
  }
79
- resolveAdminExtensions(config.plugins ?? [], config);
80
- return config;
102
+ const plugins = Object.freeze([...(config.plugins ?? [])]);
103
+ return Object.freeze({
104
+ plugins,
105
+ fields: resolvePluginFields(plugins),
106
+ editors: Object.freeze({ ...config.fields }),
107
+ rowLabels: Object.freeze({ ...config.rowLabels }),
108
+ languages: Object.freeze(config.languages?.length ? [...config.languages] : [en]),
109
+ extensions: Object.freeze(resolveAdminExtensions(plugins, config)),
110
+ });
81
111
  }
82
112
 
83
113
  /**
@@ -87,40 +117,47 @@ export function defineAdmin(config: AdminConfig): AdminConfig {
87
117
  * resources the current user cannot access, so absence there does not prove a typo.
88
118
  * Throws with the affected registration or field when a selection/config is invalid.
89
119
  */
90
- export function validateAdminConfig(
91
- config: AdminConfig,
120
+ export function validateAdminManifest(
121
+ config: ResolvedAdminConfig,
92
122
  manifest: Pick<SchemaManifest, "collections" | "globals" | "blocks"> &
93
123
  Partial<Pick<SchemaManifest, "plugins">>,
94
124
  options: { completeManifest?: boolean } = {}
95
125
  ): void {
96
- defineAdmin(config);
97
- validateAdminEditors(config, manifest);
98
- validatePluginManifest(config.plugins ?? [], manifest, options.completeManifest === true);
126
+ validateManifestPluginPairs(
127
+ config.plugins,
128
+ config.fields,
129
+ manifest,
130
+ options.completeManifest === true
131
+ );
132
+ const validatePluginField = createPluginFieldValidator(config.fields);
133
+ const failures: string[] = [];
99
134
  bindSchemaManifest(manifest);
100
135
  const inspect = (fields: readonly SchemaField[], owner: string) => {
101
136
  for (const field of fields) {
102
- const selection = field.nested?.rowLabelComponent;
103
- if (selection?.reference !== undefined) {
104
- const reference = selection.reference;
105
- if (
106
- !localEditorReference.test(reference) ||
107
- selection.plugin !== undefined ||
108
- selection.component !== undefined ||
109
- (field.type !== "array" && field.type !== "blocks")
110
- )
111
- throw new Error(`${owner}.${field.path}: invalid local row label selection.`);
112
- const label = config.rowLabels?.[reference as `app:${string}`];
113
- if (label === undefined)
114
- throw new Error(
115
- `${owner}.${field.path}: row label ${reference} is not registered in admin/src/admin.config.ts.`
116
- );
117
- try {
137
+ try {
138
+ validateFieldEditorSelection(config.editors, field);
139
+ validatePluginField(field);
140
+ const selection = field.nested?.rowLabelComponent;
141
+ if (selection?.reference !== undefined) {
142
+ const reference = selection.reference;
143
+ if (
144
+ !localEditorReference.test(reference) ||
145
+ selection.plugin !== undefined ||
146
+ selection.component !== undefined ||
147
+ (field.type !== "array" && field.type !== "blocks")
148
+ )
149
+ throw new Error("invalid local row label selection.");
150
+ const label = config.rowLabels?.[reference as `app:${string}`];
151
+ if (label === undefined)
152
+ throw new Error(
153
+ `row label ${reference} is not registered in admin/src/admin.config.ts.`
154
+ );
118
155
  label.decode(field);
119
- } catch (error) {
120
- throw new Error(
121
- `${owner}.${field.path}: ${error instanceof Error ? error.message : String(error)}`
122
- );
123
156
  }
157
+ } catch (error) {
158
+ failures.push(
159
+ `${owner}.${field.path}: ${error instanceof Error ? error.message : String(error)}`
160
+ );
124
161
  }
125
162
  for (const tree of field.plugin?.embeddedTrees ?? [])
126
163
  for (const item of tree.cases)
@@ -131,11 +168,12 @@ export function validateAdminConfig(
131
168
  };
132
169
  for (const collection of manifest.collections) inspect(collection.fields, collection.slug);
133
170
  for (const global of manifest.globals ?? []) inspect(global.fields, global.slug);
171
+ if (failures.length) throw new Error(failures.join("\n"));
134
172
  // Runtime manifests are permission-filtered; only build checks can prove target absence.
135
173
  if (!options.completeManifest) return;
136
174
  const collections = new Map(manifest.collections.map((item) => [item.slug, item]));
137
175
  const globals = new Set((manifest.globals ?? []).map((item) => item.slug));
138
- const extensions = resolveAdminExtensions(config.plugins ?? [], config);
176
+ const extensions = config.extensions;
139
177
  for (const cell of extensions.listCells) {
140
178
  const collection = collections.get(cell.collection);
141
179
  if (!collection?.fields.some((field) => field.name === cell.field || field.path === cell.field))
@@ -1,5 +1,5 @@
1
1
  import type { AdminPlugin } from "../plugin";
2
- import { validatePluginRegistrations } from "../plugin-registry";
2
+ import { resolvePluginFields } from "../plugin-registry";
3
3
 
4
4
  export { definePluginField, defineFieldComponent } from "../field";
5
5
  export type {
@@ -66,6 +66,6 @@ export function defineAdminPlugin<const Plugin extends Omit<AdminPlugin, "apiVer
66
66
  apiVersion: authoringAPIVersion,
67
67
  });
68
68
  const definition: Omit<AdminPlugin, "apiVersion"> = plugin;
69
- validatePluginRegistrations([{ ...definition, apiVersion: authoringAPIVersion }]);
69
+ resolvePluginFields([{ ...definition, apiVersion: authoringAPIVersion }]);
70
70
  return result;
71
71
  }
package/src/authoring.ts CHANGED
@@ -84,6 +84,16 @@ export interface EmbeddedSchemaFormScope {
84
84
  readOnly?: boolean;
85
85
  }
86
86
 
87
+ /** A configured editorial-name control for one existing embedded item. */
88
+ export interface EmbeddedSchemaHeaderProps extends EmbeddedSchemaFormScope {
89
+ /**
90
+ * Receives a permitted change to the Go-configured name field. Apply it to the
91
+ * latest matching item through your editor's own history/serialization path.
92
+ * The host does not write the parent form or save the document itself.
93
+ */
94
+ onChange: (change: { field: string; value: string }) => void;
95
+ }
96
+
87
97
  /** Select a Go-declared embedded variant when creating or copying an item's field data. */
88
98
  export interface EmbeddedSchemaVariantScope {
89
99
  /** The embedded tree key declared by the Go field. */
@@ -159,6 +169,12 @@ export interface FieldAuthoringHost {
159
169
  * if the user needs Apply/Cancel. Neither approach saves the document itself.
160
170
  */
161
171
  schemaForm?: Snippet<[EmbeddedSchemaFormScope]>;
172
+ /**
173
+ * Render the configured name input and field feedback, without the card chrome.
174
+ * Uses the item's schema, access, visibility and stable identity. An open draft
175
+ * locks the card input; an item without a name field renders no control.
176
+ */
177
+ schemaHeader?: Snippet<[EmbeddedSchemaHeaderProps]>;
162
178
  /**
163
179
  * Start a temporary Apply/Cancel form. Pass `{ treeKey, identity }` to edit an
164
180
  * existing item, or `{ treeKey, caseTag, variantSlug }` to prepare a new one using
@@ -1,7 +1,5 @@
1
- import { bindSchemaManifest } from "@riducms/protocol";
2
- import { resolveBlockTypes } from "@riducms/protocol";
3
1
  import type { Component } from "svelte";
4
- import type { SchemaField, SchemaManifest } from "@riducms/protocol";
2
+ import type { SchemaField } from "@riducms/protocol";
5
3
 
6
4
  import type { FieldEditorProps, FieldEditorType } from "./types";
7
5
  import { decodeComponentConfig } from "../component-config";
@@ -134,41 +132,19 @@ export function validateFieldEditorRegistrations(config: FieldEditorConfig): voi
134
132
  }
135
133
  }
136
134
 
137
- /** Uses the same executable registrations and decoders in the browser and build checks. */
138
- export function validateAdminEditors(
139
- config: FieldEditorConfig,
140
- manifest: Pick<SchemaManifest, "collections" | "globals" | "blocks">
135
+ /** Check one local selection using the same decoder as the mounted editor. */
136
+ export function validateFieldEditorSelection(
137
+ editors: NonNullable<FieldEditorConfig["fields"]>,
138
+ field: SchemaField
141
139
  ): void {
142
- validateFieldEditorRegistrations(config);
143
- const failures: string[] = [];
144
- bindSchemaManifest(manifest);
145
- const inspect = (fields: readonly SchemaField[], owner: string) => {
146
- for (const field of fields) {
147
- const reference = field.admin.editor?.reference;
148
- if (reference !== undefined) {
149
- const editor = config.fields?.[reference as `app:${string}`];
150
- try {
151
- if (!localEditorReference.test(reference))
152
- throw new Error(`Malformed editor reference ${reference}; expected app:name.`);
153
- if (editor === undefined)
154
- throw new Error(
155
- `Editor ${reference} is not registered. Register it in admin/src/admin.config.ts or change the field component in Go.`
156
- );
157
- editor.decode(field);
158
- } catch (error) {
159
- failures.push(
160
- `${owner}.${field.path}: ${error instanceof Error ? error.message : String(error)}`
161
- );
162
- }
163
- }
164
- for (const tree of field.plugin?.embeddedTrees ?? [])
165
- for (const item of tree.cases)
166
- for (const variant of resolveBlockTypes(item)) inspect(variant.fields, owner);
167
- if (field.nested !== undefined) inspect(field.nested.fields, owner);
168
- for (const block of resolveBlockTypes(field.blocks) ?? []) inspect(block.fields, owner);
169
- }
170
- };
171
- for (const collection of manifest.collections) inspect(collection.fields, collection.slug);
172
- for (const global of manifest.globals ?? []) inspect(global.fields, global.slug);
173
- if (failures.length !== 0) throw new Error(failures.join("\n"));
140
+ const reference = field.admin.editor?.reference;
141
+ if (reference === undefined) return;
142
+ if (!localEditorReference.test(reference))
143
+ throw new Error(`Malformed editor reference ${reference}; expected app:name.`);
144
+ const editor = editors[reference as `app:${string}`];
145
+ if (editor === undefined)
146
+ throw new Error(
147
+ `Editor ${reference} is not registered. Register it in admin/src/admin.config.ts or change the field component in Go.`
148
+ );
149
+ editor.decode(field);
174
150
  }
package/src/index.ts CHANGED
@@ -1,5 +1,6 @@
1
1
  export type {
2
2
  EmbeddedSchemaFormScope,
3
+ EmbeddedSchemaHeaderProps,
3
4
  EmbeddedSchemaVariantScope,
4
5
  EmbeddedSchemaDraft,
5
6
  EmbeddedSchemaDraftEditorProps,
@@ -36,7 +37,7 @@ export {
36
37
  export {
37
38
  ADMIN_PLUGIN_API_VERSION,
38
39
  resolveAdminExtensions,
39
- resolveAdminPluginPairs,
40
+ assertAdminPluginPairs,
40
41
  type AdminPlugin,
41
42
  type AdminContributions,
42
43
  type AdminDashboardPanel,
@@ -79,7 +80,6 @@ export {
79
80
  type AdminRoute,
80
81
  type BackendAdminPlugin,
81
82
  type ResolvedAdminExtensions,
82
- type ResolvedAdminPluginPairs,
83
83
  } from "./plugin";
84
84
 
85
85
  export { resolvePluginFields, type ResolvedPluginField } from "./plugin-registry";
@@ -1,8 +1,6 @@
1
- import { bindSchemaManifest } from "@riducms/protocol";
2
- import { resolveBlockTypes } from "@riducms/protocol";
3
1
  import { ADMIN_PLUGIN_API_VERSION, type SchemaField, type SchemaManifest } from "@riducms/protocol";
4
2
  import { assertPluginFieldRegistration, type RegisteredPluginField } from "./field";
5
- import type { AdminPlugin } from "./plugin";
3
+ import { assertAdminPluginPairs, type AdminPlugin } from "./plugin";
6
4
 
7
5
  /** A registered field editor with the names Ridu uses to select it. Returned by registry resolution. */
8
6
  export interface ResolvedPluginField {
@@ -15,10 +13,6 @@ export interface ResolvedPluginField {
15
13
  readonly registration: RegisteredPluginField;
16
14
  }
17
15
 
18
- export function validatePluginRegistrations(plugins: readonly AdminPlugin[]): void {
19
- resolvePluginFields(plugins);
20
- }
21
-
22
16
  /**
23
17
  * Collect field registrations, checking plugin IDs, field-type ownership, named
24
18
  * components and API versions. Throws on malformed/duplicate registrations.
@@ -87,13 +81,13 @@ export function resolvePluginFields(
87
81
  }
88
82
 
89
83
  /** Canonical manifest, actual executable registrations, and the same decoders as the host. */
90
- export function validatePluginManifest(
84
+ export function validateManifestPluginPairs(
91
85
  plugins: readonly AdminPlugin[],
86
+ registrations: readonly ResolvedPluginField[],
92
87
  manifest: Pick<SchemaManifest, "collections" | "globals" | "blocks"> &
93
88
  Partial<Pick<SchemaManifest, "plugins">>,
94
89
  complete: boolean
95
90
  ): void {
96
- const registrations = resolvePluginFields(plugins);
97
91
  // Startup may receive only public or access-filtered schemas. Build/check own
98
92
  // completeness; selected fields below still validate their exact renderer.
99
93
  if (complete && manifest.plugins !== undefined)
@@ -108,38 +102,22 @@ export function validatePluginManifest(
108
102
  `Named renderer ${item.owner}:${item.key} selects undeclared field type ${item.registration.fieldType}.`
109
103
  );
110
104
  }
111
- const fields = new Map(
112
- registrations.filter((item) => item.componentKey === undefined).map((item) => [item.key, item])
113
- );
114
- const components = new Map(
115
- registrations
116
- .filter((item) => item.componentKey !== undefined)
117
- .map((item) => [`${item.owner}:${item.key}`, item])
118
- );
119
105
  if (manifest.plugins !== undefined) {
120
106
  for (const backend of manifest.plugins) {
121
107
  if (backend.admin === undefined) continue;
122
108
  const plugin = plugins.find((candidate) => candidate.key === backend.key);
123
109
  if (plugin === undefined)
124
110
  throw new Error(`Missing admin registration for backend plugin ${backend.key}.`);
125
- if (
126
- plugin.apiVersion !== backend.admin.apiVersion ||
127
- plugin.pairingVersion !== backend.admin.pairingVersion
128
- )
129
- throw new Error(`Backend/admin compatibility mismatch for plugin ${backend.key}.`);
130
- if (
131
- JSON.stringify(backend.admin.routes ?? []) !==
132
- JSON.stringify((plugin.routes ?? []).map((route) => route.path)) ||
133
- JSON.stringify(backend.admin.assets ?? []) !== JSON.stringify(plugin.assets ?? [])
134
- )
135
- throw new Error(
136
- `Plugin ${backend.key} routes/assets do not match the canonical Go descriptor.`
137
- );
138
- const expected = (backend.fieldTypes ?? []).map((field) => field.key).sort();
139
- if (JSON.stringify(expected) !== JSON.stringify(Object.keys(plugin.fields ?? {}).sort()))
140
- throw new Error(
141
- `Plugin ${backend.key} field types do not match the canonical Go descriptor (${expected.join(", ")}).`
142
- );
111
+ assertAdminPluginPairs([
112
+ {
113
+ admin: plugin,
114
+ backend: {
115
+ ...backend.admin,
116
+ key: backend.key,
117
+ fieldTypes: (backend.fieldTypes ?? []).map((field) => field.key),
118
+ },
119
+ },
120
+ ]);
143
121
  }
144
122
  if (complete)
145
123
  for (const plugin of plugins) {
@@ -151,38 +129,32 @@ export function validatePluginManifest(
151
129
  throw new Error(`Admin plugin ${plugin.key} has no paired Go admin descriptor.`);
152
130
  }
153
131
  }
154
- const failures: string[] = [];
155
- bindSchemaManifest(manifest);
156
- const inspect = (items: readonly SchemaField[], owner: string) => {
157
- for (const field of items) {
158
- try {
159
- const selection = field.admin.component;
160
- const selected =
161
- selection !== undefined
162
- ? components.get(`${selection.plugin}:${selection.component}`)
163
- : field.type === "plugin"
164
- ? fields.get(field.plugin?.key ?? "")
165
- : undefined;
166
- if (selection !== undefined || field.type === "plugin") {
167
- if (selected === undefined)
168
- throw new Error(
169
- `Missing renderer for ${selection !== undefined ? `${selection.plugin}:${selection.component}` : field.plugin?.key}.`
170
- );
171
- selected.registration.decodeConfig(field);
172
- }
173
- } catch (error) {
174
- failures.push(
175
- `${owner}.${field.path}: ${error instanceof Error ? error.message : String(error)}`
132
+ }
133
+
134
+ /** Resolve a selection against static registrations; the caller owns schema traversal. */
135
+ export function createPluginFieldValidator(registrations: readonly ResolvedPluginField[]) {
136
+ const fields = new Map(
137
+ registrations.filter((item) => item.componentKey === undefined).map((item) => [item.key, item])
138
+ );
139
+ const components = new Map(
140
+ registrations
141
+ .filter((item) => item.componentKey !== undefined)
142
+ .map((item) => [`${item.owner}:${item.key}`, item])
143
+ );
144
+ return (field: SchemaField): void => {
145
+ const selection = field.admin.component;
146
+ const selected =
147
+ selection !== undefined
148
+ ? components.get(`${selection.plugin}:${selection.component}`)
149
+ : field.type === "plugin"
150
+ ? fields.get(field.plugin?.key ?? "")
151
+ : undefined;
152
+ if (selection !== undefined || field.type === "plugin") {
153
+ if (selected === undefined)
154
+ throw new Error(
155
+ `Missing renderer for ${selection !== undefined ? `${selection.plugin}:${selection.component}` : field.plugin?.key}.`
176
156
  );
177
- }
178
- if (field.nested !== undefined) inspect(field.nested.fields, owner);
179
- for (const block of resolveBlockTypes(field.blocks) ?? []) inspect(block.fields, owner);
180
- for (const tree of field.plugin?.embeddedTrees ?? [])
181
- for (const branch of tree.cases)
182
- for (const block of resolveBlockTypes(branch)) inspect(block.fields, owner);
157
+ selected.registration.decodeConfig(field);
183
158
  }
184
159
  };
185
- for (const collection of manifest.collections) inspect(collection.fields, collection.slug);
186
- for (const global of manifest.globals ?? []) inspect(global.fields, global.slug);
187
- if (failures.length) throw new Error(failures.join("\n"));
188
160
  }
package/src/plugin.ts CHANGED
@@ -1,6 +1,5 @@
1
1
  import type { FieldType } from "@riducms/protocol";
2
2
  import type { RegisteredPluginField } from "./field";
3
- import { resolvePluginFields, type ResolvedPluginField } from "./plugin-registry";
4
3
  import { ADMIN_PLUGIN_API_VERSION } from "@riducms/protocol";
5
4
  import type {
6
5
  OperationCapabilities,
@@ -402,9 +401,7 @@ export interface AdminPluginPair {
402
401
  }
403
402
 
404
403
  /** Checked plugin registrations collected for Ridu's admin runtime. Normally consumed by generated code. */
405
- export interface ResolvedAdminPluginPairs {
406
- plugins: readonly AdminPlugin[];
407
- fields: readonly ResolvedPluginField[];
404
+ export interface ResolvedAdminExtensions {
408
405
  rowLabels: readonly RowLabelPlugin[];
409
406
  routes: readonly AdminRoute[];
410
407
  dashboard: readonly AdminDashboardPanel[];
@@ -423,19 +420,14 @@ export interface ResolvedAdminPluginPairs {
423
420
  applicationMessages: PluginMessageCatalog | undefined;
424
421
  }
425
422
 
426
- export type ResolvedAdminExtensions = Omit<ResolvedAdminPluginPairs, "plugins" | "fields">;
427
-
428
423
  /**
429
424
  * Check that installed Go/admin plugin packages agree on identity, API version,
430
425
  * pairing version, routes, assets and field types. Ridu's generated file calls this;
431
426
  * authors should fix mismatched packages rather than editing generated metadata.
432
427
  * Missing imports fail during compilation; incompatible pairs throw here.
433
428
  */
434
- export function resolveAdminPluginPairs(
435
- pairs: readonly AdminPluginPair[]
436
- ): ResolvedAdminPluginPairs {
429
+ export function assertAdminPluginPairs(pairs: readonly AdminPluginPair[]): void {
437
430
  const keys = new Set<string>();
438
- const plugins: AdminPlugin[] = [];
439
431
 
440
432
  for (const { admin, backend } of pairs) {
441
433
  const identity = `${backend.package}#${backend.export}`;
@@ -473,15 +465,7 @@ export function resolveAdminPluginPairs(
473
465
  Object.keys(admin.fields ?? {}).sort()
474
466
  );
475
467
  keys.add(backend.key);
476
- plugins.push(admin);
477
468
  }
478
- const extensions = resolveAdminExtensions(plugins);
479
-
480
- return {
481
- plugins: Object.freeze(plugins),
482
- fields: resolvePluginFields(plugins),
483
- ...extensions,
484
- };
485
469
  }
486
470
 
487
471
  /**