Skip to content

Commit b7f9199

Browse files
committed
chore: resource registry
1 parent 3e2badc commit b7f9199

File tree

4 files changed

+433
-1
lines changed

4 files changed

+433
-1
lines changed

packages/appkit/src/core/appkit.ts

Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import type {
22
BasePlugin,
3+
BasePluginConfig,
34
CacheConfig,
45
InputPluginMap,
56
OptionalConfigPluginDef,
@@ -9,9 +10,18 @@ import type {
910
} from "shared";
1011
import { CacheManager } from "../cache";
1112
import { ServiceContext } from "../context";
13+
import { ConfigurationError } from "../errors";
14+
import { createLogger } from "../logging";
15+
import {
16+
getPluginManifest,
17+
ResourceRegistry,
18+
type ResourceRequirement,
19+
} from "../registry";
1220
import type { TelemetryConfig } from "../telemetry";
1321
import { TelemetryManager } from "../telemetry";
1422

23+
const logger = createLogger("appkit");
24+
1525
export class AppKit<TPlugins extends InputPluginMap> {
1626
#pluginInstances: Record<string, BasePlugin> = {};
1727
#setupPromises: Promise<void>[] = [];
@@ -152,6 +162,90 @@ export class AppKit<TPlugins extends InputPluginMap> {
152162
await ServiceContext.initialize();
153163

154164
const rawPlugins = config.plugins as T;
165+
166+
// Phase 1a: Collect resource requirements from all plugins
167+
const registry = ResourceRegistry.getInstance();
168+
registry.clear(); // Clear any previous state
169+
170+
for (const pluginData of rawPlugins) {
171+
if (!pluginData?.plugin) continue;
172+
173+
const pluginName = pluginData.name;
174+
175+
// Load manifest and register static resources
176+
try {
177+
const manifest = getPluginManifest(pluginData.plugin);
178+
179+
// Register required resources
180+
for (const resource of manifest.resources.required) {
181+
registry.register(pluginName, { ...resource, required: true });
182+
}
183+
184+
// Register optional resources
185+
for (const resource of manifest.resources.optional || []) {
186+
registry.register(pluginName, { ...resource, required: false });
187+
}
188+
189+
// Check for runtime resource requirements
190+
if (typeof pluginData.plugin.getResourceRequirements === "function") {
191+
const runtimeResources = pluginData.plugin.getResourceRequirements(
192+
pluginData.config as BasePluginConfig,
193+
);
194+
for (const resource of runtimeResources) {
195+
// Cast from shared's ResourceRequirement to registry's ResourceRequirement
196+
// The shared type has looser typing (string) vs registry (ResourceType enum)
197+
registry.register(pluginName, resource as ResourceRequirement);
198+
}
199+
}
200+
201+
logger.debug(
202+
"Collected resources from plugin %s: %d total",
203+
pluginName,
204+
registry.getByPlugin(pluginName).length,
205+
);
206+
} catch (error) {
207+
// Plugin doesn't have a manifest - this is allowed for legacy plugins
208+
// or plugins that don't declare resources
209+
logger.debug(
210+
"Plugin %s has no manifest or invalid manifest: %s",
211+
pluginName,
212+
error instanceof Error ? error.message : String(error),
213+
);
214+
}
215+
}
216+
217+
// Phase 1b: Validate resources
218+
const validation = registry.validate();
219+
const isDevelopment = process.env.NODE_ENV === "development";
220+
221+
if (!validation.valid) {
222+
const errorMessage = ResourceRegistry.formatMissingResources(
223+
validation.missing,
224+
);
225+
226+
if (isDevelopment) {
227+
// In development mode, warn but continue
228+
logger.warn(
229+
"Missing resources detected (continuing in dev mode):\n%s",
230+
errorMessage,
231+
);
232+
} else {
233+
// In production, throw error
234+
throw new ConfigurationError(errorMessage, {
235+
context: {
236+
missingResources: validation.missing.map((r) => ({
237+
type: r.type,
238+
alias: r.alias,
239+
plugin: r.plugin,
240+
env: r.env,
241+
})),
242+
},
243+
});
244+
}
245+
} else if (registry.size() > 0) {
246+
logger.debug("All %d resources validated successfully", registry.size());
247+
}
248+
155249
const preparedPlugins = AppKit.preparePlugins(rawPlugins);
156250
const mergedConfig = {
157251
plugins: preparedPlugins,

packages/appkit/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ export type {
4444
export {
4545
getPluginManifest,
4646
getResourceRequirements,
47+
ResourceRegistry,
4748
ResourceType,
4849
} from "./registry";
4950
// Telemetry (for advanced custom telemetry)

packages/appkit/src/registry/index.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,10 @@
77
* Components:
88
* - Type definitions for resources, manifests, and validation
99
* - Manifest loader for reading plugin declarations
10-
* - (Future) ResourceRegistry singleton for tracking requirements
10+
* - ResourceRegistry singleton for tracking requirements across all plugins
1111
* - (Future) Config generators for app.yaml, databricks.yml, .env.example
1212
*/
1313

1414
export { getPluginManifest, getResourceRequirements } from "./manifest-loader";
15+
export { ResourceRegistry } from "./resource-registry";
1516
export * from "./types";

0 commit comments

Comments
 (0)