-
Notifications
You must be signed in to change notification settings - Fork 12
Expand file tree
/
Copy pathplugin.ts
More file actions
226 lines (195 loc) · 6.92 KB
/
plugin.ts
File metadata and controls
226 lines (195 loc) · 6.92 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
import type express from "express";
import type { JSONSchema7 } from "json-schema";
import type {
PluginManifest as GeneratedPluginManifest,
ResourceRequirement as GeneratedResourceRequirement,
ResourceFieldEntry,
} from "./schemas/plugin-manifest.generated";
// Re-export generated types as the shared canonical definitions.
export type { ResourceFieldEntry };
/** Base plugin interface. */
export interface BasePlugin {
name: string;
abortActiveOperations?(): void;
setup(): Promise<void>;
injectRoutes(router: express.Router): void;
getEndpoints(): PluginEndpointMap;
getSkipBodyParsingPaths?(): ReadonlySet<string>;
exports?(): unknown;
}
/** Base configuration interface for AppKit plugins */
export interface BasePluginConfig {
name?: string;
host?: string;
[key: string]: unknown;
/*
* Telemetry configuration
* @default true for all telemetry types
*/
telemetry?: TelemetryOptions;
}
export type TelemetryOptions =
| boolean
| {
traces?: boolean;
metrics?: boolean;
logs?: boolean;
};
export interface PluginConfig {
config?: unknown;
plugin: PluginConstructor;
}
export type PluginPhase = "core" | "normal" | "deferred";
/**
* Plugin constructor with required manifest declaration.
* All plugins must declare a manifest with their metadata and resource requirements.
*/
export type PluginConstructor<
C = BasePluginConfig,
I extends BasePlugin = BasePlugin,
> = (new (
config: C,
) => I) & {
DEFAULT_CONFIG?: Record<string, unknown>;
phase?: PluginPhase;
/**
* Static manifest declaring plugin metadata and resource requirements.
* Required for all plugins.
*/
manifest: PluginManifest;
/**
* Optional runtime resource requirements based on config.
* Use this when resource requirements depend on plugin configuration.
*/
getResourceRequirements?(config: C): ResourceRequirement[];
/**
* Returns extra headers to append to the OTLP trace exporter.
* Called by `_createApp` before TelemetryManager initializes.
*/
appendTraceHeaders?(config: C): Record<string, string>;
};
/**
* Manifest declaration for plugins.
* Extends the generated PluginManifest with a generic name parameter
* and uses JSONSchema7 for config.schema (the generated ConfigSchema
* is too restrictive for plugin consumers).
*
* @see {@link GeneratedPluginManifest} — generated base from plugin-manifest.schema.json
* @see `packages/appkit/src/registry/types.ts` `PluginManifest` — strict appkit narrowing (enum types)
*/
export interface PluginManifest<TName extends string = string>
extends Omit<
GeneratedPluginManifest,
"name" | "config" | "$schema" | "resources"
> {
name: TName;
resources: {
required: Omit<ResourceRequirement, "required">[];
optional: Omit<ResourceRequirement, "required">[];
};
config?: {
schema: JSONSchema7;
};
}
/**
* Resource requirement with runtime fields added beyond the schema definition.
* - `fields` is made required (schema has it optional, but registry always populates it)
* - `required` boolean tracks whether the resource is mandatory at runtime
*
* @see {@link GeneratedResourceRequirement} — generated base from plugin-manifest.schema.json
* @see `packages/appkit/src/registry/types.ts` `ResourceRequirement` — strict appkit narrowing (enum types)
*/
export interface ResourceRequirement extends GeneratedResourceRequirement {
fields: Record<string, ResourceFieldEntry>;
required: boolean;
}
export type ConfigFor<T> = T extends { DEFAULT_CONFIG: infer D }
? D
: T extends new (
...args: any[]
) => { config: infer C }
? C
: BasePluginConfig;
// Optional config plugin definition (used internally)
export type OptionalConfigPluginDef<P extends PluginConstructor> = {
plugin: P;
config?: Partial<ConfigFor<P>>;
};
// Input plugin map type (used internally by AppKit)
export type InputPluginMap = {
[key: string]: OptionalConfigPluginDef<PluginConstructor> | undefined;
};
// AppKit with plugins - extracts instances from plugin map
export type AppKitWithPlugins<T extends InputPluginMap> = {
[K in keyof T]: T[K] extends {
plugin: PluginConstructor<BasePluginConfig, infer I>;
}
? I
: never;
};
/**
* Extracts the exports type from a plugin.
* This is the return type of the plugin's exports() method.
* If the plugin doesn't implement exports(), returns an empty object type.
*/
export type PluginExports<T extends BasePlugin> =
T["exports"] extends () => infer R ? R : Record<string, never>;
/**
* Wraps an SDK with the `asUser` method that AppKit automatically adds.
* When `asUser(req)` is called, it returns the same SDK but scoped to the user's credentials.
*
* When the SDK is a function (callable export), it is returned as-is since
* the plugin manages its own `asUser` pattern per-call.
*/
export type WithAsUser<SDK> = SDK extends (...args: any[]) => any
? SDK
: SDK & {
/**
* Execute operations using the user's identity from the request.
* Returns a user-scoped SDK where all methods execute with the
* user's Databricks credentials instead of the service principal.
*/
asUser: (req: IAppRequest) => SDK;
};
/**
* Maps plugin names to their exported types (with asUser automatically added).
* Each plugin exposes its public API via the exports() method,
* and AppKit wraps it with asUser() for user-scoped execution.
*
* Callable exports (functions) are passed through without wrapping,
* as they manage their own `asUser` pattern (e.g. files plugin).
*/
export type PluginMap<
U extends readonly PluginData<PluginConstructor, unknown, string>[],
> = {
[P in U[number] as P["name"]]: WithAsUser<
PluginExports<InstanceType<P["plugin"]>>
>;
};
/** Tuple of plugin class, config, and name. Created by `toPlugin()` and passed to `createApp()`. */
export type PluginData<T, U, N> = { plugin: T; config: U; name: N };
/** Factory function type returned by `toPlugin()`. Accepts optional config and returns a PluginData tuple. */
export type ToPlugin<T, U, N extends string> = (
config?: U,
) => PluginData<T, U, N>;
/** Express router type for plugin route registration */
export type IAppRouter = express.Router;
export type IAppResponse = express.Response;
export type IAppRequest = express.Request;
export type HttpMethod = "get" | "post" | "put" | "delete" | "patch" | "head";
export type RouteConfig = {
/** Unique name for this endpoint (used for frontend access) */
name: string;
method: HttpMethod;
path: string;
handler: (req: IAppRequest, res: IAppResponse) => Promise<void>;
/** When true, the server will skip JSON body parsing for this route (e.g. file uploads). */
skipBodyParsing?: boolean;
};
/** Map of endpoint names to their full paths for a plugin */
export type PluginEndpointMap = Record<string, string>;
/** Map of plugin names to their endpoint maps */
export type PluginEndpoints = Record<string, PluginEndpointMap>;
export interface QuerySchemas {
[key: string]: unknown;
}