@dbx-tools/appkit 0.6.69 → 0.6.71

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.
@@ -0,0 +1,129 @@
1
+ /**
2
+ * Soft-fail Lakebase cache storage for AppKit's persistent cache.
3
+ *
4
+ * AppKit's `PersistentStorage.initialize()` runs DDL migrations and throws on
5
+ * any step failure. With `cache.strictPersistence: true` that throw makes
6
+ * `CacheManager` silently disable the cache. This wraps the same storage,
7
+ * still runs its migrations, but logs a failed step instead of throwing so a
8
+ * usable table keeps serving.
9
+ *
10
+ * `PersistentStorage` is not on AppKit's public export map. The deep load is
11
+ * lazy and best-effort: if the installed AppKit layout changes, this returns
12
+ * `undefined` and `createApp` leaves AppKit on its default cache path.
13
+ *
14
+ * @module
15
+ */
16
+
17
+ import { createRequire } from "node:module";
18
+ import { dirname, join } from "node:path";
19
+ import { createLakebasePool, getWorkspaceClient, type CacheConfig } from "@databricks/appkit";
20
+ import { error, log } from "@dbx-tools/shared-core";
21
+
22
+ const logger = log.logger("cache-storage");
23
+
24
+ /** Whether `LOG_LEVEL` is currently at or below debug. */
25
+ function isDebugEnabled(): boolean {
26
+ return log.isLevelEnabled("debug");
27
+ }
28
+
29
+ type LakebasePool = ReturnType<typeof createLakebasePool>;
30
+ type CacheStorage = NonNullable<CacheConfig["storage"]>;
31
+
32
+ /** AppKit's internal persistent storage surface. */
33
+ type PersistentStorageBase = CacheStorage & {
34
+ initialize(): Promise<void>;
35
+ initialized: boolean;
36
+ };
37
+
38
+ type PersistentStorageConstructor = new (
39
+ config: CacheConfig,
40
+ pool: LakebasePool,
41
+ ) => PersistentStorageBase;
42
+
43
+ let persistentStorageCtor: PersistentStorageConstructor | undefined | null = null;
44
+
45
+ /**
46
+ * Lazily resolve AppKit's internal `PersistentStorage` constructor. `null`
47
+ * means the lookup already failed; `undefined` means not attempted yet.
48
+ */
49
+ function loadPersistentStorage(): PersistentStorageConstructor | undefined {
50
+ if (persistentStorageCtor !== null) {
51
+ return persistentStorageCtor;
52
+ }
53
+ try {
54
+ const require = createRequire(import.meta.url);
55
+ const modulePath = join(
56
+ dirname(require.resolve("@databricks/appkit")),
57
+ "cache/storage/persistent.js",
58
+ );
59
+ const loaded = require(modulePath).PersistentStorage as
60
+ PersistentStorageConstructor | undefined;
61
+ if (typeof loaded !== "function") {
62
+ logger.debug("soft persistent cache skipped (PersistentStorage missing)");
63
+ persistentStorageCtor = undefined;
64
+ return undefined;
65
+ }
66
+ persistentStorageCtor = loaded;
67
+ return loaded;
68
+ } catch (err) {
69
+ logger.debug("soft persistent cache skipped (PersistentStorage unavailable)", {
70
+ error: error.errorMessage(err),
71
+ });
72
+ persistentStorageCtor = undefined;
73
+ return undefined;
74
+ }
75
+ }
76
+
77
+ /** Soften `initialize()` so a migration failure is logged, not thrown. */
78
+ function softenInitialize(storage: PersistentStorageBase): void {
79
+ const originalInitialize = storage.initialize.bind(storage);
80
+ storage.initialize = async () => {
81
+ try {
82
+ await originalInitialize();
83
+ } catch (err) {
84
+ if (isDebugEnabled()) {
85
+ logger.error("persistent cache migration failed", err);
86
+ } else {
87
+ logger.warn("persistent cache migration failed", {
88
+ error: error.errorMessage(err),
89
+ });
90
+ }
91
+ storage.initialized = true;
92
+ }
93
+ };
94
+ }
95
+
96
+ /**
97
+ * Build a soft-fail Lakebase cache storage when a pool can be created and
98
+ * AppKit's PersistentStorage can be loaded. Returns `undefined` otherwise so
99
+ * AppKit can fall through to its normal cache path.
100
+ */
101
+ export async function createSoftPersistentStorage(
102
+ cache: CacheConfig | undefined,
103
+ ): Promise<CacheStorage | undefined> {
104
+ const PersistentStorage = loadPersistentStorage();
105
+ if (!PersistentStorage) {
106
+ return undefined;
107
+ }
108
+
109
+ let pool: LakebasePool | undefined;
110
+ try {
111
+ pool = createLakebasePool({ workspaceClient: getWorkspaceClient({}) });
112
+ const storage = new PersistentStorage(cache ?? {}, pool);
113
+ softenInitialize(storage);
114
+ if (!(await storage.healthCheck())) {
115
+ await storage.close().catch(() => {});
116
+ return undefined;
117
+ }
118
+ await storage.initialize();
119
+ return storage;
120
+ } catch (err) {
121
+ logger.debug("soft persistent cache unavailable", {
122
+ error: error.errorMessage(err),
123
+ });
124
+ if (pool) {
125
+ await pool.end().catch(() => {});
126
+ }
127
+ return undefined;
128
+ }
129
+ }
package/src/appkit.ts CHANGED
@@ -1,6 +1,22 @@
1
1
  /**
2
- * Generic AppKit runtime glue: the per-request execution context and the types
3
- * derived from it. Not plugin-specific - that lives in `./plugin`.
2
+ * AppKit runtime glue: the auto-configuring `createApp` drop-in, plus the
3
+ * per-request execution context helpers and types derived from it.
4
+ *
5
+ * `createApp` runs dbx-tools auto-configuration, then delegates to AppKit's own
6
+ * `createApp` with the same arguments and the same typed plugin-export map.
7
+ * Drop it in as a one-for-one replacement:
8
+ *
9
+ * ```ts
10
+ * import { lakebase, server } from "@databricks/appkit";
11
+ * import { appkit } from "@dbx-tools/appkit";
12
+ *
13
+ * await appkit.createApp({ plugins: [server(), lakebase()] });
14
+ * ```
15
+ *
16
+ * Auto-configuration runs BEFORE delegating so plugins see a fully populated
17
+ * `process.env` during their synchronous `setup()`. Lakebase Postgres runs when
18
+ * a `lakebase` plugin is present, or when {@link AutoConfigureMode} is set
19
+ * explicitly on the config object.
4
20
  *
5
21
  * `getExecutionContext()` is AppKit's own accessor for the OBO-scoped workspace
6
22
  * client + request metadata; the wrappers here make it safe to call outside a
@@ -11,11 +27,323 @@
11
27
  * @module
12
28
  */
