@databricks/appkit 0.36.0 → 0.38.0
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/CLAUDE.md +3 -2
- package/NOTICE.md +1 -0
- package/dist/appkit/package.js +1 -1
- package/dist/cache/index.js +1 -2
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/storage/persistent.js +1 -2
- package/dist/cache/storage/persistent.js.map +1 -1
- package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
- package/dist/cli/commands/plugin/create/resource-defaults.js +2 -1
- package/dist/cli/commands/plugin/create/resource-defaults.js.map +1 -1
- package/dist/cli/commands/plugin/schema-resources.js +34 -51
- package/dist/cli/commands/plugin/schema-resources.js.map +1 -1
- package/dist/cli/commands/plugin/sync/sync.js +20 -6
- package/dist/cli/commands/plugin/sync/sync.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate-manifest.js +71 -157
- package/dist/cli/commands/plugin/validate/validate-manifest.js.map +1 -1
- package/dist/cli/commands/plugin/validate/validate.js +2 -2
- package/dist/cli/commands/plugin/validate/validate.js.map +1 -1
- package/dist/cli/commands/setup.js +2 -2
- package/dist/cli/commands/setup.js.map +1 -1
- package/dist/connectors/index.js +0 -1
- package/dist/connectors/jobs/client.js +1 -2
- package/dist/connectors/jobs/client.js.map +1 -1
- package/dist/connectors/lakebase/routing-pool.js +1 -2
- package/dist/connectors/lakebase/routing-pool.js.map +1 -1
- package/dist/connectors/sql-warehouse/client.js +1 -2
- package/dist/connectors/sql-warehouse/client.js.map +1 -1
- package/dist/context/execution-context.js +9 -13
- package/dist/context/execution-context.js.map +1 -1
- package/dist/context/index.js +3 -13
- package/dist/context/service-context.js +137 -136
- package/dist/context/service-context.js.map +1 -1
- package/dist/context/user-context.js +1 -5
- package/dist/context/user-context.js.map +1 -1
- package/dist/core/agent/create-agent.js +1 -2
- package/dist/core/agent/create-agent.js.map +1 -1
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +9 -37
- package/dist/core/appkit.js.map +1 -1
- package/dist/errors/authentication.js +44 -40
- package/dist/errors/authentication.js.map +1 -1
- package/dist/errors/base.js +86 -70
- package/dist/errors/base.js.map +1 -1
- package/dist/errors/configuration.js +70 -66
- package/dist/errors/configuration.js.map +1 -1
- package/dist/errors/connection.js +50 -46
- package/dist/errors/connection.js.map +1 -1
- package/dist/errors/execution.js +47 -43
- package/dist/errors/execution.js.map +1 -1
- package/dist/errors/index.js +10 -27
- package/dist/errors/initialization.js +38 -34
- package/dist/errors/initialization.js.map +1 -1
- package/dist/errors/server.js +34 -31
- package/dist/errors/server.js.map +1 -1
- package/dist/errors/tunnel.js +47 -43
- package/dist/errors/tunnel.js.map +1 -1
- package/dist/errors/validation.js +41 -37
- package/dist/errors/validation.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -9
- package/dist/plugin/dev-reader.js +1 -2
- package/dist/plugin/dev-reader.js.map +1 -1
- package/dist/plugin/interceptors/retry.js +1 -2
- package/dist/plugin/interceptors/retry.js.map +1 -1
- package/dist/plugin/interceptors/telemetry.js +1 -2
- package/dist/plugin/interceptors/telemetry.js.map +1 -1
- package/dist/plugin/plugin.d.ts +12 -4
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +59 -28
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugins/agents/agents.d.ts +56 -3
- package/dist/plugins/agents/agents.d.ts.map +1 -1
- package/dist/plugins/agents/agents.js +160 -8
- package/dist/plugins/agents/agents.js.map +1 -1
- package/dist/plugins/agents/schemas.js +0 -1
- package/dist/plugins/agents/schemas.js.map +1 -1
- package/dist/plugins/analytics/analytics.js +1 -2
- package/dist/plugins/analytics/analytics.js.map +1 -1
- package/dist/plugins/analytics/manifest.js +9 -1
- package/dist/plugins/analytics/query.js +2 -4
- package/dist/plugins/analytics/query.js.map +1 -1
- package/dist/plugins/files/manifest.js +7 -1
- package/dist/plugins/files/plugin.js +3 -6
- package/dist/plugins/files/plugin.js.map +1 -1
- package/dist/plugins/genie/genie.js +1 -2
- package/dist/plugins/genie/genie.js.map +1 -1
- package/dist/plugins/genie/manifest.js +7 -1
- package/dist/plugins/jobs/plugin.js +2 -4
- package/dist/plugins/jobs/plugin.js.map +1 -1
- package/dist/plugins/lakebase/lakebase.js +1 -2
- package/dist/plugins/lakebase/lakebase.js.map +1 -1
- package/dist/plugins/lakebase/manifest.js +26 -4
- package/dist/plugins/server/index.js +1 -2
- package/dist/plugins/server/index.js.map +1 -1
- package/dist/plugins/server/vite-dev-server.js +1 -2
- package/dist/plugins/server/vite-dev-server.js.map +1 -1
- package/dist/plugins/serving/serving.js +1 -2
- package/dist/plugins/serving/serving.js.map +1 -1
- package/dist/registry/index.d.ts +1 -1
- package/dist/registry/manifest-loader.d.ts +4 -4
- package/dist/registry/manifest-loader.d.ts.map +1 -1
- package/dist/registry/manifest-loader.js +1 -2
- package/dist/registry/manifest-loader.js.map +1 -1
- package/dist/registry/resource-registry.js +1 -2
- package/dist/registry/resource-registry.js.map +1 -1
- package/dist/registry/types.d.ts +21 -7
- package/dist/registry/types.d.ts.map +1 -1
- package/dist/registry/types.generated.d.ts +1 -1
- package/dist/registry/types.generated.js +1 -1
- package/dist/registry/types.generated.js.map +1 -1
- package/dist/registry/types.js +3 -3
- package/dist/registry/types.js.map +1 -1
- package/dist/schemas/manifest.d.ts +1139 -0
- package/dist/schemas/manifest.d.ts.map +1 -0
- package/dist/schemas/manifest.js +524 -0
- package/dist/schemas/manifest.js.map +1 -0
- package/dist/shared/src/index.d.ts +1 -1
- package/dist/shared/src/plugin.d.ts +38 -7
- package/dist/shared/src/plugin.d.ts.map +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +1110 -0
- package/dist/shared/src/schemas/manifest.d.ts.map +1 -0
- package/dist/stream/arrow-stream-processor.js +1 -2
- package/dist/stream/arrow-stream-processor.js.map +1 -1
- package/dist/stream/buffers.js +1 -2
- package/dist/stream/buffers.js.map +1 -1
- package/docs/api/appkit/Enumeration.ResourceType.md +1 -1
- package/docs/api/appkit/Interface.PluginManifest.md +57 -3
- package/docs/api/appkit/Interface.ResourceEntry.md +6 -4
- package/docs/api/appkit/Interface.ResourceRequirement.md +7 -61
- package/docs/api/appkit/TypeAlias.ResourceFieldEntry.md +6 -0
- package/docs/api/appkit/Variable.agents.md +1 -1
- package/docs/api/appkit.md +12 -12
- package/docs/app-management.md +1 -1
- package/docs/development/ai-assisted-development.md +2 -2
- package/docs/development/local-development.md +1 -1
- package/docs/development/remote-bridge.md +1 -1
- package/docs/development/templates.md +118 -12
- package/docs/development.md +1 -1
- package/docs/plugins/agents.md +8 -4
- package/docs/plugins/custom-plugins.md +33 -23
- package/docs/plugins/lakebase.md +1 -1
- package/docs/plugins/manifest.md +293 -0
- package/docs.md +2 -2
- package/llms.txt +3 -2
- package/package.json +3 -3
- package/sbom.cdx.json +1 -1
- package/dist/_virtual/_rolldown/runtime.js +0 -7
- package/dist/connectors/lakebase-v1/client.js +0 -15
- package/dist/connectors/lakebase-v1/client.js.map +0 -1
- package/dist/connectors/lakebase-v1/index.js +0 -3
- package/dist/context/index.js.map +0 -1
- package/dist/errors/index.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/schemas/plugin-manifest.generated.d.ts +0 -182
- package/dist/schemas/plugin-manifest.generated.d.ts.map +0 -1
- package/dist/schemas/plugin-manifest.schema.json +0 -489
- package/dist/schemas/template-plugins.schema.json +0 -113
- package/dist/shared/src/schemas/plugin-manifest.generated.d.ts +0 -182
- package/dist/shared/src/schemas/plugin-manifest.generated.d.ts.map +0 -1
- package/docs/api/appkit/Interface.ResourceFieldEntry.md +0 -82
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ResourceFieldEntry } from "../shared/src/schemas/
|
|
1
|
+
import { ResourceFieldEntry } from "../shared/src/schemas/manifest.js";
|
|
2
2
|
import { PluginConstructor } from "../shared/src/plugin.js";
|
|
3
3
|
import "../shared/src/index.js";
|
|
4
4
|
import { ResourcePermission, ResourceType } from "./types.generated.js";
|
|
@@ -34,12 +34,12 @@ declare function getPluginManifest(plugin: PluginConstructor): PluginManifest;
|
|
|
34
34
|
*/
|
|
35
35
|
declare function getResourceRequirements(plugin: PluginConstructor): {
|
|
36
36
|
required: boolean;
|
|
37
|
-
description: string;
|
|
38
|
-
fields: Record<string, ResourceFieldEntry>;
|
|
39
37
|
type: ResourceType;
|
|
38
|
+
description: string;
|
|
39
|
+
permission: ResourcePermission;
|
|
40
40
|
alias: string;
|
|
41
41
|
resourceKey: string;
|
|
42
|
-
|
|
42
|
+
fields: Record<string, ResourceFieldEntry>;
|
|
43
43
|
}[];
|
|
44
44
|
//#endregion
|
|
45
45
|
export { getPluginManifest, getResourceRequirements };
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA
|
|
1
|
+
{"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA"}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { createLogger } from "../logging/logger.js";
|
|
2
2
|
import { ConfigurationError } from "../errors/configuration.js";
|
|
3
|
-
import
|
|
3
|
+
import "../errors/index.js";
|
|
4
4
|
import { PERMISSIONS_BY_TYPE, ResourceType } from "./types.generated.js";
|
|
5
5
|
import "./types.js";
|
|
6
6
|
|
|
7
7
|
//#region src/registry/manifest-loader.ts
|
|
8
|
-
init_errors();
|
|
9
8
|
const logger = createLogger("manifest-loader");
|
|
10
9
|
function normalizeType(s) {
|
|
11
10
|
const v = Object.values(ResourceType).find((x) => x === s);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"manifest-loader.js","names":[],"sources":["../../src/registry/manifest-loader.ts"],"sourcesContent":["import type { PluginConstructor } from \"shared\";\nimport { ConfigurationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport type {\n PluginManifest,\n ResourcePermission,\n ResourceRequirement,\n} from \"./types\";\nimport { PERMISSIONS_BY_TYPE, ResourceType } from \"./types\";\n\nconst logger = createLogger(\"manifest-loader\");\n\n/** Loose resource from shared/manifest (string type and permission). */\ninterface LooseResource {\n type: string;\n alias: string;\n resourceKey: string;\n description: string;\n permission: string;\n fields: Record<string, { env: string; description?: string }>;\n}\n\nfunction normalizeType(s: string): ResourceType {\n const v = Object.values(ResourceType).find((x) => x === s);\n if (v !== undefined) return v;\n throw new ConfigurationError(\n `Invalid resource type: \"${s}\". Valid: ${Object.values(ResourceType).join(\", \")}`,\n );\n}\n\nfunction normalizePermission(\n type: ResourceType,\n s: string,\n): ResourcePermission {\n const allowed = PERMISSIONS_BY_TYPE[type];\n if (allowed.includes(s as ResourcePermission)) return s as ResourcePermission;\n throw new ConfigurationError(\n `Invalid permission \"${s}\" for type ${type}. Valid: ${allowed.join(\", \")}`,\n );\n}\n\nfunction normalizeResource(r: LooseResource): ResourceRequirement {\n const type = normalizeType(r.type);\n const permission = normalizePermission(type, r.permission);\n return {\n ...r,\n type,\n permission,\n required: false,\n };\n}\n\n/**\n * Loads and validates the manifest from a plugin constructor.\n * Normalizes string type/permission to strict ResourceType/ResourcePermission.\n *\n * @param plugin - The plugin constructor class\n * @returns The validated, normalized plugin manifest\n * @throws {ConfigurationError} If the manifest is missing, invalid, or has invalid resource type/permission\n */\nexport function getPluginManifest(plugin: PluginConstructor): PluginManifest {\n const pluginName = plugin.manifest?.name || plugin.name || \"unknown\";\n\n if (!plugin.manifest) {\n throw new ConfigurationError(\n `Plugin ${pluginName} is missing a manifest. All plugins must declare a static manifest property.`,\n );\n }\n\n const raw = plugin.manifest;\n\n if (!raw.name || typeof raw.name !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${pluginName} manifest has missing or invalid 'name' field`,\n );\n }\n\n if (!raw.displayName || typeof raw.displayName !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has missing or invalid 'displayName' field`,\n );\n }\n\n if (!raw.description || typeof raw.description !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has missing or invalid 'description' field`,\n );\n }\n\n if (!raw.resources) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest is missing 'resources' field`,\n );\n }\n\n if (!Array.isArray(raw.resources.required)) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has invalid 'resources.required' field (expected array)`,\n );\n }\n\n if (\n raw.resources.optional !== undefined &&\n !Array.isArray(raw.resources.optional)\n ) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has invalid 'resources.optional' field (expected array)`,\n );\n }\n\n const required = raw.resources.required.map((r) => {\n const norm = normalizeResource(r as LooseResource);\n const { required: _, ...rest } = norm;\n return rest;\n });\n const optional = (raw.resources.optional || []).map((r) => {\n const norm = normalizeResource(r as LooseResource);\n const { required: _, ...rest } = norm;\n return rest;\n });\n\n logger.debug(\n \"Loaded manifest for plugin %s: %d required resources, %d optional resources\",\n raw.name,\n required.length,\n optional.length,\n );\n\n return {\n ...raw,\n resources: { required, optional },\n };\n}\n\n/**\n * Gets the resource requirements from a plugin's manifest.\n *\n * Combines required and optional resources into a single array with the\n * `required` flag set appropriately.\n *\n * @param plugin - The plugin constructor class\n * @returns Combined array of required and optional resources\n * @throws {ConfigurationError} If the plugin manifest is missing or invalid\n *\n * @example\n * ```typescript\n * const resources = getResourceRequirements(AnalyticsPlugin);\n * for (const resource of resources) {\n * console.log(`${resource.type}: ${resource.description} (required: ${resource.required})`);\n * }\n * ```\n */\nexport function getResourceRequirements(plugin: PluginConstructor) {\n const manifest = getPluginManifest(plugin);\n\n const required = manifest.resources.required.map((r) => ({\n ...r,\n required: true,\n }));\n const optional = (manifest.resources.optional || []).map((r) => ({\n ...r,\n required: false,\n }));\n\n return [...required, ...optional];\n}\n\n/**\n * Validates a manifest object structure.\n *\n * @param manifest - The manifest object to validate\n * @returns true if the manifest is valid, false otherwise\n *\n * @internal\n */\nexport function isValidManifest(manifest: unknown): manifest is PluginManifest {\n if (!manifest || typeof manifest !== \"object\") {\n return false;\n }\n\n const m = manifest as Record<string, unknown>;\n\n // Check required fields\n if (typeof m.name !== \"string\") return false;\n if (typeof m.displayName !== \"string\") return false;\n if (typeof m.description !== \"string\") return false;\n\n // Check resources structure\n if (!m.resources || typeof m.resources !== \"object\") return false;\n\n const resources = m.resources as Record<string, unknown>;\n if (!Array.isArray(resources.required)) return false;\n\n // Optional field can be missing or must be an array\n if (resources.optional !== undefined && !Array.isArray(resources.optional)) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";;;;;;;
|
|
1
|
+
{"version":3,"file":"manifest-loader.js","names":[],"sources":["../../src/registry/manifest-loader.ts"],"sourcesContent":["import type { PluginConstructor } from \"shared\";\nimport { ConfigurationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport type {\n PluginManifest,\n ResourcePermission,\n ResourceRequirement,\n} from \"./types\";\nimport { PERMISSIONS_BY_TYPE, ResourceType } from \"./types\";\n\nconst logger = createLogger(\"manifest-loader\");\n\n/** Loose resource from shared/manifest (string type and permission). */\ninterface LooseResource {\n type: string;\n alias: string;\n resourceKey: string;\n description: string;\n permission: string;\n fields: Record<string, { env: string; description?: string }>;\n}\n\nfunction normalizeType(s: string): ResourceType {\n const v = Object.values(ResourceType).find((x) => x === s);\n if (v !== undefined) return v;\n throw new ConfigurationError(\n `Invalid resource type: \"${s}\". Valid: ${Object.values(ResourceType).join(\", \")}`,\n );\n}\n\nfunction normalizePermission(\n type: ResourceType,\n s: string,\n): ResourcePermission {\n const allowed = PERMISSIONS_BY_TYPE[type];\n if (allowed.includes(s as ResourcePermission)) return s as ResourcePermission;\n throw new ConfigurationError(\n `Invalid permission \"${s}\" for type ${type}. Valid: ${allowed.join(\", \")}`,\n );\n}\n\nfunction normalizeResource(r: LooseResource): ResourceRequirement {\n const type = normalizeType(r.type);\n const permission = normalizePermission(type, r.permission);\n return {\n ...r,\n type,\n permission,\n required: false,\n };\n}\n\n/**\n * Loads and validates the manifest from a plugin constructor.\n * Normalizes string type/permission to strict ResourceType/ResourcePermission.\n *\n * @param plugin - The plugin constructor class\n * @returns The validated, normalized plugin manifest\n * @throws {ConfigurationError} If the manifest is missing, invalid, or has invalid resource type/permission\n */\nexport function getPluginManifest(plugin: PluginConstructor): PluginManifest {\n const pluginName = plugin.manifest?.name || plugin.name || \"unknown\";\n\n if (!plugin.manifest) {\n throw new ConfigurationError(\n `Plugin ${pluginName} is missing a manifest. All plugins must declare a static manifest property.`,\n );\n }\n\n const raw = plugin.manifest;\n\n if (!raw.name || typeof raw.name !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${pluginName} manifest has missing or invalid 'name' field`,\n );\n }\n\n if (!raw.displayName || typeof raw.displayName !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has missing or invalid 'displayName' field`,\n );\n }\n\n if (!raw.description || typeof raw.description !== \"string\") {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has missing or invalid 'description' field`,\n );\n }\n\n if (!raw.resources) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest is missing 'resources' field`,\n );\n }\n\n if (!Array.isArray(raw.resources.required)) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has invalid 'resources.required' field (expected array)`,\n );\n }\n\n if (\n raw.resources.optional !== undefined &&\n !Array.isArray(raw.resources.optional)\n ) {\n throw new ConfigurationError(\n `Plugin ${raw.name} manifest has invalid 'resources.optional' field (expected array)`,\n );\n }\n\n const required = raw.resources.required.map((r) => {\n const norm = normalizeResource(r as LooseResource);\n const { required: _, ...rest } = norm;\n return rest;\n });\n const optional = (raw.resources.optional || []).map((r) => {\n const norm = normalizeResource(r as LooseResource);\n const { required: _, ...rest } = norm;\n return rest;\n });\n\n logger.debug(\n \"Loaded manifest for plugin %s: %d required resources, %d optional resources\",\n raw.name,\n required.length,\n optional.length,\n );\n\n return {\n ...raw,\n resources: { required, optional },\n };\n}\n\n/**\n * Gets the resource requirements from a plugin's manifest.\n *\n * Combines required and optional resources into a single array with the\n * `required` flag set appropriately.\n *\n * @param plugin - The plugin constructor class\n * @returns Combined array of required and optional resources\n * @throws {ConfigurationError} If the plugin manifest is missing or invalid\n *\n * @example\n * ```typescript\n * const resources = getResourceRequirements(AnalyticsPlugin);\n * for (const resource of resources) {\n * console.log(`${resource.type}: ${resource.description} (required: ${resource.required})`);\n * }\n * ```\n */\nexport function getResourceRequirements(plugin: PluginConstructor) {\n const manifest = getPluginManifest(plugin);\n\n const required = manifest.resources.required.map((r) => ({\n ...r,\n required: true,\n }));\n const optional = (manifest.resources.optional || []).map((r) => ({\n ...r,\n required: false,\n }));\n\n return [...required, ...optional];\n}\n\n/**\n * Validates a manifest object structure.\n *\n * @param manifest - The manifest object to validate\n * @returns true if the manifest is valid, false otherwise\n *\n * @internal\n */\nexport function isValidManifest(manifest: unknown): manifest is PluginManifest {\n if (!manifest || typeof manifest !== \"object\") {\n return false;\n }\n\n const m = manifest as Record<string, unknown>;\n\n // Check required fields\n if (typeof m.name !== \"string\") return false;\n if (typeof m.displayName !== \"string\") return false;\n if (typeof m.description !== \"string\") return false;\n\n // Check resources structure\n if (!m.resources || typeof m.resources !== \"object\") return false;\n\n const resources = m.resources as Record<string, unknown>;\n if (!Array.isArray(resources.required)) return false;\n\n // Optional field can be missing or must be an array\n if (resources.optional !== undefined && !Array.isArray(resources.optional)) {\n return false;\n }\n\n return true;\n}\n"],"mappings":";;;;;;;AAUA,MAAM,SAAS,aAAa,kBAAkB;AAY9C,SAAS,cAAc,GAAyB;CAC9C,MAAM,IAAI,OAAO,OAAO,aAAa,CAAC,MAAM,MAAM,MAAM,EAAE;AAC1D,KAAI,MAAM,OAAW,QAAO;AAC5B,OAAM,IAAI,mBACR,2BAA2B,EAAE,YAAY,OAAO,OAAO,aAAa,CAAC,KAAK,KAAK,GAChF;;AAGH,SAAS,oBACP,MACA,GACoB;CACpB,MAAM,UAAU,oBAAoB;AACpC,KAAI,QAAQ,SAAS,EAAwB,CAAE,QAAO;AACtD,OAAM,IAAI,mBACR,uBAAuB,EAAE,aAAa,KAAK,WAAW,QAAQ,KAAK,KAAK,GACzE;;AAGH,SAAS,kBAAkB,GAAuC;CAChE,MAAM,OAAO,cAAc,EAAE,KAAK;CAClC,MAAM,aAAa,oBAAoB,MAAM,EAAE,WAAW;AAC1D,QAAO;EACL,GAAG;EACH;EACA;EACA,UAAU;EACX;;;;;;;;;;AAWH,SAAgB,kBAAkB,QAA2C;CAC3E,MAAM,aAAa,OAAO,UAAU,QAAQ,OAAO,QAAQ;AAE3D,KAAI,CAAC,OAAO,SACV,OAAM,IAAI,mBACR,UAAU,WAAW,8EACtB;CAGH,MAAM,MAAM,OAAO;AAEnB,KAAI,CAAC,IAAI,QAAQ,OAAO,IAAI,SAAS,SACnC,OAAM,IAAI,mBACR,UAAU,WAAW,+CACtB;AAGH,KAAI,CAAC,IAAI,eAAe,OAAO,IAAI,gBAAgB,SACjD,OAAM,IAAI,mBACR,UAAU,IAAI,KAAK,sDACpB;AAGH,KAAI,CAAC,IAAI,eAAe,OAAO,IAAI,gBAAgB,SACjD,OAAM,IAAI,mBACR,UAAU,IAAI,KAAK,sDACpB;AAGH,KAAI,CAAC,IAAI,UACP,OAAM,IAAI,mBACR,UAAU,IAAI,KAAK,wCACpB;AAGH,KAAI,CAAC,MAAM,QAAQ,IAAI,UAAU,SAAS,CACxC,OAAM,IAAI,mBACR,UAAU,IAAI,KAAK,mEACpB;AAGH,KACE,IAAI,UAAU,aAAa,UAC3B,CAAC,MAAM,QAAQ,IAAI,UAAU,SAAS,CAEtC,OAAM,IAAI,mBACR,UAAU,IAAI,KAAK,mEACpB;CAGH,MAAM,WAAW,IAAI,UAAU,SAAS,KAAK,MAAM;EAEjD,MAAM,EAAE,UAAU,GAAG,GAAG,SADX,kBAAkB,EAAmB;AAElD,SAAO;GACP;CACF,MAAM,YAAY,IAAI,UAAU,YAAY,EAAE,EAAE,KAAK,MAAM;EAEzD,MAAM,EAAE,UAAU,GAAG,GAAG,SADX,kBAAkB,EAAmB;AAElD,SAAO;GACP;AAEF,QAAO,MACL,+EACA,IAAI,MACJ,SAAS,QACT,SAAS,OACV;AAED,QAAO;EACL,GAAG;EACH,WAAW;GAAE;GAAU;GAAU;EAClC;;;;;;;;;;;;;;;;;;;;AAqBH,SAAgB,wBAAwB,QAA2B;CACjE,MAAM,WAAW,kBAAkB,OAAO;CAE1C,MAAM,WAAW,SAAS,UAAU,SAAS,KAAK,OAAO;EACvD,GAAG;EACH,UAAU;EACX,EAAE;CACH,MAAM,YAAY,SAAS,UAAU,YAAY,EAAE,EAAE,KAAK,OAAO;EAC/D,GAAG;EACH,UAAU;EACX,EAAE;AAEH,QAAO,CAAC,GAAG,UAAU,GAAG,SAAS"}
|
|
@@ -1,12 +1,11 @@
|
|
|
1
1
|
import { createLogger } from "../logging/logger.js";
|
|
2
2
|
import { ConfigurationError } from "../errors/configuration.js";
|
|
3
|
-
import
|
|
3
|
+
import "../errors/index.js";
|
|
4
4
|
import { PERMISSION_HIERARCHY_BY_TYPE } from "./types.generated.js";
|
|
5
5
|
import "./types.js";
|
|
6
6
|
import { getPluginManifest } from "./manifest-loader.js";
|
|
7
7
|
|
|
8
8
|
//#region src/registry/resource-registry.ts
|
|
9
|
-
init_errors();
|
|
10
9
|
const logger = createLogger("resource-registry");
|
|
11
10
|
/**
|
|
12
11
|
* Dedup key for registry: type + resourceKey (machine-stable).
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"resource-registry.js","names":[],"sources":["../../src/registry/resource-registry.ts"],"sourcesContent":["/**\n * Resource Registry\n *\n * Central registry that tracks all resource requirements across all plugins.\n * Provides visibility into Databricks resources needed by the application\n * and handles deduplication when multiple plugins require the same resource\n * (dedup key: type + resourceKey).\n *\n * Use `new ResourceRegistry()` for instance-scoped usage (e.g. createApp).\n * getInstance() / resetInstance() remain for backward compatibility in tests.\n */\n\nimport type { BasePluginConfig, PluginConstructor, PluginData } from \"shared\";\nimport { ConfigurationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { getPluginManifest } from \"./manifest-loader\";\nimport type {\n ResourceEntry,\n ResourcePermission,\n ResourceRequirement,\n ValidationResult,\n} from \"./types\";\nimport { PERMISSION_HIERARCHY_BY_TYPE, type ResourceType } from \"./types\";\n\nconst logger = createLogger(\"resource-registry\");\n\n/**\n * Dedup key for registry: type + resourceKey (machine-stable).\n * alias is for UI/display only.\n */\nfunction getDedupKey(type: string, resourceKey: string): string {\n return `${type}:${resourceKey}`;\n}\n\n/**\n * Returns the most permissive permission for a given resource type.\n * Uses per-type hierarchy; unknown permissions are treated as least permissive.\n */\nfunction getMostPermissivePermission(\n resourceType: ResourceType,\n p1: ResourcePermission,\n p2: ResourcePermission,\n): ResourcePermission {\n const hierarchy = PERMISSION_HIERARCHY_BY_TYPE[resourceType as ResourceType];\n const index1 = hierarchy?.indexOf(p1) ?? -1;\n const index2 = hierarchy?.indexOf(p2) ?? -1;\n return index1 > index2 ? p1 : p2;\n}\n\n/**\n * Central registry for tracking plugin resource requirements.\n * Deduplication uses type + resourceKey (machine-stable); alias is for display only.\n */\nexport class ResourceRegistry {\n private resources: Map<string, ResourceEntry> = new Map();\n\n /**\n * Registers a resource requirement for a plugin.\n * If a resource with the same type+resourceKey already exists, merges them:\n * - Combines plugin names (comma-separated)\n * - Uses the most permissive permission (per-type hierarchy)\n * - Marks as required if any plugin requires it\n * - Combines descriptions if they differ\n * - Merges fields; warns when same field name uses different env vars\n *\n * @param plugin - Name of the plugin registering the resource\n * @param resource - Resource requirement specification\n */\n public register(plugin: string, resource: ResourceRequirement): void {\n const key = getDedupKey(resource.type, resource.resourceKey);\n const existing = this.resources.get(key);\n\n if (existing) {\n // Merge with existing resource\n const merged = this.mergeResources(existing, plugin, resource);\n this.resources.set(key, merged);\n } else {\n // Create new resource entry with permission source tracking\n const entry: ResourceEntry = {\n ...resource,\n plugin,\n resolved: false,\n permissionSources: { [plugin]: resource.permission },\n };\n this.resources.set(key, entry);\n }\n }\n\n /**\n * Collects and registers resource requirements from an array of plugins.\n * For each plugin, loads its manifest (required) and runtime resource requirements.\n *\n * @param rawPlugins - Array of plugin data entries from createApp configuration\n * @throws {ConfigurationError} If any plugin is missing a manifest or manifest is invalid\n */\n public collectResources(\n rawPlugins: PluginData<PluginConstructor, unknown, string>[],\n ): void {\n for (const pluginData of rawPlugins) {\n if (!pluginData?.plugin) continue;\n\n const pluginName = pluginData.name;\n const manifest = getPluginManifest(pluginData.plugin);\n\n // Register required resources\n for (const resource of manifest.resources.required) {\n this.register(pluginName, { ...resource, required: true });\n }\n\n // Register optional resources\n for (const resource of manifest.resources.optional || []) {\n this.register(pluginName, { ...resource, required: false });\n }\n\n // Check for runtime resource requirements\n if (typeof pluginData.plugin.getResourceRequirements === \"function\") {\n const runtimeResources = pluginData.plugin.getResourceRequirements(\n pluginData.config as BasePluginConfig,\n );\n for (const resource of runtimeResources) {\n this.register(pluginName, resource as ResourceRequirement);\n }\n }\n\n logger.debug(\n \"Collected resources from plugin %s: %d total\",\n pluginName,\n this.getByPlugin(pluginName).length,\n );\n }\n }\n\n /**\n * Merges a new resource requirement with an existing entry.\n * Applies intelligent merging logic for conflicting properties.\n */\n private mergeResources(\n existing: ResourceEntry,\n newPlugin: string,\n newResource: ResourceRequirement,\n ): ResourceEntry {\n // Combine plugin names if not already included\n const plugins = existing.plugin.split(\", \");\n if (!plugins.includes(newPlugin)) {\n plugins.push(newPlugin);\n }\n\n // Track per-plugin permission sources\n const permissionSources: Record<string, ResourcePermission> = {\n ...(existing.permissionSources ?? {}),\n [newPlugin]: newResource.permission,\n };\n\n // Use the most permissive permission for this resource type; warn when escalating\n const permission = getMostPermissivePermission(\n existing.type as ResourceType,\n existing.permission,\n newResource.permission,\n );\n\n if (permission !== existing.permission) {\n logger.warn(\n 'Resource %s:%s permission escalated from \"%s\" to \"%s\" due to plugin \"%s\" ' +\n \"(previously requested by: %s). Review plugin permissions to ensure least-privilege.\",\n existing.type,\n existing.resourceKey,\n existing.permission,\n permission,\n newPlugin,\n existing.plugin,\n );\n }\n\n // Mark as required if any plugin requires it\n const required = existing.required || newResource.required;\n\n // Combine descriptions if they differ\n let description = existing.description;\n if (\n newResource.description &&\n newResource.description !== existing.description\n ) {\n if (!existing.description.includes(newResource.description)) {\n description = `${existing.description}; ${newResource.description}`;\n }\n }\n\n // Merge fields: union of field names; warn when same field name uses different env\n const fields = { ...(existing.fields ?? {}) };\n for (const [fieldName, newField] of Object.entries(\n newResource.fields ?? {},\n )) {\n const existingField = fields[fieldName];\n if (existingField) {\n if (existingField.env !== newField.env) {\n logger.warn(\n 'Resource %s:%s field \"%s\": conflicting env vars \"%s\" (from %s) vs \"%s\" (from %s). Using first.',\n existing.type,\n existing.resourceKey,\n fieldName,\n existingField.env,\n existing.plugin,\n newField.env,\n newPlugin,\n );\n }\n // keep existing\n } else {\n fields[fieldName] = newField;\n }\n }\n\n return {\n ...existing,\n plugin: plugins.join(\", \"),\n permission,\n permissionSources,\n required,\n description,\n fields,\n };\n }\n\n /**\n * Retrieves all registered resources.\n * Returns a copy of the array to prevent external mutations.\n *\n * @returns Array of all registered resource entries\n */\n public getAll(): ResourceEntry[] {\n return Array.from(this.resources.values());\n }\n\n /**\n * Gets a specific resource by type and resourceKey (dedup key).\n *\n * @param type - Resource type\n * @param resourceKey - Stable machine key (not alias; alias is for display only)\n * @returns The resource entry if found, undefined otherwise\n */\n public get(type: string, resourceKey: string): ResourceEntry | undefined {\n return this.resources.get(getDedupKey(type, resourceKey));\n }\n\n /**\n * Clears all registered resources.\n * Useful for testing or when rebuilding the registry.\n */\n public clear(): void {\n this.resources.clear();\n }\n\n /**\n * Returns the number of registered resources.\n */\n public size(): number {\n return this.resources.size;\n }\n\n /**\n * Gets all resources required by a specific plugin.\n *\n * @param pluginName - Name of the plugin\n * @returns Array of resources where the plugin is listed as a requester\n */\n public getByPlugin(pluginName: string): ResourceEntry[] {\n return this.getAll().filter((entry) =>\n entry.plugin.split(\", \").includes(pluginName),\n );\n }\n\n /**\n * Gets all required resources (where required=true).\n *\n * @returns Array of required resource entries\n */\n public getRequired(): ResourceEntry[] {\n return this.getAll().filter((entry) => entry.required);\n }\n\n /**\n * Gets all optional resources (where required=false).\n *\n * @returns Array of optional resource entries\n */\n public getOptional(): ResourceEntry[] {\n return this.getAll().filter((entry) => !entry.required);\n }\n\n /**\n * Validates all registered resources against the environment.\n *\n * Checks each resource's field environment variables to determine if it's resolved.\n * Updates the `resolved` and `values` fields on each resource entry.\n *\n * Only required resources affect the `valid` status - optional resources\n * are checked but don't cause validation failure.\n *\n * @returns ValidationResult with validity status, missing resources, and all resources\n *\n * @example\n * ```typescript\n * const registry = ResourceRegistry.getInstance();\n * const result = registry.validate();\n *\n * if (!result.valid) {\n * console.error(\"Missing resources:\", result.missing.map(r => Object.values(r.fields).map(f => f.env)));\n * }\n * ```\n */\n public validate(): ValidationResult {\n const missing: ResourceEntry[] = [];\n\n for (const entry of this.resources.values()) {\n const values: Record<string, string> = {};\n let allSet = true;\n for (const [fieldName, fieldDef] of Object.entries(entry.fields)) {\n if (!fieldDef.env) continue;\n const val = process.env[fieldDef.env];\n if (val !== undefined && val !== \"\") {\n values[fieldName] = val;\n } else {\n allSet = false;\n }\n }\n if (allSet) {\n entry.resolved = true;\n entry.values = values;\n logger.debug(\n \"Resource %s:%s resolved from fields\",\n entry.type,\n entry.alias,\n );\n } else {\n entry.resolved = false;\n entry.values = Object.keys(values).length > 0 ? values : undefined;\n if (entry.required) {\n missing.push(entry);\n logger.debug(\n \"Required resource %s:%s missing (fields: %s)\",\n entry.type,\n entry.alias,\n Object.keys(entry.fields).join(\", \"),\n );\n } else {\n logger.debug(\n \"Optional resource %s:%s not configured (fields: %s)\",\n entry.type,\n entry.alias,\n Object.keys(entry.fields).join(\", \"),\n );\n }\n }\n }\n\n return {\n valid: missing.length === 0,\n missing,\n all: this.getAll(),\n };\n }\n\n /**\n * Validates all registered resources and enforces the result.\n *\n * - In production: throws a {@link ConfigurationError} if any required resources are missing.\n * - In development (`NODE_ENV=development`): logs a warning but continues, unless\n * `APPKIT_STRICT_VALIDATION=true` is set, in which case throws like production.\n * - When all resources are valid: logs a debug message with the count.\n *\n * @returns ValidationResult with validity status, missing resources, and all resources\n * @throws {ConfigurationError} In production when required resources are missing, or in dev when APPKIT_STRICT_VALIDATION=true\n */\n public enforceValidation(): ValidationResult {\n const validation = this.validate();\n const isDevelopment = process.env.NODE_ENV === \"development\";\n const strictValidation =\n process.env.APPKIT_STRICT_VALIDATION === \"true\" ||\n process.env.APPKIT_STRICT_VALIDATION === \"1\";\n\n if (!validation.valid) {\n const errorMessage = ResourceRegistry.formatMissingResources(\n validation.missing,\n );\n\n const shouldThrow = !isDevelopment || strictValidation;\n\n if (shouldThrow) {\n throw new ConfigurationError(errorMessage, {\n context: {\n missingResources: validation.missing.map((r) => ({\n type: r.type,\n alias: r.alias,\n plugin: r.plugin,\n envVars: Object.values(r.fields).map((f) => f.env),\n })),\n },\n });\n }\n\n // Dev mode without strict: use a visually prominent box so the warning can't be missed\n const banner = ResourceRegistry.formatDevWarningBanner(\n validation.missing,\n );\n logger.warn(\"\\n%s\", banner);\n } else if (this.size() > 0) {\n logger.debug(\"All %d resources validated successfully\", this.size());\n }\n\n return validation;\n }\n\n /**\n * Formats missing resources into a human-readable error message.\n *\n * @param missing - Array of missing resource entries\n * @returns Formatted error message string\n */\n public static formatMissingResources(missing: ResourceEntry[]): string {\n if (missing.length === 0) {\n return \"No missing resources\";\n }\n\n const lines = missing.map((entry) => {\n const envVars = Object.values(entry.fields).map((f) => f.env);\n const envHint = ` (set ${envVars.join(\", \")})`;\n return ` - ${entry.type}:${entry.alias} [${entry.plugin}]${envHint}`;\n });\n\n return `Missing required resources:\\n${lines.join(\"\\n\")}`;\n }\n\n /**\n * Formats a highly visible warning banner for dev-mode missing resources.\n * Uses box drawing to ensure the message is impossible to miss in scrolling logs.\n *\n * @param missing - Array of missing resource entries\n * @returns Formatted banner string\n */\n public static formatDevWarningBanner(missing: ResourceEntry[]): string {\n const contentLines: string[] = [\n \"MISSING REQUIRED RESOURCES (dev mode — would fail in production)\",\n \"\",\n ];\n\n for (const entry of missing) {\n const envVars = Object.values(entry.fields).map((f) => f.env);\n contentLines.push(\n ` ${entry.type}:${entry.alias} (plugin: ${entry.plugin})`,\n );\n contentLines.push(` Set: ${envVars.join(\", \")}`);\n }\n\n contentLines.push(\"\");\n contentLines.push(\n \"Add these to your .env file or environment to suppress this warning.\",\n );\n\n const maxLen = Math.max(...contentLines.map((l) => l.length));\n const border = \"=\".repeat(maxLen + 4);\n\n const boxed = contentLines.map((line) => `| ${line.padEnd(maxLen)} |`);\n\n return [border, ...boxed, border].join(\"\\n\");\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"resource-registry.js","names":[],"sources":["../../src/registry/resource-registry.ts"],"sourcesContent":["/**\n * Resource Registry\n *\n * Central registry that tracks all resource requirements across all plugins.\n * Provides visibility into Databricks resources needed by the application\n * and handles deduplication when multiple plugins require the same resource\n * (dedup key: type + resourceKey).\n *\n * Use `new ResourceRegistry()` for instance-scoped usage (e.g. createApp).\n * getInstance() / resetInstance() remain for backward compatibility in tests.\n */\n\nimport type { BasePluginConfig, PluginConstructor, PluginData } from \"shared\";\nimport { ConfigurationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { getPluginManifest } from \"./manifest-loader\";\nimport type {\n ResourceEntry,\n ResourcePermission,\n ResourceRequirement,\n ValidationResult,\n} from \"./types\";\nimport { PERMISSION_HIERARCHY_BY_TYPE, type ResourceType } from \"./types\";\n\nconst logger = createLogger(\"resource-registry\");\n\n/**\n * Dedup key for registry: type + resourceKey (machine-stable).\n * alias is for UI/display only.\n */\nfunction getDedupKey(type: string, resourceKey: string): string {\n return `${type}:${resourceKey}`;\n}\n\n/**\n * Returns the most permissive permission for a given resource type.\n * Uses per-type hierarchy; unknown permissions are treated as least permissive.\n */\nfunction getMostPermissivePermission(\n resourceType: ResourceType,\n p1: ResourcePermission,\n p2: ResourcePermission,\n): ResourcePermission {\n const hierarchy = PERMISSION_HIERARCHY_BY_TYPE[resourceType as ResourceType];\n const index1 = hierarchy?.indexOf(p1) ?? -1;\n const index2 = hierarchy?.indexOf(p2) ?? -1;\n return index1 > index2 ? p1 : p2;\n}\n\n/**\n * Central registry for tracking plugin resource requirements.\n * Deduplication uses type + resourceKey (machine-stable); alias is for display only.\n */\nexport class ResourceRegistry {\n private resources: Map<string, ResourceEntry> = new Map();\n\n /**\n * Registers a resource requirement for a plugin.\n * If a resource with the same type+resourceKey already exists, merges them:\n * - Combines plugin names (comma-separated)\n * - Uses the most permissive permission (per-type hierarchy)\n * - Marks as required if any plugin requires it\n * - Combines descriptions if they differ\n * - Merges fields; warns when same field name uses different env vars\n *\n * @param plugin - Name of the plugin registering the resource\n * @param resource - Resource requirement specification\n */\n public register(plugin: string, resource: ResourceRequirement): void {\n const key = getDedupKey(resource.type, resource.resourceKey);\n const existing = this.resources.get(key);\n\n if (existing) {\n // Merge with existing resource\n const merged = this.mergeResources(existing, plugin, resource);\n this.resources.set(key, merged);\n } else {\n // Create new resource entry with permission source tracking\n const entry: ResourceEntry = {\n ...resource,\n plugin,\n resolved: false,\n permissionSources: { [plugin]: resource.permission },\n };\n this.resources.set(key, entry);\n }\n }\n\n /**\n * Collects and registers resource requirements from an array of plugins.\n * For each plugin, loads its manifest (required) and runtime resource requirements.\n *\n * @param rawPlugins - Array of plugin data entries from createApp configuration\n * @throws {ConfigurationError} If any plugin is missing a manifest or manifest is invalid\n */\n public collectResources(\n rawPlugins: PluginData<PluginConstructor, unknown, string>[],\n ): void {\n for (const pluginData of rawPlugins) {\n if (!pluginData?.plugin) continue;\n\n const pluginName = pluginData.name;\n const manifest = getPluginManifest(pluginData.plugin);\n\n // Register required resources\n for (const resource of manifest.resources.required) {\n this.register(pluginName, { ...resource, required: true });\n }\n\n // Register optional resources\n for (const resource of manifest.resources.optional || []) {\n this.register(pluginName, { ...resource, required: false });\n }\n\n // Check for runtime resource requirements\n if (typeof pluginData.plugin.getResourceRequirements === \"function\") {\n const runtimeResources = pluginData.plugin.getResourceRequirements(\n pluginData.config as BasePluginConfig,\n );\n for (const resource of runtimeResources) {\n this.register(pluginName, resource as ResourceRequirement);\n }\n }\n\n logger.debug(\n \"Collected resources from plugin %s: %d total\",\n pluginName,\n this.getByPlugin(pluginName).length,\n );\n }\n }\n\n /**\n * Merges a new resource requirement with an existing entry.\n * Applies intelligent merging logic for conflicting properties.\n */\n private mergeResources(\n existing: ResourceEntry,\n newPlugin: string,\n newResource: ResourceRequirement,\n ): ResourceEntry {\n // Combine plugin names if not already included\n const plugins = existing.plugin.split(\", \");\n if (!plugins.includes(newPlugin)) {\n plugins.push(newPlugin);\n }\n\n // Track per-plugin permission sources\n const permissionSources: Record<string, ResourcePermission> = {\n ...(existing.permissionSources ?? {}),\n [newPlugin]: newResource.permission,\n };\n\n // Use the most permissive permission for this resource type; warn when escalating\n const permission = getMostPermissivePermission(\n existing.type as ResourceType,\n existing.permission,\n newResource.permission,\n );\n\n if (permission !== existing.permission) {\n logger.warn(\n 'Resource %s:%s permission escalated from \"%s\" to \"%s\" due to plugin \"%s\" ' +\n \"(previously requested by: %s). Review plugin permissions to ensure least-privilege.\",\n existing.type,\n existing.resourceKey,\n existing.permission,\n permission,\n newPlugin,\n existing.plugin,\n );\n }\n\n // Mark as required if any plugin requires it\n const required = existing.required || newResource.required;\n\n // Combine descriptions if they differ\n let description = existing.description;\n if (\n newResource.description &&\n newResource.description !== existing.description\n ) {\n if (!existing.description.includes(newResource.description)) {\n description = `${existing.description}; ${newResource.description}`;\n }\n }\n\n // Merge fields: union of field names; warn when same field name uses different env\n const fields = { ...(existing.fields ?? {}) };\n for (const [fieldName, newField] of Object.entries(\n newResource.fields ?? {},\n )) {\n const existingField = fields[fieldName];\n if (existingField) {\n if (existingField.env !== newField.env) {\n logger.warn(\n 'Resource %s:%s field \"%s\": conflicting env vars \"%s\" (from %s) vs \"%s\" (from %s). Using first.',\n existing.type,\n existing.resourceKey,\n fieldName,\n existingField.env,\n existing.plugin,\n newField.env,\n newPlugin,\n );\n }\n // keep existing\n } else {\n fields[fieldName] = newField;\n }\n }\n\n return {\n ...existing,\n plugin: plugins.join(\", \"),\n permission,\n permissionSources,\n required,\n description,\n fields,\n };\n }\n\n /**\n * Retrieves all registered resources.\n * Returns a copy of the array to prevent external mutations.\n *\n * @returns Array of all registered resource entries\n */\n public getAll(): ResourceEntry[] {\n return Array.from(this.resources.values());\n }\n\n /**\n * Gets a specific resource by type and resourceKey (dedup key).\n *\n * @param type - Resource type\n * @param resourceKey - Stable machine key (not alias; alias is for display only)\n * @returns The resource entry if found, undefined otherwise\n */\n public get(type: string, resourceKey: string): ResourceEntry | undefined {\n return this.resources.get(getDedupKey(type, resourceKey));\n }\n\n /**\n * Clears all registered resources.\n * Useful for testing or when rebuilding the registry.\n */\n public clear(): void {\n this.resources.clear();\n }\n\n /**\n * Returns the number of registered resources.\n */\n public size(): number {\n return this.resources.size;\n }\n\n /**\n * Gets all resources required by a specific plugin.\n *\n * @param pluginName - Name of the plugin\n * @returns Array of resources where the plugin is listed as a requester\n */\n public getByPlugin(pluginName: string): ResourceEntry[] {\n return this.getAll().filter((entry) =>\n entry.plugin.split(\", \").includes(pluginName),\n );\n }\n\n /**\n * Gets all required resources (where required=true).\n *\n * @returns Array of required resource entries\n */\n public getRequired(): ResourceEntry[] {\n return this.getAll().filter((entry) => entry.required);\n }\n\n /**\n * Gets all optional resources (where required=false).\n *\n * @returns Array of optional resource entries\n */\n public getOptional(): ResourceEntry[] {\n return this.getAll().filter((entry) => !entry.required);\n }\n\n /**\n * Validates all registered resources against the environment.\n *\n * Checks each resource's field environment variables to determine if it's resolved.\n * Updates the `resolved` and `values` fields on each resource entry.\n *\n * Only required resources affect the `valid` status - optional resources\n * are checked but don't cause validation failure.\n *\n * @returns ValidationResult with validity status, missing resources, and all resources\n *\n * @example\n * ```typescript\n * const registry = ResourceRegistry.getInstance();\n * const result = registry.validate();\n *\n * if (!result.valid) {\n * console.error(\"Missing resources:\", result.missing.map(r => Object.values(r.fields).map(f => f.env)));\n * }\n * ```\n */\n public validate(): ValidationResult {\n const missing: ResourceEntry[] = [];\n\n for (const entry of this.resources.values()) {\n const values: Record<string, string> = {};\n let allSet = true;\n for (const [fieldName, fieldDef] of Object.entries(entry.fields)) {\n if (!fieldDef.env) continue;\n const val = process.env[fieldDef.env];\n if (val !== undefined && val !== \"\") {\n values[fieldName] = val;\n } else {\n allSet = false;\n }\n }\n if (allSet) {\n entry.resolved = true;\n entry.values = values;\n logger.debug(\n \"Resource %s:%s resolved from fields\",\n entry.type,\n entry.alias,\n );\n } else {\n entry.resolved = false;\n entry.values = Object.keys(values).length > 0 ? values : undefined;\n if (entry.required) {\n missing.push(entry);\n logger.debug(\n \"Required resource %s:%s missing (fields: %s)\",\n entry.type,\n entry.alias,\n Object.keys(entry.fields).join(\", \"),\n );\n } else {\n logger.debug(\n \"Optional resource %s:%s not configured (fields: %s)\",\n entry.type,\n entry.alias,\n Object.keys(entry.fields).join(\", \"),\n );\n }\n }\n }\n\n return {\n valid: missing.length === 0,\n missing,\n all: this.getAll(),\n };\n }\n\n /**\n * Validates all registered resources and enforces the result.\n *\n * - In production: throws a {@link ConfigurationError} if any required resources are missing.\n * - In development (`NODE_ENV=development`): logs a warning but continues, unless\n * `APPKIT_STRICT_VALIDATION=true` is set, in which case throws like production.\n * - When all resources are valid: logs a debug message with the count.\n *\n * @returns ValidationResult with validity status, missing resources, and all resources\n * @throws {ConfigurationError} In production when required resources are missing, or in dev when APPKIT_STRICT_VALIDATION=true\n */\n public enforceValidation(): ValidationResult {\n const validation = this.validate();\n const isDevelopment = process.env.NODE_ENV === \"development\";\n const strictValidation =\n process.env.APPKIT_STRICT_VALIDATION === \"true\" ||\n process.env.APPKIT_STRICT_VALIDATION === \"1\";\n\n if (!validation.valid) {\n const errorMessage = ResourceRegistry.formatMissingResources(\n validation.missing,\n );\n\n const shouldThrow = !isDevelopment || strictValidation;\n\n if (shouldThrow) {\n throw new ConfigurationError(errorMessage, {\n context: {\n missingResources: validation.missing.map((r) => ({\n type: r.type,\n alias: r.alias,\n plugin: r.plugin,\n envVars: Object.values(r.fields).map((f) => f.env),\n })),\n },\n });\n }\n\n // Dev mode without strict: use a visually prominent box so the warning can't be missed\n const banner = ResourceRegistry.formatDevWarningBanner(\n validation.missing,\n );\n logger.warn(\"\\n%s\", banner);\n } else if (this.size() > 0) {\n logger.debug(\"All %d resources validated successfully\", this.size());\n }\n\n return validation;\n }\n\n /**\n * Formats missing resources into a human-readable error message.\n *\n * @param missing - Array of missing resource entries\n * @returns Formatted error message string\n */\n public static formatMissingResources(missing: ResourceEntry[]): string {\n if (missing.length === 0) {\n return \"No missing resources\";\n }\n\n const lines = missing.map((entry) => {\n const envVars = Object.values(entry.fields).map((f) => f.env);\n const envHint = ` (set ${envVars.join(\", \")})`;\n return ` - ${entry.type}:${entry.alias} [${entry.plugin}]${envHint}`;\n });\n\n return `Missing required resources:\\n${lines.join(\"\\n\")}`;\n }\n\n /**\n * Formats a highly visible warning banner for dev-mode missing resources.\n * Uses box drawing to ensure the message is impossible to miss in scrolling logs.\n *\n * @param missing - Array of missing resource entries\n * @returns Formatted banner string\n */\n public static formatDevWarningBanner(missing: ResourceEntry[]): string {\n const contentLines: string[] = [\n \"MISSING REQUIRED RESOURCES (dev mode — would fail in production)\",\n \"\",\n ];\n\n for (const entry of missing) {\n const envVars = Object.values(entry.fields).map((f) => f.env);\n contentLines.push(\n ` ${entry.type}:${entry.alias} (plugin: ${entry.plugin})`,\n );\n contentLines.push(` Set: ${envVars.join(\", \")}`);\n }\n\n contentLines.push(\"\");\n contentLines.push(\n \"Add these to your .env file or environment to suppress this warning.\",\n );\n\n const maxLen = Math.max(...contentLines.map((l) => l.length));\n const border = \"=\".repeat(maxLen + 4);\n\n const boxed = contentLines.map((line) => `| ${line.padEnd(maxLen)} |`);\n\n return [border, ...boxed, border].join(\"\\n\");\n }\n}\n"],"mappings":"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"}
|
package/dist/registry/types.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { ResourceFieldEntry } from "../shared/src/schemas/
|
|
2
|
-
import { PluginManifest as PluginManifest$1
|
|
1
|
+
import { ResourceFieldEntry } from "../shared/src/schemas/manifest.js";
|
|
2
|
+
import { PluginManifest as PluginManifest$1 } from "../shared/src/plugin.js";
|
|
3
3
|
import "../shared/src/index.js";
|
|
4
4
|
import { ResourcePermission, ResourceType } from "./types.generated.js";
|
|
5
5
|
import { JSONSchema7 } from "json-schema";
|
|
@@ -8,16 +8,30 @@ import { JSONSchema7 } from "json-schema";
|
|
|
8
8
|
/**
|
|
9
9
|
* Declares a resource requirement for a plugin.
|
|
10
10
|
* Can be defined statically in a manifest or dynamically via getResourceRequirements().
|
|
11
|
-
* Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union.
|
|
12
11
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
12
|
+
* Defined as a flat structural shape rather than narrowing the shared
|
|
13
|
+
* discriminated-union `ResourceRequirement`. The registry needs to construct
|
|
14
|
+
* and pass these values around at runtime where `type` and `permission` are
|
|
15
|
+
* looked up dynamically from a string; preserving per-variant tightness here
|
|
16
|
+
* would force every constructor to thread variant types end-to-end. The
|
|
17
|
+
* runtime guarantee (permission valid for type) is enforced by the manifest
|
|
18
|
+
* schema's parse, not by the registry's static types.
|
|
15
19
|
*/
|
|
16
|
-
interface ResourceRequirement
|
|
20
|
+
interface ResourceRequirement {
|
|
17
21
|
/** Type of Databricks resource required (narrowed to enum) */
|
|
18
22
|
type: ResourceType;
|
|
19
23
|
/** Required permission level for the resource (narrowed to union) */
|
|
20
24
|
permission: ResourcePermission;
|
|
25
|
+
/** Human-readable label for UI/display only. */
|
|
26
|
+
alias: string;
|
|
27
|
+
/** Stable key for machine use: deduplication, env naming, composite keys, app.yaml. */
|
|
28
|
+
resourceKey: string;
|
|
29
|
+
/** Human-readable description of why this resource is needed. */
|
|
30
|
+
description: string;
|
|
31
|
+
/** Map of field name to field entry. Required at runtime. */
|
|
32
|
+
fields: Record<string, ResourceFieldEntry>;
|
|
33
|
+
/** Whether the resource is mandatory at runtime. */
|
|
34
|
+
required: boolean;
|
|
21
35
|
}
|
|
22
36
|
/**
|
|
23
37
|
* Internal representation of a resource in the registry.
|
|
@@ -60,7 +74,7 @@ type ConfigSchema = JSONSchema7;
|
|
|
60
74
|
* Attached to plugin classes as a static property.
|
|
61
75
|
* Extends the shared PluginManifest with strict resource types.
|
|
62
76
|
*
|
|
63
|
-
* @see `packages/shared/src/schemas/
|
|
77
|
+
* @see `packages/shared/src/schemas/manifest.ts` `pluginManifestSchema` — Zod source of truth
|
|
64
78
|
* @see {@link SharedPluginManifest} — shared re-export with JSONSchema7 config
|
|
65
79
|
*/
|
|
66
80
|
interface PluginManifest<TName extends string = string> extends Omit<PluginManifest$1, "resources" | "config"> {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/registry/types.ts"],"mappings":"
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/registry/types.ts"],"mappings":";;;;;;;;;;;;;;;;;AA6EA;;UArBiB,mBAAA;EA6BN;EA3BT,IAAA,EAAM,YAAA;EAkCc;EAhCpB,UAAA,EAAY,kBAAA;EAiB4C;EAfxD,KAAA;EAeqC;EAbrC,WAAA;EAkBA;EAhBA,WAAA;EAmBS;EAjBT,MAAA,EAAQ,MAAA,SAAe,kBAAA;EAwBH;EAtBpB,QAAA;AAAA;;AA4BF;;;UArBiB,aAAA,SAAsB,mBAAA;EAuBrC;EArBA,MAAA;EAwBS;EArBT,QAAA;EAwBK;EArBL,MAAA,GAAS,MAAA;EAqBS;AAWpB;;;;EAzBE,iBAAA,GAAoB,MAAA,SAAe,kBAAA;AAAA;;;;UAMpB,gBAAA;EAuCE;EArCjB,KAAA;EAwCiB;EArCjB,OAAA,EAAS,aAAA;EA6CC;EA1CV,GAAA,EAAK,aAAA;AAAA;;;;;;;KAWK,YAAA,GAAe,WAAA;;;;;;;;;UAUV,cAAA,wCACP,IAAA,CAAK,gBAAA;EAoBS;EAlBtB,IAAA,EAAM,KAAA;;;;EAKN,SAAA;sEAEE,QAAA,EAAU,IAAA,CAAK,mBAAA;IAGf,QAAA,EAAU,IAAA,CAAK,mBAAA;EAAA;;;;;EAOjB,MAAA;IACE,MAAA,EAAQ,YAAA;EAAA;AAAA"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
//#region src/registry/types.generated.ts
|
|
2
|
-
/** Resource types from
|
|
2
|
+
/** Resource types from resourceTypeSchema.options */
|
|
3
3
|
let ResourceType = /* @__PURE__ */ function(ResourceType) {
|
|
4
4
|
ResourceType["SECRET"] = "secret";
|
|
5
5
|
ResourceType["JOB"] = "job";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.generated.js","names":[],"sources":["../../src/registry/types.generated.ts"],"sourcesContent":["// AUTO-GENERATED from packages/shared/src/schemas/
|
|
1
|
+
{"version":3,"file":"types.generated.js","names":[],"sources":["../../src/registry/types.generated.ts"],"sourcesContent":["// AUTO-GENERATED from packages/shared/src/schemas/manifest.ts (Zod canonical).\n// Do not edit. Run: pnpm exec tsx tools/generate-registry-types.ts\n\n/** Resource types from resourceTypeSchema.options */\nexport enum ResourceType {\n SECRET = \"secret\",\n JOB = \"job\",\n SQL_WAREHOUSE = \"sql_warehouse\",\n SERVING_ENDPOINT = \"serving_endpoint\",\n VOLUME = \"volume\",\n VECTOR_SEARCH_INDEX = \"vector_search_index\",\n UC_FUNCTION = \"uc_function\",\n UC_CONNECTION = \"uc_connection\",\n DATABASE = \"database\",\n POSTGRES = \"postgres\",\n GENIE_SPACE = \"genie_space\",\n EXPERIMENT = \"experiment\",\n APP = \"app\",\n}\n\n// ============================================================================\n// Permissions per resource type (from per-type permission enum schemas)\n// ============================================================================\n/** Permissions for SECRET resources */\nexport type SecretPermission = \"READ\" | \"WRITE\" | \"MANAGE\";\n\n/** Permissions for JOB resources */\nexport type JobPermission = \"CAN_VIEW\" | \"CAN_MANAGE_RUN\" | \"CAN_MANAGE\";\n\n/** Permissions for SQL_WAREHOUSE resources */\nexport type SqlWarehousePermission = \"CAN_USE\" | \"CAN_MANAGE\";\n\n/** Permissions for SERVING_ENDPOINT resources */\nexport type ServingEndpointPermission = \"CAN_VIEW\" | \"CAN_QUERY\" | \"CAN_MANAGE\";\n\n/** Permissions for VOLUME resources */\nexport type VolumePermission = \"READ_VOLUME\" | \"WRITE_VOLUME\";\n\n/** Permissions for VECTOR_SEARCH_INDEX resources */\nexport type VectorSearchIndexPermission = \"SELECT\";\n\n/** Permissions for UC_FUNCTION resources */\nexport type UcFunctionPermission = \"EXECUTE\";\n\n/** Permissions for UC_CONNECTION resources */\nexport type UcConnectionPermission = \"USE_CONNECTION\";\n\n/** Permissions for DATABASE resources */\nexport type DatabasePermission = \"CAN_CONNECT_AND_CREATE\";\n\n/** Permissions for POSTGRES resources */\nexport type PostgresPermission = \"CAN_CONNECT_AND_CREATE\";\n\n/** Permissions for GENIE_SPACE resources */\nexport type GenieSpacePermission =\n | \"CAN_VIEW\"\n | \"CAN_RUN\"\n | \"CAN_EDIT\"\n | \"CAN_MANAGE\";\n\n/** Permissions for EXPERIMENT resources */\nexport type ExperimentPermission = \"CAN_READ\" | \"CAN_EDIT\" | \"CAN_MANAGE\";\n\n/** Permissions for APP resources */\nexport type AppPermission = \"CAN_USE\";\n\n/** Union of all possible permission levels across all resource types. */\nexport type ResourcePermission =\n | SecretPermission\n | JobPermission\n | SqlWarehousePermission\n | ServingEndpointPermission\n | VolumePermission\n | VectorSearchIndexPermission\n | UcFunctionPermission\n | UcConnectionPermission\n | DatabasePermission\n | PostgresPermission\n | GenieSpacePermission\n | ExperimentPermission\n | AppPermission;\n\n/** Permission hierarchy per resource type (weakest to strongest). Schema enum order. */\nexport const PERMISSION_HIERARCHY_BY_TYPE: Record<\n ResourceType,\n readonly ResourcePermission[]\n> = {\n [ResourceType.SECRET]: [\"READ\", \"WRITE\", \"MANAGE\"],\n [ResourceType.JOB]: [\"CAN_VIEW\", \"CAN_MANAGE_RUN\", \"CAN_MANAGE\"],\n [ResourceType.SQL_WAREHOUSE]: [\"CAN_USE\", \"CAN_MANAGE\"],\n [ResourceType.SERVING_ENDPOINT]: [\"CAN_VIEW\", \"CAN_QUERY\", \"CAN_MANAGE\"],\n [ResourceType.VOLUME]: [\"READ_VOLUME\", \"WRITE_VOLUME\"],\n [ResourceType.VECTOR_SEARCH_INDEX]: [\"SELECT\"],\n [ResourceType.UC_FUNCTION]: [\"EXECUTE\"],\n [ResourceType.UC_CONNECTION]: [\"USE_CONNECTION\"],\n [ResourceType.DATABASE]: [\"CAN_CONNECT_AND_CREATE\"],\n [ResourceType.POSTGRES]: [\"CAN_CONNECT_AND_CREATE\"],\n [ResourceType.GENIE_SPACE]: [\"CAN_VIEW\", \"CAN_RUN\", \"CAN_EDIT\", \"CAN_MANAGE\"],\n [ResourceType.EXPERIMENT]: [\"CAN_READ\", \"CAN_EDIT\", \"CAN_MANAGE\"],\n [ResourceType.APP]: [\"CAN_USE\"],\n} as const;\n\n/** Set of valid permissions per type (for validation). */\nexport const PERMISSIONS_BY_TYPE: Record<\n ResourceType,\n readonly ResourcePermission[]\n> = PERMISSION_HIERARCHY_BY_TYPE;\n"],"mappings":";;AAIA,IAAY,sDAAL;AACL;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;AACA;;;;AAkEF,MAAa,+BAGT;EACD,aAAa,SAAS;EAAC;EAAQ;EAAS;EAAS;EACjD,aAAa,MAAM;EAAC;EAAY;EAAkB;EAAa;EAC/D,aAAa,gBAAgB,CAAC,WAAW,aAAa;EACtD,aAAa,mBAAmB;EAAC;EAAY;EAAa;EAAa;EACvE,aAAa,SAAS,CAAC,eAAe,eAAe;EACrD,aAAa,sBAAsB,CAAC,SAAS;EAC7C,aAAa,cAAc,CAAC,UAAU;EACtC,aAAa,gBAAgB,CAAC,iBAAiB;EAC/C,aAAa,WAAW,CAAC,yBAAyB;EAClD,aAAa,WAAW,CAAC,yBAAyB;EAClD,aAAa,cAAc;EAAC;EAAY;EAAW;EAAY;EAAa;EAC5E,aAAa,aAAa;EAAC;EAAY;EAAY;EAAa;EAChE,aAAa,MAAM,CAAC,UAAU;CAChC;;AAGD,MAAa,sBAGT"}
|
package/dist/registry/types.js
CHANGED
|
@@ -9,9 +9,9 @@ import { PERMISSIONS_BY_TYPE, PERMISSION_HIERARCHY_BY_TYPE, ResourceType } from
|
|
|
9
9
|
* in a machine-readable format.
|
|
10
10
|
*
|
|
11
11
|
* Base interfaces (ResourceFieldEntry, ResourceRequirement, PluginManifest)
|
|
12
|
-
* are
|
|
13
|
-
* This module re-exports them with strict narrowing
|
|
14
|
-
* ResourcePermission union) for appkit-internal use.
|
|
12
|
+
* are inferred from the Zod schemas in `packages/shared/src/schemas/manifest.ts`
|
|
13
|
+
* and imported via shared. This module re-exports them with strict narrowing
|
|
14
|
+
* (ResourceType enum, ResourcePermission union) for appkit-internal use.
|
|
15
15
|
*
|
|
16
16
|
* Resource types, permissions, and hierarchy constants are generated by
|
|
17
17
|
* tools/generate-registry-types.ts into types.generated.ts.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.js","names":[],"sources":["../../src/registry/types.ts"],"sourcesContent":["/**\n * Resource Registry Type System\n *\n * This module defines the type system for the AppKit Resource Registry,\n * which enables plugins to declare their Databricks resource requirements\n * in a machine-readable format.\n *\n * Base interfaces (ResourceFieldEntry, ResourceRequirement, PluginManifest)\n * are
|
|
1
|
+
{"version":3,"file":"types.js","names":[],"sources":["../../src/registry/types.ts"],"sourcesContent":["/**\n * Resource Registry Type System\n *\n * This module defines the type system for the AppKit Resource Registry,\n * which enables plugins to declare their Databricks resource requirements\n * in a machine-readable format.\n *\n * Base interfaces (ResourceFieldEntry, ResourceRequirement, PluginManifest)\n * are inferred from the Zod schemas in `packages/shared/src/schemas/manifest.ts`\n * and imported via shared. This module re-exports them with strict narrowing\n * (ResourceType enum, ResourcePermission union) for appkit-internal use.\n *\n * Resource types, permissions, and hierarchy constants are generated by\n * tools/generate-registry-types.ts into types.generated.ts.\n */\n\n// Re-export generated registry types (enum + const must be value exports for runtime)\nimport {\n PERMISSION_HIERARCHY_BY_TYPE,\n PERMISSIONS_BY_TYPE,\n type ResourcePermission,\n ResourceType,\n} from \"./types.generated\";\n\nexport {\n PERMISSION_HIERARCHY_BY_TYPE,\n PERMISSIONS_BY_TYPE,\n ResourceType,\n type ResourcePermission,\n};\n\n// Re-export schema-derived base type from shared.\nexport type { ResourceFieldEntry } from \"shared\";\n\n// Import shared base types for strict extension below.\nimport type {\n ResourceFieldEntry,\n PluginManifest as SharedPluginManifest,\n} from \"shared\";\n\n// ============================================================================\n// Strict appkit wrappers — narrow shared base types for registry use\n// ============================================================================\n\n/**\n * Declares a resource requirement for a plugin.\n * Can be defined statically in a manifest or dynamically via getResourceRequirements().\n *\n * Defined as a flat structural shape rather than narrowing the shared\n * discriminated-union `ResourceRequirement`. The registry needs to construct\n * and pass these values around at runtime where `type` and `permission` are\n * looked up dynamically from a string; preserving per-variant tightness here\n * would force every constructor to thread variant types end-to-end. The\n * runtime guarantee (permission valid for type) is enforced by the manifest\n * schema's parse, not by the registry's static types.\n */\nexport interface ResourceRequirement {\n /** Type of Databricks resource required (narrowed to enum) */\n type: ResourceType;\n /** Required permission level for the resource (narrowed to union) */\n permission: ResourcePermission;\n /** Human-readable label for UI/display only. */\n alias: string;\n /** Stable key for machine use: deduplication, env naming, composite keys, app.yaml. */\n resourceKey: string;\n /** Human-readable description of why this resource is needed. */\n description: string;\n /** Map of field name to field entry. Required at runtime. */\n fields: Record<string, ResourceFieldEntry>;\n /** Whether the resource is mandatory at runtime. */\n required: boolean;\n}\n\n/**\n * Internal representation of a resource in the registry.\n * Extends ResourceRequirement with resolution state and plugin ownership.\n */\nexport interface ResourceEntry extends ResourceRequirement {\n /** Plugin(s) that require this resource (comma-separated if multiple) */\n plugin: string;\n\n /** Whether the resource has been resolved (all field env vars set) */\n resolved: boolean;\n\n /** Resolved value per field name. Populated by validate() when all field env vars are set. */\n values?: Record<string, string>;\n\n /**\n * Per-plugin permission tracking.\n * Maps plugin name to the permission it originally requested.\n * Populated when multiple plugins share the same resource.\n */\n permissionSources?: Record<string, ResourcePermission>;\n}\n\n/**\n * Result of validating all registered resources against the environment.\n */\nexport interface ValidationResult {\n /** Whether all required resources are available */\n valid: boolean;\n\n /** List of missing required resources */\n missing: ResourceEntry[];\n\n /** Complete list of all registered resources (required and optional) */\n all: ResourceEntry[];\n}\n\nimport type { JSONSchema7 } from \"json-schema\";\n\n/**\n * Configuration schema definition for plugin config.\n * Re-exported from the standard JSON Schema Draft 7 types.\n *\n * @see {@link https://json-schema.org/draft-07/json-schema-release-notes | JSON Schema Draft 7}\n */\nexport type ConfigSchema = JSONSchema7;\n\n/**\n * Plugin manifest that declares metadata and resource requirements.\n * Attached to plugin classes as a static property.\n * Extends the shared PluginManifest with strict resource types.\n *\n * @see `packages/shared/src/schemas/manifest.ts` `pluginManifestSchema` — Zod source of truth\n * @see {@link SharedPluginManifest} — shared re-export with JSONSchema7 config\n */\nexport interface PluginManifest<TName extends string = string>\n extends Omit<SharedPluginManifest, \"resources\" | \"config\"> {\n /** Plugin identifier — the single source of truth for the plugin's name */\n name: TName;\n\n /**\n * Resource requirements declaration (with strict ResourceRequirement types)\n */\n resources: {\n /** Resources that must be available for the plugin to function */\n required: Omit<ResourceRequirement, \"required\">[];\n\n /** Resources that enhance functionality but are not mandatory */\n optional: Omit<ResourceRequirement, \"required\">[];\n };\n\n /**\n * Configuration schema for the plugin.\n * Uses JSONSchema7 instead of the generated ConfigSchema (which is too restrictive).\n */\n config?: {\n schema: ConfigSchema;\n };\n}\n"],"mappings":""}
|