13
29
 
14
- import { createApp, getExecutionContext, InitializationError } from "@databricks/appkit";
15
- import { log } from "@dbx-tools/shared-core";
30
+ import {
31
+ createApp as appkitCreateApp,
32
+ getExecutionContext,
33
+ InitializationError,
34
+ } from "@databricks/appkit";
35
+ // AppKit's root barrel re-exports `PluginData` but not `PluginMap`; the package
36
+ // publishes this subpath for exactly that type.
37
+ import type { PluginMap } from "@databricks/appkit/dist/shared/src/plugin";
38
+ import { async, log } from "@dbx-tools/shared-core";
39
+
40
+ import {
41
+ createInterceptorContext,
42
+ type Interceptor,
43
+ type InterceptorRuntime,
44
+ lifecycleBridge,
45
+ type ResolvedAppEnv,
46
+ } from "./interceptor.ts";
47
+ import { createSoftPersistentStorage } from "./_cache-storage.ts";
48
+ import { applyLakebaseEnv, type LakebaseConnection } from "./lakebase-resolver.ts";
49
+ import { provisionCacheSchema } from "./provision.ts";
16
50
 
17
51
  const logger = log.logger("appkit");
18
52
 
53
+ type AppKitCreateAppConfig = NonNullable<Parameters<typeof appkitCreateApp>[0]>;
54
+ type AppKitPlugins = NonNullable<AppKitCreateAppConfig["plugins"]>;
55
+
56
+ /**
57
+ * What auto-configuration does before AppKit boots.
58
+ *
59
+ * - `"provision"`: resolve the Lakebase connection into `process.env`, then
60
+ * grant the AppKit cache schema to the connecting role.
61
+ * - `"env"`: resolve the Lakebase connection into `process.env` only.
62
+ *
63
+ * Omit it to get `"provision"` gated on a `lakebase` plugin being registered;
64
+ * set it explicitly to run regardless of the plugin list, or pass `false` to
65
+ * skip auto-configuration entirely.
66
+ *
67
+ * Set it EXPLICITLY on an app that has no `lakebase()` plugin but still wants
68
+ * AppKit's PERSISTENT cache. AppKit picks Lakebase for `CacheManager` only when
69
+ * `createLakebasePool()` can build a pool, and that reads `LAKEBASE_ENDPOINT` /
70
+ * `PGHOST` / `PGDATABASE` straight from `process.env` - so without this step the
71
+ * cache silently falls back to in-memory, and anything it holds (a session
72
+ * signing key, a one-time code) is lost on restart. `"env"` is the right mode
73
+ * there: the app SP cannot grant on the cache schema anyway.
74
+ */
75
+ export type AutoConfigureMode = "env" | "provision";
76
+
77
+ /** AppKit's `createApp` config plus the dbx-tools auto-configuration switch. */
78
+ export type CreateAppConfig<T extends AppKitPlugins = AppKitPlugins> = Omit<
79
+ AppKitCreateAppConfig,
80
+ "plugins" | "onPluginsReady"
81
+ > & {
82
+ plugins?: T;
83
+ onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
84
+ /** Auto-configuration to run before AppKit boots. Defaults to `"provision"`. */
85
+ autoConfigure?: AutoConfigureMode | false;
86
+ /**
87
+ * One or many {@link Interceptor}s handed an {@link InterceptorContext} once
88
+ * auto-configuration has computed the env. Each receives the resolved env, an
89
+ * AppKit-lifecycle hook, and `bindProcess` for concurrently-style supervision -
90
+ * see `./interceptor`. The tunnel is the primary consumer.
91
+ */
92
+ interceptor?: Interceptor | Interceptor[];
93
+ };
94
+
95
+ const LAKEBASE_PLUGIN = "lakebase";
96
+ const DEFAULT_AUTO_CONFIGURE: AutoConfigureMode = "provision";
97
+
98
+ /**
99
+ * Upper bound on boot-time auto-configuration. It runs as the service principal
100
+ * before any plugin is constructed, so it sits outside AppKit's interceptor
101
+ * chain and inherits no timeout, retry, or telemetry from it. The budget covers
102
+ * the resolver's own worst case (create a project, then wait for its endpoint)
103
+ * plus the cache-schema grants.
104
+ */
105
+ const AUTO_CONFIGURE_TIMEOUT_MS = 11 * 60_000;
106
+
107
+ function usesPlugin<T extends AppKitPlugins>(
108
+ config: CreateAppConfig<T> | undefined,
109
+ name: string,
110
+ ): boolean {
111
+ return Boolean(config?.plugins?.some((entry) => entry.name === name));
112
+ }
113
+
114
+ /** Plugin names for boot logs (order preserved). */
115
+ function pluginNames(config: CreateAppConfig | undefined): string[] {
116
+ return (config?.plugins ?? []).map((entry) => entry.name);
117
+ }
118
+
119
+ /**
120
+ * Run enabled auto-configuration steps without calling AppKit's `createApp`.
121
+ *
122
+ * Lakebase Postgres resolves when {@link CreateAppConfig.autoConfigure} is set
123
+ * explicitly or a `lakebase` plugin is listed in `config.plugins`. `signal`
124
+ * cancels the resolution; it is combined with an internal boot timeout either
125
+ * way.
126
+ *
127
+ * @example
128
+ * import { appkit } from "@dbx-tools/appkit";
129
+ *
130
+ * // Populate PGHOST / PGDATABASE / LAKEBASE_ENDPOINT without booting AppKit.
131
+ * await appkit.autoConfigure({ autoConfigure: "env" });
132
+ */
133
+ export async function autoConfigure<T extends AppKitPlugins>(
134
+ config?: CreateAppConfig<T>,
135
+ signal?: AbortSignal,
136
+ ): Promise<LakebaseConnection | undefined> {
137
+ const mode = config?.autoConfigure ?? DEFAULT_AUTO_CONFIGURE;
138
+ const explicit = config?.autoConfigure !== undefined;
139
+ const lakebasePluginPresent = usesPlugin(config, LAKEBASE_PLUGIN);
140
+ const plugins = pluginNames(config);
141
+ logger.debug("autoConfigure: start", {
142
+ mode,
143
+ explicit,
144
+ lakebasePluginPresent,
145
+ plugins,
146
+ timeoutMs: AUTO_CONFIGURE_TIMEOUT_MS,
147
+ callerSignal: Boolean(signal),
148
+ });
149
+
150
+ if (mode === false || !(explicit || lakebasePluginPresent)) {
151
+ const skippedReason = mode === false ? "disabled" : "no lakebase plugin";
152
+ logger.debug("autoConfigure: skip", { skippedReason, mode, lakebasePluginPresent, plugins });
153
+ logger.info("ready", {
154
+ autoConfigure: mode,
155
+ lakebasePluginPresent,
156
+ provisioned: false,
157
+ skippedReason,
158
+ });
159
+ return undefined;
160
+ }
161
+
162
+ const controller = new AbortController();
163
+ async.tieAbortSignal(controller, signal);
164
+ async.tieAbortSignal(controller, AbortSignal.timeout(AUTO_CONFIGURE_TIMEOUT_MS));
165
+
166
+ const provision = mode === "provision";
167
+ logger.debug("autoConfigure: resolve lakebase", { provision, mode });
168
+ const resolved = await autoConfigureLakebase(provision, controller.signal);
169
+ logger.debug("autoConfigure: done", {
170
+ mode,
171
+ lakebasePluginPresent,
172
+ provisioned: provision,
173
+ ...redactLakebaseConnection(resolved),
174
+ });
175
+ logger.info("ready", { autoConfigure: mode, lakebasePluginPresent, provisioned: provision });
176
+ return resolved;
177
+ }
178
+
179
+ /**
180
+ * Resolve Lakebase Postgres connection info, write the resolved values to
181
+ * `process.env`, and return the record. Used by {@link autoConfigure}; call
182
+ * {@link applyLakebaseEnv} directly when finer control is needed (a different
183
+ * `autoCreate` policy, or a caller that wants the env without booting AppKit).
184
+ */
185
+ async function autoConfigureLakebase(
186
+ provision: boolean,
187
+ signal: AbortSignal,
188
+ ): Promise<LakebaseConnection> {
189
+ logger.debug("autoConfigureLakebase: applyLakebaseEnv");
190
+ const { resolved, user } = await applyLakebaseEnv(undefined, signal);
191
+ logger.debug("autoConfigureLakebase: env applied", {
192
+ ...redactLakebaseConnection(resolved),
193
+ user,
194
+ env: {
195
+ LAKEBASE_ENDPOINT: process.env.LAKEBASE_ENDPOINT,
196
+ PGHOST: process.env.PGHOST,
197
+ PGPORT: process.env.PGPORT,
198
+ PGDATABASE: process.env.PGDATABASE,
199
+ PGUSER: process.env.PGUSER,
200
+ PGSSLMODE: process.env.PGSSLMODE,
201
+ },
202
+ });
203
+ logger.info("env updated", { ...redactLakebaseConnection(resolved), user });
204
+ if (provision) {
205
+ logger.debug("autoConfigureLakebase: provisionCacheSchema", { user });
206
+ await provisionCacheSchema(user, logger);
207
+ logger.debug("autoConfigureLakebase: provisionCacheSchema done");
208
+ } else {
209
+ logger.debug("autoConfigureLakebase: skip provision (mode=env)");
210
+ }
211
+ return resolved;
212
+ }
213
+
214
+ /**
215
+ * Whether `createApp` should inject soft-fail Lakebase cache storage.
216
+ *
217
+ * Skips when the caller disabled the cache, already supplied a storage
218
+ * backend (including in-memory), or Lakebase is not in play.
219
+ */
220
+ function shouldInjectSoftPersistentCache(
221
+ config: CreateAppConfig | undefined,
222
+ lakebase: LakebaseConnection | undefined,
223
+ ): boolean {
224
+ const cache = config?.cache;
225
+ if (cache?.enabled === false) return false;
226
+ if (cache?.storage) return false;
227
+ return Boolean(lakebase) || usesPlugin(config, LAKEBASE_PLUGIN);
228
+ }
229
+
230
+ function redactLakebaseConnection(resolved: LakebaseConnection): Record<string, unknown> {
231
+ return {
232
+ project: resolved.project,
233
+ branch: resolved.branch,
234
+ endpoint: resolved.endpoint,
235
+ database: resolved.database,
236
+ host: resolved.host,
237
+ port: resolved.port,
238
+ sslMode: resolved.sslMode,
239
+ };
240
+ }
241
+
242
+ /** Build the {@link ResolvedAppEnv} interceptors read, from the auto-config result. */
243
+ function resolvedAppEnv(lakebase: LakebaseConnection | undefined): ResolvedAppEnv {
244
+ return {
245
+ ...(lakebase ? { lakebase } : {}),
246
+ ...(process.env.DATABRICKS_HOST ? { databricksHost: process.env.DATABRICKS_HOST } : {}),
247
+ };
248
+ }
249
+
250
+ /** Normalize the `interceptor?: Interceptor | Interceptor[]` option to an array. */
251
+ function interceptorList(interceptor: CreateAppConfig["interceptor"]): Interceptor[] {
252
+ if (!interceptor) return [];
253
+ return Array.isArray(interceptor) ? interceptor : [interceptor];
254
+ }
255
+
256
+ /**
257
+ * Auto-configuring drop-in for AppKit's `createApp`: same config, same typed
258
+ * plugin-export map, with {@link autoConfigure} run first.
259
+ *
260
+ * When {@link CreateAppConfig.interceptor}s are given, each is invoked with an
261
+ * {@link InterceptorContext} AFTER auto-configuration computes the env and BEFORE
262
+ * AppKit boots - so an interceptor can read the resolved connection, register
263
+ * lifecycle handlers, and `bindProcess` a child. A hidden {@link lifecycleBridge}
264
+ * plugin is injected so those `onLifecycle` handlers fire on the genuine AppKit
265
+ * events; it has no exports, so the returned {@link PluginMap} is unchanged.
266
+ *
267
+ * @example
268
+ * import { lakebase, server } from "@databricks/appkit";
269
+ * import { appkit } from "@dbx-tools/appkit";
270
+ *
271
+ * const app = await appkit.createApp({ plugins: [server(), lakebase()] });
272
+ */
273
+ export async function createApp<T extends AppKitPlugins>(
274
+ config?: CreateAppConfig<T>,
275
+ ): Promise<PluginMap<T>> {
276
+ const plugins = pluginNames(config);
277
+ const interceptors = interceptorList(config?.interceptor);
278
+ logger.debug("createApp: start", {
279
+ plugins,
280
+ pluginCount: plugins.length,
281
+ interceptorCount: interceptors.length,
282
+ autoConfigure: config?.autoConfigure ?? "(default)",
283
+ hasOnPluginsReady: typeof config?.onPluginsReady === "function",
284
+ });
285
+
286
+ logger.debug("createApp: autoConfigure");
287
+ const lakebase = await autoConfigure(config);
288
+ logger.debug("createApp: autoConfigure returned", {
289
+ lakebase: lakebase ? redactLakebaseConnection(lakebase) : undefined,
290
+ });
291
+
292
+ const appConfig = { ...config };
293
+ delete appConfig.autoConfigure;
294
+ delete appConfig.interceptor;
295
+
296
+ if (shouldInjectSoftPersistentCache(config, lakebase)) {
297
+ logger.debug("createApp: inject soft persistent cache storage");
298
+ const storage = await createSoftPersistentStorage(config?.cache);
299
+ if (storage) {
300
+ appConfig.cache = { ...config?.cache, storage };
301
+ logger.debug("createApp: soft persistent cache storage ready");
302
+ } else {
303
+ logger.debug("createApp: soft persistent cache storage unavailable");
304
+ }
305
+ }
306
+
307
+ if (interceptors.length === 0) {
308
+ logger.debug("createApp: no interceptors; delegating to AppKit createApp", {
309
+ plugins: pluginNames(appConfig),
310
+ });
311
+ const result = await appkitCreateApp<T>(appConfig);
312
+ logger.debug("createApp: AppKit createApp returned", {
313
+ exportKeys: Object.keys(result ?? {}),
314
+ });
315
+ return result;
316
+ }
317
+
318
+ // Build the context from the computed env, run each interceptor (they register
319
+ // lifecycle handlers + bind processes), then inject the bridge that relays the
320
+ // REAL AppKit lifecycle into `runtime.emitLifecycle` during its `setup()`.
321
+ const env = resolvedAppEnv(lakebase);
322
+ logger.debug("createApp: interceptor context", {
323
+ hasLakebase: Boolean(env.lakebase),
324
+ databricksHost: env.databricksHost,
325
+ interceptorCount: interceptors.length,
326
+ });
327
+ const runtime: InterceptorRuntime = createInterceptorContext(env);
328
+ for (let i = 0; i < interceptors.length; i++) {
329
+ logger.debug("createApp: run interceptor", { index: i, of: interceptors.length });
330
+ await interceptors[i]!(runtime.context);
331
+ logger.debug("createApp: interceptor done", { index: i });
332
+ }
333
+ // Append the bridge to the plugins tuple. It is hidden and exports nothing, so
334
+ // the returned map still matches `PluginMap<T>`; the cast (through `unknown`) is
335
+ // only because appending widens the tuple type beyond `T`.
336
+ appConfig.plugins = [...(appConfig.plugins ?? []), lifecycleBridge({ runtime })] as unknown as T;
337
+ logger.debug("createApp: lifecycle bridge injected; delegating to AppKit createApp", {
338
+ plugins: pluginNames(appConfig),
339
+ });
340
+ const result = await appkitCreateApp<T>(appConfig);
341
+ logger.debug("createApp: AppKit createApp returned", {
342
+ exportKeys: Object.keys(result ?? {}),
343
+ });
344
+ return result;
345
+ }
346
+
19
347
  /**
20
348
  * The AppKit per-request execution context returned by `getExecutionContext()`
21
349
  * - the OBO-scoped workspace client plus the surrounding request metadata.
@@ -69,8 +397,11 @@ export function tryGetExecutionContext(): ExecutionContextLike | undefined {
69
397
  * const client = appkit.tryGetExecutionContext()?.client;
70
398
  */
71
399
  export async function ensureInitialized(): Promise<void> {
72
- if (!tryGetExecutionContext()) {
73
- logger.debug("initializing a bare AppKit app");
74
- await createApp({ plugins: [] });
400
+ if (tryGetExecutionContext()) {
401
+ logger.debug("ensureInitialized: already initialized");
402
+ return;
75
403
  }
404
+ logger.debug("ensureInitialized: booting bare AppKit app");
405
+ await createApp({ plugins: [], autoConfigure: false });
406
+ logger.debug("ensureInitialized: done");
76
407
  }
@@ -5,17 +5,17 @@
5
5
  * unit - concurrently-style, where any death takes the whole set down.
6
6
  *
7
7
  * An interceptor is a plain function `(ctx) => void | Promise<void>` passed to
8
- * {@link CreateAppConfig.interceptor} ("one or many"). {@link createApp} runs each
9
- * one AFTER auto-configuration has populated `process.env` but as part of booting
10
- * the app, so an interceptor sees the resolved connection and can bind processes
11
- * before or during setup.
8
+ * `appkit.createApp`'s `interceptor` option ("one or many"). `appkit.createApp`
9
+ * runs each one AFTER auto-configuration has populated `process.env` but as part
10
+ * of booting the app, so an interceptor sees the resolved connection and can bind
11
+ * processes before or during setup.
12
12
  *
13
13
  * The names here mirror AppKit's own vocabulary rather than inventing parallel
14
14
  * ones: {@link LifecycleEvent} and {@link InterceptorContext.onLifecycle} are the
15
15
  * exact shape of `PluginContext.onLifecycle` (`setup:complete` / `server:ready` /
16
16
  * `shutdown`). The bridge that makes that hook reachable from OUTSIDE a plugin -
17
17
  * where interceptors run - is {@link lifecycleBridge}, a tiny internal plugin
18
- * {@link createApp} injects to capture `this.context` and relay its events.
18
+ * `appkit.createApp` injects to capture `this.context` and relay its events.
19
19
  *
20
20
  * @module
21
21
  */
@@ -49,7 +49,7 @@ export type LifecycleHandler = () => void | Promise<void>;
49
49
  * they read COMPUTED values instead of re-reading `process.env` themselves.
50
50
  *
51
51
  * `lakebase` is the resolved Postgres connection when Lakebase auto-config ran
52
- * (see `create-app`'s `autoConfigure`), else `undefined`. `databricksHost` is the
52
+ * (see `appkit.autoConfigure`), else `undefined`. `databricksHost` is the
53
53
  * workspace host as resolved into the environment (`DATABRICKS_HOST`), which the
54
54
  * tunnel interceptor both reads and, when it must, sets.
55
55
  */
@@ -72,9 +72,9 @@ export type BindableProcess = Pick<ChildProcess, "pid" | "kill" | "killed" | "on
72
72
  * The handle passed to each interceptor.
73
73
  *
74
74
  * @example
75
- * import { createApp } from "@dbx-tools/appkit";
75
+ * import { appkit } from "@dbx-tools/appkit";
76
76
  *
77
- * await createApp({
77
+ * await appkit.createApp({
78
78
  * plugins: [server()],
79
79
  * interceptor: (ctx) => {
80
80
  * const portr = spawnPortr(ctx.env.databricksHost);
@@ -120,7 +120,7 @@ const TEARDOWN_SIGNALS = ["SIGINT", "SIGTERM", "SIGHUP"] as const;
120
120
  /**
121
121
  * The mutable machinery behind an {@link InterceptorContext}. Split out so
122
122
  * {@link createInterceptorContext} can hand the public context to interceptors
123
- * while `create-app` retains the `emitLifecycle` side-channel the bridge drives.
123
+ * while `appkit.createApp` retains the `emitLifecycle` side-channel the bridge drives.
124
124
  */
125
125
  export interface InterceptorRuntime {
126
126
  /** The context handed to each interceptor. */
@@ -227,7 +227,7 @@ function hasOnLifecycle(context: unknown): context is LifecycleContextLike {
227
227
  }
228
228
 
229
229
  /**
230
- * The internal plugin {@link createApp} injects to make AppKit's lifecycle
230
+ * The internal plugin `appkit.createApp` injects to make AppKit's lifecycle
231
231
  * reachable from interceptor code. On `setup()` it reads its own `this.context`
232
232
  * (the real `PluginContext`) and forwards each {@link LifecycleEvent} to the
233
233
  * interceptor runtime, so `ctx.onLifecycle(...)` handlers fire on the genuine
@@ -1,96 +0,0 @@
1
- /**
2
- * `createApp` wrapper: runs dbx-tools auto-configuration, then delegates to
3
- * AppKit's own `createApp` with the exact same arguments.
4
- *
5
- * Drop it in as a one-for-one replacement for `@databricks/appkit`'s
6
- * `createApp` - same parameters, same return type, full plugin-export inference
7
- * preserved:
8
- *
9
- * ```ts
10
- * import { createApp } from "@dbx-tools/appkit";
11
- * import { lakebase, server } from "@databricks/appkit";
12
- *
13
- * await createApp.createApp({ plugins: [server(), lakebase()] });
14
- * ```
15
- *
16
- * Auto-configuration runs BEFORE delegating so plugins see a fully populated
17
- * `process.env` during their synchronous `setup()`. Lakebase Postgres runs when
18
- * a `lakebase` plugin is present, or when {@link AutoConfigureMode} is set
19
- * explicitly on the config object.
20
- *
21
- * @module
22
- */
23
- import { createApp as appkitCreateApp } from "@databricks/appkit";
24
- import type { PluginMap } from "@databricks/appkit/dist/shared/src/plugin";
25
- import { type Interceptor } from "./interceptor.ts";
26
- import { type LakebaseConnection } from "./lakebase-resolver.ts";
27
- type AppKitCreateAppConfig = NonNullable<Parameters<typeof appkitCreateApp>[0]>;
28
- type AppKitPlugins = NonNullable<AppKitCreateAppConfig["plugins"]>;
29
- /**
30
- * What auto-configuration does before AppKit boots.
31
- *
32
- * - `"provision"`: resolve the Lakebase connection into `process.env`, then
33
- * grant the AppKit cache schema to the connecting role.
34
- * - `"env"`: resolve the Lakebase connection into `process.env` only.
35
- *
36
- * Omit it to get `"provision"` gated on a `lakebase` plugin being registered;
37
- * set it explicitly to run regardless of the plugin list, or pass `false` to
38
- * skip auto-configuration entirely.
39
- *
40
- * Set it EXPLICITLY on an app that has no `lakebase()` plugin but still wants
41
- * AppKit's PERSISTENT cache. AppKit picks Lakebase for `CacheManager` only when
42
- * `createLakebasePool()` can build a pool, and that reads `LAKEBASE_ENDPOINT` /
43
- * `PGHOST` / `PGDATABASE` straight from `process.env` - so without this step the
44
- * cache silently falls back to in-memory, and anything it holds (a session
45
- * signing key, a one-time code) is lost on restart. `"env"` is the right mode
46
- * there: the app SP cannot grant on the cache schema anyway.
47
- */
48
- export type AutoConfigureMode = "env" | "provision";
49
- /** AppKit's `createApp` config plus the dbx-tools auto-configuration switch. */
50
- export type CreateAppConfig<T extends AppKitPlugins = AppKitPlugins> = Omit<AppKitCreateAppConfig, "plugins" | "onPluginsReady"> & {
51
- plugins?: T;
52
- onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
53
- /** Auto-configuration to run before AppKit boots. Defaults to `"provision"`. */
54
- autoConfigure?: AutoConfigureMode | false;
55
- /**
56
- * One or many {@link Interceptor}s handed an {@link InterceptorContext} once
57
- * auto-configuration has computed the env. Each receives the resolved env, an
58
- * AppKit-lifecycle hook, and `bindProcess` for concurrently-style supervision -
59
- * see `./interceptor`. The tunnel is the primary consumer.
60
- */
61
- interceptor?: Interceptor | Interceptor[];
62
- };
63
- /**
64
- * Run enabled auto-configuration steps without calling AppKit's `createApp`.
65
- *
66
- * Lakebase Postgres resolves when {@link CreateAppConfig.autoConfigure} is set
67
- * explicitly or a `lakebase` plugin is listed in `config.plugins`. `signal`
68
- * cancels the resolution; it is combined with an internal boot timeout either
69
- * way.
70
- *
71
- * @example
72
- * import { createApp } from "@dbx-tools/appkit";
73
- *
74
- * // Populate PGHOST / PGDATABASE / LAKEBASE_ENDPOINT without booting AppKit.
75
- * await createApp.autoConfigure({ autoConfigure: "env" });
76
- */
77
- export declare function autoConfigure<T extends AppKitPlugins>(config?: CreateAppConfig<T>, signal?: AbortSignal): Promise<LakebaseConnection | undefined>;
78
- /**
79
- * Auto-configuring drop-in for AppKit's `createApp`: same config, same typed
80
- * plugin-export map, with {@link autoConfigure} run first.
81
- *
82
- * When {@link CreateAppConfig.interceptor}s are given, each is invoked with an
83
- * {@link InterceptorContext} AFTER auto-configuration computes the env and BEFORE
84
- * AppKit boots - so an interceptor can read the resolved connection, register
85
- * lifecycle handlers, and `bindProcess` a child. A hidden {@link lifecycleBridge}
86
- * plugin is injected so those `onLifecycle` handlers fire on the genuine AppKit
87
- * events; it has no exports, so the returned {@link PluginMap} is unchanged.
88
- *
89
- * @example
90
- * import { createApp } from "@dbx-tools/appkit";
91
- * import { lakebase, server } from "@databricks/appkit";
92
- *
93
- * const app = await createApp.createApp({ plugins: [server(), lakebase()] });
94
- */
95
- export declare function createApp<T extends AppKitPlugins>(config?: CreateAppConfig<T>): Promise<PluginMap<T>>;
96
- export {};