@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.
- package/README.md +7 -8
- package/index.ts +1 -3
- package/lib/index.d.ts +1 -3
- package/lib/index.js +1 -2
- package/lib/src/_cache-storage.d.ts +24 -0
- package/lib/src/_cache-storage.js +106 -0
- package/lib/src/appkit.d.ts +92 -3
- package/lib/src/appkit.js +255 -8
- package/lib/src/interceptor.d.ts +10 -10
- package/lib/src/interceptor.js +7 -7
- package/lib/tsconfig.tsbuildinfo +1 -1
- package/package.json +3 -3
- package/src/_cache-storage.ts +129 -0
- package/src/appkit.ts +338 -7
- package/src/interceptor.ts +10 -10
- package/lib/src/create-app.d.ts +0 -96
- package/lib/src/create-app.js +0 -154
- package/src/create-app.ts +0 -230
package/lib/src/create-app.js
DELETED
|
@@ -1,154 +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 { async, log } from "@dbx-tools/shared-core";
|
|
25
|
-
import { createInterceptorContext, lifecycleBridge, } from "./interceptor.js";
|
|
26
|
-
import { applyLakebaseEnv } from "./lakebase-resolver.js";
|
|
27
|
-
import { provisionCacheSchema } from "./provision.js";
|
|
28
|
-
const logger = log.logger("create-app");
|
|
29
|
-
const LAKEBASE_PLUGIN = "lakebase";
|
|
30
|
-
const DEFAULT_AUTO_CONFIGURE = "provision";
|
|
31
|
-
/**
|
|
32
|
-
* Upper bound on boot-time auto-configuration. It runs as the service principal
|
|
33
|
-
* before any plugin is constructed, so it sits outside AppKit's interceptor
|
|
34
|
-
* chain and inherits no timeout, retry, or telemetry from it. The budget covers
|
|
35
|
-
* the resolver's own worst case (create a project, then wait for its endpoint)
|
|
36
|
-
* plus the cache-schema grants.
|
|
37
|
-
*/
|
|
38
|
-
const AUTO_CONFIGURE_TIMEOUT_MS = 11 * 60_000;
|
|
39
|
-
function usesPlugin(config, name) {
|
|
40
|
-
return Boolean(config?.plugins?.some((entry) => entry.name === name));
|
|
41
|
-
}
|
|
42
|
-
/**
|
|
43
|
-
* Run enabled auto-configuration steps without calling AppKit's `createApp`.
|
|
44
|
-
*
|
|
45
|
-
* Lakebase Postgres resolves when {@link CreateAppConfig.autoConfigure} is set
|
|
46
|
-
* explicitly or a `lakebase` plugin is listed in `config.plugins`. `signal`
|
|
47
|
-
* cancels the resolution; it is combined with an internal boot timeout either
|
|
48
|
-
* way.
|
|
49
|
-
*
|
|
50
|
-
* @example
|
|
51
|
-
* import { createApp } from "@dbx-tools/appkit";
|
|
52
|
-
*
|
|
53
|
-
* // Populate PGHOST / PGDATABASE / LAKEBASE_ENDPOINT without booting AppKit.
|
|
54
|
-
* await createApp.autoConfigure({ autoConfigure: "env" });
|
|
55
|
-
*/
|
|
56
|
-
export async function autoConfigure(config, signal) {
|
|
57
|
-
const mode = config?.autoConfigure ?? DEFAULT_AUTO_CONFIGURE;
|
|
58
|
-
const explicit = config?.autoConfigure !== undefined;
|
|
59
|
-
const lakebasePluginPresent = usesPlugin(config, LAKEBASE_PLUGIN);
|
|
60
|
-
if (mode === false || !(explicit || lakebasePluginPresent)) {
|
|
61
|
-
logger.info("ready", {
|
|
62
|
-
autoConfigure: mode,
|
|
63
|
-
lakebasePluginPresent,
|
|
64
|
-
provisioned: false,
|
|
65
|
-
skippedReason: mode === false ? "disabled" : "no lakebase plugin",
|
|
66
|
-
});
|
|
67
|
-
return undefined;
|
|
68
|
-
}
|
|
69
|
-
const controller = new AbortController();
|
|
70
|
-
async.tieAbortSignal(controller, signal);
|
|
71
|
-
async.tieAbortSignal(controller, AbortSignal.timeout(AUTO_CONFIGURE_TIMEOUT_MS));
|
|
72
|
-
const provision = mode === "provision";
|
|
73
|
-
const resolved = await autoConfigureLakebase(provision, controller.signal);
|
|
74
|
-
logger.info("ready", { autoConfigure: mode, lakebasePluginPresent, provisioned: provision });
|
|
75
|
-
return resolved;
|
|
76
|
-
}
|
|
77
|
-
/**
|
|
78
|
-
* Resolve Lakebase Postgres connection info, write the resolved values to
|
|
79
|
-
* `process.env`, and return the record. Used by {@link autoConfigure}; call
|
|
80
|
-
* {@link applyLakebaseEnv} directly when finer control is needed (a different
|
|
81
|
-
* `autoCreate` policy, or a caller that wants the env without booting AppKit).
|
|
82
|
-
*/
|
|
83
|
-
async function autoConfigureLakebase(provision, signal) {
|
|
84
|
-
const { resolved, user } = await applyLakebaseEnv(undefined, signal);
|
|
85
|
-
logger.info("env updated", { ...redactLakebaseConnection(resolved), user });
|
|
86
|
-
if (provision) {
|
|
87
|
-
await provisionCacheSchema(user, logger);
|
|
88
|
-
}
|
|
89
|
-
return resolved;
|
|
90
|
-
}
|
|
91
|
-
function redactLakebaseConnection(resolved) {
|
|
92
|
-
return {
|
|
93
|
-
project: resolved.project,
|
|
94
|
-
branch: resolved.branch,
|
|
95
|
-
endpoint: resolved.endpoint,
|
|
96
|
-
database: resolved.database,
|
|
97
|
-
host: resolved.host,
|
|
98
|
-
port: resolved.port,
|
|
99
|
-
sslMode: resolved.sslMode,
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
/** Build the {@link ResolvedAppEnv} interceptors read, from the auto-config result. */
|
|
103
|
-
function resolvedAppEnv(lakebase) {
|
|
104
|
-
return {
|
|
105
|
-
...(lakebase ? { lakebase } : {}),
|
|
106
|
-
...(process.env.DATABRICKS_HOST ? { databricksHost: process.env.DATABRICKS_HOST } : {}),
|
|
107
|
-
};
|
|
108
|
-
}
|
|
109
|
-
/** Normalize the `interceptor?: Interceptor | Interceptor[]` option to an array. */
|
|
110
|
-
function interceptorList(interceptor) {
|
|
111
|
-
if (!interceptor)
|
|
112
|
-
return [];
|
|
113
|
-
return Array.isArray(interceptor) ? interceptor : [interceptor];
|
|
114
|
-
}
|
|
115
|
-
/**
|
|
116
|
-
* Auto-configuring drop-in for AppKit's `createApp`: same config, same typed
|
|
117
|
-
* plugin-export map, with {@link autoConfigure} run first.
|
|
118
|
-
*
|
|
119
|
-
* When {@link CreateAppConfig.interceptor}s are given, each is invoked with an
|
|
120
|
-
* {@link InterceptorContext} AFTER auto-configuration computes the env and BEFORE
|
|
121
|
-
* AppKit boots - so an interceptor can read the resolved connection, register
|
|
122
|
-
* lifecycle handlers, and `bindProcess` a child. A hidden {@link lifecycleBridge}
|
|
123
|
-
* plugin is injected so those `onLifecycle` handlers fire on the genuine AppKit
|
|
124
|
-
* events; it has no exports, so the returned {@link PluginMap} is unchanged.
|
|
125
|
-
*
|
|
126
|
-
* @example
|
|
127
|
-
* import { createApp } from "@dbx-tools/appkit";
|
|
128
|
-
* import { lakebase, server } from "@databricks/appkit";
|
|
129
|
-
*
|
|
130
|
-
* const app = await createApp.createApp({ plugins: [server(), lakebase()] });
|
|
131
|
-
*/
|
|
132
|
-
export async function createApp(config) {
|
|
133
|
-
const lakebase = await autoConfigure(config);
|
|
134
|
-
const appConfig = { ...config };
|
|
135
|
-
delete appConfig.autoConfigure;
|
|
136
|
-
delete appConfig.interceptor;
|
|
137
|
-
const interceptors = interceptorList(config?.interceptor);
|
|
138
|
-
if (interceptors.length === 0) {
|
|
139
|
-
return appkitCreateApp(appConfig);
|
|
140
|
-
}
|
|
141
|
-
// Build the context from the computed env, run each interceptor (they register
|
|
142
|
-
// lifecycle handlers + bind processes), then inject the bridge that relays the
|
|
143
|
-
// REAL AppKit lifecycle into `runtime.emitLifecycle` during its `setup()`.
|
|
144
|
-
const runtime = createInterceptorContext(resolvedAppEnv(lakebase));
|
|
145
|
-
for (const interceptor of interceptors) {
|
|
146
|
-
await interceptor(runtime.context);
|
|
147
|
-
}
|
|
148
|
-
// Append the bridge to the plugins tuple. It is hidden and exports nothing, so
|
|
149
|
-
// the returned map still matches `PluginMap<T>`; the cast (through `unknown`) is
|
|
150
|
-
// only because appending widens the tuple type beyond `T`.
|
|
151
|
-
appConfig.plugins = [...(appConfig.plugins ?? []), lifecycleBridge({ runtime })];
|
|
152
|
-
return appkitCreateApp(appConfig);
|
|
153
|
-
}
|
|
154
|
-
//# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJmaWxlIjoiY3JlYXRlLWFwcC5qcyIsInNvdXJjZVJvb3QiOiIiLCJzb3VyY2VzIjpbIi4uLy4uL3NyYy9jcmVhdGUtYXBwLnRzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBOzs7Ozs7Ozs7Ozs7Ozs7Ozs7Ozs7R0FxQkc7QUFFSCxPQUFPLEVBQUUsU0FBUyxJQUFJLGVBQWUsRUFBRSxNQUFNLG9CQUFvQixDQUFDO0FBSWxFLE9BQU8sRUFBRSxLQUFLLEVBQUUsR0FBRyxFQUFFLE1BQU0sd0JBQXdCLENBQUM7QUFFcEQsT0FBTyxFQUNMLHdCQUF3QixFQUd4QixlQUFlLEdBRWhCLE1BQU0sa0JBQWtCLENBQUM7QUFDMUIsT0FBTyxFQUFFLGdCQUFnQixFQUEyQixNQUFNLHdCQUF3QixDQUFDO0FBQ25GLE9BQU8sRUFBRSxvQkFBb0IsRUFBRSxNQUFNLGdCQUFnQixDQUFDO0FBNEN0RCxNQUFNLE1BQU0sR0FBRyxHQUFHLENBQUMsTUFBTSxDQUFDLFlBQVksQ0FBQyxDQUFDO0FBRXhDLE1BQU0sZUFBZSxHQUFHLFVBQVUsQ0FBQztBQUNuQyxNQUFNLHNCQUFzQixHQUFzQixXQUFXLENBQUM7QUFFOUQ7Ozs7OztHQU1HO0FBQ0gsTUFBTSx5QkFBeUIsR0FBRyxFQUFFLEdBQUcsTUFBTSxDQUFDO0FBRTlDLFNBQVMsVUFBVSxDQUNqQixNQUFzQyxFQUN0QyxJQUFZO0lBRVosT0FBTyxPQUFPLENBQUMsTUFBTSxFQUFFLE9BQU8sRUFBRSxJQUFJLENBQUMsQ0FBQyxLQUFLLEVBQUUsRUFBRSxDQUFDLEtBQUssQ0FBQyxJQUFJLEtBQUssSUFBSSxDQUFDLENBQUMsQ0FBQztBQUN4RSxDQUFDO0FBRUQ7Ozs7Ozs7Ozs7Ozs7R0FhRztBQUNILE1BQU0sQ0FBQyxLQUFLLFVBQVUsYUFBYSxDQUNqQyxNQUEyQixFQUMzQixNQUFvQjtJQUVwQixNQUFNLElBQUksR0FBRyxNQUFNLEVBQUUsYUFBYSxJQUFJLHNCQUFzQixDQUFDO0lBQzdELE1BQU0sUUFBUSxHQUFHLE1BQU0sRUFB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ZGJ4LXRvb2xzIGF1dG8tY29uZmlndXJhdGlvbiwgdGhlbiBkZWxlZ2F0ZXMgdG9cbiAqIEFwcEtpdCdzIG93biBgY3JlYXRlQXBwYCB3aXRoIHRoZSBleGFjdCBzYW1lIGFyZ3VtZW50cy5cbiAqXG4gKiBEcm9wIGl0IGluIGFzIGEgb25lLWZvci1vbmUgcmVwbGFjZW1lbnQgZm9yIGBAZGF0YWJyaWNrcy9hcHBraXRgJ3NcbiAqIGBjcmVhdGVBcHBgIC0gc2FtZSBwYXJhbWV0ZXJzLCBzYW1lIHJldHVybiB0eXBlLCBmdWxsIHBsdWdpbi1leHBvcnQgaW5mZXJlbmNlXG4gKiBwcmVzZXJ2ZWQ6XG4gKlxuICogYGBgdHNcbiAqIGltcG9ydCB7IGNyZWF0ZUFwcCB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuICogaW1wb3J0IHsgbGFrZWJhc2UsIHNlcnZlciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbiAqXG4gKiBhd2FpdCBjcmVhdGVBcHAuY3JlYXRlQXBwKHsgcGx1Z2luczogW3NlcnZlcigpLCBsYWtlYmFzZSgpXSB9KTtcbiAqIGBgYFxuICpcbiAqIEF1dG8tY29uZmlndXJhdGlvbiBydW5zIEJFRk9SRSBkZWxlZ2F0aW5nIHNvIHBsdWdpbnMgc2VlIGEgZnVsbHkgcG9wdWxhdGVkXG4gKiBgcHJvY2Vzcy5lbnZgIGR1cmluZyB0aGVpciBzeW5jaHJvbm91cyBgc2V0dXAoKWAuIExha2ViYXNlIFBvc3RncmVzIHJ1bnMgd2hlblxuICogYSBgbGFrZWJhc2VgIHBsdWdpbiBpcyBwcmVzZW50LCBvciB3aGVuIHtAbGluayBBdXRvQ29uZmlndXJlTW9kZX0gaXMgc2V0XG4gKiBleHBsaWNpdGx5IG9uIHRoZSBjb25maWcgb2JqZWN0LlxuICpcbiAqIEBtb2R1bGVcbiAqL1xuXG5pbXBvcnQgeyBjcmVhdGVBcHAgYXMgYXBwa2l0Q3JlYXRlQXBwIH0gZnJvbSBcIkBkYXRhYnJpY2tzL2FwcGtpdFwiO1xuLy8gQXBwS2l0J3Mgcm9vdCBiYXJyZWwgcmUtZXhwb3J0cyBgUGx1Z2luRGF0YWAgYnV0IG5vdCBgUGx1Z2luTWFwYDsgdGhlIHBhY2thZ2Vcbi8vIHB1Ymxpc2hlcyB0aGlzIHN1YnBhdGggZm9yIGV4YWN0bHkgdGhhdCB0eXBlLlxuaW1wb3J0IHR5cGUgeyBQbHVnaW5NYXAgfSBmcm9tIFwiQGRhdGFicmlja3MvYXBwa2l0L2Rpc3Qvc2hhcmVkL3NyYy9wbHVnaW5cIjtcbmltcG9ydCB7IGFzeW5jLCBsb2cgfSBmcm9tIFwiQGRieC10b29scy9zaGFyZWQtY29yZVwiO1xuXG5pbXBvcnQge1xuICBjcmVhdGVJbnRlcmNlcHRvckNvbnRleHQsXG4gIHR5cGUgSW50ZXJjZXB0b3IsXG4gIHR5cGUgSW50ZXJjZXB0b3JSdW50aW1lLFxuICBsaWZlY3ljbGVCcmlkZ2UsXG4gIHR5cGUgUmVzb2x2ZWRBcHBFbnYsXG59IGZyb20gXCIuL2ludGVyY2VwdG9yLnRzXCI7XG5pbXBvcnQgeyBhcHBseUxha2ViYXNlRW52LCB0eXBlIExha2ViYXNlQ29ubmVjdGlvbiB9IGZyb20gXCIuL2xha2ViYXNlLXJlc29sdmVyLnRzXCI7XG5pbXBvcnQgeyBwcm92aXNpb25DYWNoZVNjaGVtYSB9IGZyb20gXCIuL3Byb3Zpc2lvbi50c1wiO1xuXG50eXBlIEFwcEtpdENyZWF0ZUFwcENvbmZpZyA9IE5vbk51bGxhYmxlPFBhcmFtZXRlcnM8dHlwZW9mIGFwcGtpdENyZWF0ZUFwcD5bMF0+O1xudHlwZSBBcHBLaXRQbHVnaW5zID0gTm9uTnVsbGFibGU8QXBwS2l0Q3JlYXRlQXBwQ29uZmlnW1wicGx1Z2luc1wiXT47XG5cbi8qKlxuICogV2hhdCBhdXRvLWNvbmZpZ3VyYXRpb24gZG9lcyBiZWZvcmUgQXBwS2l0IGJvb3RzLlxuICpcbiAqIC0gYFwicHJvdmlzaW9uXCJgOiByZXNvbHZlIHRoZSBMYWtlYmFzZSBjb25uZWN0aW9uIGludG8gYHByb2Nlc3MuZW52YCwgdGhlblxuICogICBncmFudCB0aGUgQXBwS2l0IGNhY2hlIHNjaGVtYSB0byB0aGUgY29ubmVjdGluZyByb2xlLlxuICogLSBgXCJlbnZcImA6IHJlc29sdmUgdGhlIExha2ViYXNlIGNvbm5lY3Rpb24gaW50byBgcHJvY2Vzcy5lbnZgIG9ubHkuXG4gKlxuICogT21pdCBpdCB0byBnZXQgYFwicHJvdmlzaW9uXCJgIGdhdGVkIG9uIGEgYGxha2ViYXNlYCBwbHVnaW4gYmVpbmcgcmVnaXN0ZXJlZDtcbiAqIHNldCBpdCBleHBsaWNpdGx5IHRvIHJ1biByZWdhcmRsZXNzIG9mIHRoZSBwbHVnaW4gbGlzdCwgb3IgcGFzcyBgZmFsc2VgIHRvXG4gKiBza2lwIGF1dG8tY29uZmlndXJhdGlvbiBlbnRpcmVseS5cbiAqXG4gKiBTZXQgaXQgRVhQTElDSVRMWSBvbiBhbiBhcHAgdGhhdCBoYXMgbm8gYGxha2ViYXNlKClgIHBsdWdpbiBidXQgc3RpbGwgd2FudHNcbiAqIEFwcEtpdCdzIFBFUlNJU1RFTlQgY2FjaGUuIEFwcEtpdCBwaWNrcyBMYWtlYmFzZSBmb3IgYENhY2hlTWFuYWdlcmAgb25seSB3aGVuXG4gKiBgY3JlYXRlTGFrZWJhc2VQb29sKClgIGNhbiBidWlsZCBhIHBvb2wsIGFuZCB0aGF0IHJlYWRzIGBMQUtFQkFTRV9FTkRQT0lOVGAgL1xuICogYFBHSE9TVGAgLyBgUEdEQVRBQkFTRWAgc3RyYWlnaHQgZnJvbSBgcHJvY2Vzcy5lbnZgIC0gc28gd2l0aG91dCB0aGlzIHN0ZXAgdGhlXG4gKiBjYWNoZSBzaWxlbnRseSBmYWxscyBiYWNrIHRvIGluLW1lbW9yeSwgYW5kIGFueXRoaW5nIGl0IGhvbGRzIChhIHNlc3Npb25cbiAqIHNpZ25pbmcga2V5LCBhIG9uZS10aW1lIGNvZGUpIGlzIGxvc3Qgb24gcmVzdGFydC4gYFwiZW52XCJgIGlzIHRoZSByaWdodCBtb2RlXG4gKiB0aGVyZTogdGhlIGFwcCBTUCBjYW5ub3QgZ3JhbnQgb24gdGhlIGNhY2hlIHNjaGVtYSBhbnl3YXkuXG4gKi9cbmV4cG9ydCB0eXBlIEF1dG9Db25maWd1cmVNb2RlID0gXCJlbnZcIiB8IFwicHJvdmlzaW9uXCI7XG5cbi8qKiBBcHBLaXQncyBgY3JlYXRlQXBwYCBjb25maWcgcGx1cyB0aGUgZGJ4LXRvb2xzIGF1dG8tY29uZmlndXJhdGlvbiBzd2l0Y2guICovXG5leHBvcnQgdHlwZSBDcmVhdGVBcHBDb25maWc8VCBleHRlbmRzIEFwcEtpdFBsdWdpbnMgPSBBcHBLaXRQbHVnaW5zPiA9IE9taXQ8XG4gIEFwcEtpdENyZWF0ZUFwcENvbmZpZyxcbiAgXCJwbHVnaW5zXCIgfCBcIm9uUGx1Z2luc1JlYWR5XCJcbj4gJiB7XG4gIHBsdWdpbnM/OiBUO1xuICBvblBsdWdpbnNSZWFkeT86IChhcHBraXQ6IFBsdWdpbk1hcDxUPikgPT4gdm9pZCB8IFByb21pc2U8dm9pZD47XG4gIC8qKiBBdXRvLWNvbmZpZ3VyYXRpb24gdG8gcnVuIGJlZm9yZSBBcHBLaXQgYm9vdHMuIERlZmF1bHRzIHRvIGBcInByb3Zpc2lvblwiYC4gKi9cbiAgYXV0b0NvbmZpZ3VyZT86IEF1dG9Db25maWd1cmVNb2RlIHwgZmFsc2U7XG4gIC8qKlxuICAgKiBPbmUgb3IgbWFueSB7QGxpbmsgSW50ZXJjZXB0b3J9cyBoYW5kZWQgYW4ge0BsaW5rIEludGVyY2VwdG9yQ29udGV4dH0gb25jZVxuICAgKiBhdXRvLWNvbmZpZ3VyYXRpb24gaGFzIGNvbXB1dGVkIHRoZSBlbnYuIEVhY2ggcmVjZWl2ZXMgdGhlIHJlc29sdmVkIGVudiwgYW5cbiAgICogQXBwS2l0LWxpZmVjeWNsZSBob29rLCBhbmQgYGJpbmRQcm9jZXNzYCBmb3IgY29uY3VycmVudGx5LXN0eWxlIHN1cGVydmlzaW9uIC1cbiAgICogc2VlIGAuL2ludGVyY2VwdG9yYC4gVGhlIHR1bm5lbCBpcyB0aGUgcHJpbWFyeSBjb25zdW1lci5cbiAgICovXG4gIGludGVyY2VwdG9yPzogSW50ZXJjZXB0b3IgfCBJbnRlcmNlcHRvcltdO1xufTtcblxuY29uc3QgbG9nZ2VyID0gbG9nLmxvZ2dlcihcImNyZWF0ZS1hcHBcIik7XG5cbmNvbnN0IExBS0VCQVNFX1BMVUdJTiA9IFwibGFrZWJhc2VcIjtcbmNvbnN0IERFRkFVTFRfQVVUT19DT05GSUdVUkU6IEF1dG9Db25maWd1cmVNb2RlID0gXCJwcm92aXNpb25cIjtcblxuLyoqXG4gKiBVcHBlciBib3VuZCBvbiBib290LXRpbWUgYXV0by1jb25maWd1cmF0aW9uLiBJdCBydW5zIGFzIHRoZSBzZXJ2aWNlIHByaW5jaXBhbFxuICogYmVmb3JlIGFueSBwbHVnaW4gaXMgY29uc3RydWN0ZWQsIHNvIGl0IHNpdHMgb3V0c2lkZSBBcHBLaXQncyBpbnRlcmNlcHRvclxuICogY2hhaW4gYW5kIGluaGVyaXRzIG5vIHRpbWVvdXQsIHJldHJ5LCBvciB0ZWxlbWV0cnkgZnJvbSBpdC4gVGhlIGJ1ZGdldCBjb3ZlcnNcbiAqIHRoZSByZXNvbHZlcidzIG93biB3b3JzdCBjYXNlIChjcmVhdGUgYSBwcm9qZWN0LCB0aGVuIHdhaXQgZm9yIGl0cyBlbmRwb2ludClcbiAqIHBsdXMgdGhlIGNhY2hlLXNjaGVtYSBncmFudHMuXG4gKi9cbmNvbnN0IEFVVE9fQ09ORklHVVJFX1RJTUVPVVRfTVMgPSAxMSAqIDYwXzAwMDtcblxuZnVuY3Rpb24gdXNlc1BsdWdpbjxUIGV4dGVuZHMgQXBwS2l0UGx1Z2lucz4oXG4gIGNvbmZpZzogQ3JlYXRlQXBwQ29uZmlnPFQ+IHwgdW5kZWZpbmVkLFxuICBuYW1lOiBzdHJpbmcsXG4pOiBib29sZWFuIHtcbiAgcmV0dXJuIEJvb2xlYW4oY29uZmlnPy5wbHVnaW5zPy5zb21lKChlbnRyeSkgPT4gZW50cnkubmFtZSA9PT0gbmFtZSkpO1xufVxuXG4vKipcbiAqIFJ1biBlbmFibGVkIGF1dG8tY29uZmlndXJhdGlvbiBzdGVwcyB3aXRob3V0IGNhbGxpbmcgQXBwS2l0J3MgYGNyZWF0ZUFwcGAuXG4gKlxuICogTGFrZWJhc2UgUG9zdGdyZXMgcmVzb2x2ZXMgd2hlbiB7QGxpbmsgQ3JlYXRlQXBwQ29uZmlnLmF1dG9Db25maWd1cmV9IGlzIHNldFxuICogZXhwbGljaXRseSBvciBhIGBsYWtlYmFzZWAgcGx1Z2luIGlzIGxpc3RlZCBpbiBgY29uZmlnLnBsdWdpbnNgLiBgc2lnbmFsYFxuICogY2FuY2VscyB0aGUgcmVzb2x1dGlvbjsgaXQgaXMgY29tYmluZWQgd2l0aCBhbiBpbnRlcm5hbCBib290IHRpbWVvdXQgZWl0aGVyXG4gKiB3YXkuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7IGNyZWF0ZUFwcCB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuICpcbiAqIC8vIFBvcHVsYXRlIFBHSE9TVCAvIFBHREFUQUJBU0UgLyBMQUtFQkFTRV9FTkRQT0lOVCB3aXRob3V0IGJvb3RpbmcgQXBwS2l0LlxuICogYXdhaXQgY3JlYXRlQXBwLmF1dG9Db25maWd1cmUoeyBhdXRvQ29uZmlndXJlOiBcImVudlwiIH0pO1xuICovXG5leHBvcnQgYXN5bmMgZnVuY3Rpb24gYXV0b0NvbmZpZ3VyZTxUIGV4dGVuZHMgQXBwS2l0UGx1Z2lucz4oXG4gIGNvbmZpZz86IENyZWF0ZUFwcENvbmZpZzxUPixcbiAgc2lnbmFsPzogQWJvcnRTaWduYWwsXG4pOiBQcm9taXNlPExha2ViYXNlQ29ubmVjdGlvbiB8IHVuZGVmaW5lZD4ge1xuICBjb25zdCBtb2RlID0gY29uZmlnPy5hdXRvQ29uZmlndXJlID8/IERFRkFVTFRfQVVUT19DT05GSUdVUkU7XG4gIGNvbnN0IGV4cGxpY2l0ID0gY29uZmlnPy5hdXRvQ29uZmlndXJlICE9PSB1bmRlZmluZWQ7XG4gIGNvbnN0IGxha2ViYXNlUGx1Z2luUHJlc2VudCA9IHVzZXNQbHVnaW4oY29uZmlnLCBMQUtFQkFTRV9QTFVHSU4pO1xuICBpZiAobW9kZSA9PT0gZmFsc2UgfHwgIShleHBsaWNpdCB8fCBsYWtlYmFzZVBsdWdpblByZXNlbnQpKSB7XG4gICAgbG9nZ2VyLmluZm8oXCJyZWFkeVwiLCB7XG4gICAgICBhdXRvQ29uZmlndXJlOiBtb2RlLFxuICAgICAgbGFrZWJhc2VQbHVnaW5QcmVzZW50LFxuICAgICAgcHJvdmlzaW9uZWQ6IGZhbHNlLFxuICAgICAgc2tpcHBlZFJlYXNvbjogbW9kZSA9PT0gZmFsc2UgPyBcImRpc2FibGVkXCIgOiBcIm5vIGxha2ViYXNlIHBsdWdpblwiLFxuICAgIH0pO1xuICAgIHJldHVybiB1bmRlZmluZWQ7XG4gIH1cblxuICBjb25zdCBjb250cm9sbGVyID0gbmV3IEFib3J0Q29udHJvbGxlcigpO1xuICBhc3luYy50aWVBYm9ydFNpZ25hbChjb250cm9sbGVyLCBzaWduYWwpO1xuICBhc3luYy50aWVBYm9ydFNpZ25hbChjb250cm9sbGVyLCBBYm9ydFNpZ25hbC50aW1lb3V0KEFVVE9fQ09ORklHVVJFX1RJTUVPVVRfTVMpKTtcblxuICBjb25zdCBwcm92aXNpb24gPSBtb2RlID09PSBcInByb3Zpc2lvblwiO1xuICBjb25zdCByZXNvbHZlZCA9IGF3YWl0IGF1dG9Db25maWd1cmVMYWtlYmFzZShwcm92aXNpb24sIGNvbnRyb2xsZXIuc2lnbmFsKTtcbiAgbG9nZ2VyLmluZm8oXCJyZWFkeVwiLCB7IGF1dG9Db25maWd1cmU6IG1vZGUsIGxha2ViYXNlUGx1Z2luUHJlc2VudCwgcHJvdmlzaW9uZWQ6IHByb3Zpc2lvbiB9KTtcbiAgcmV0dXJuIHJlc29sdmVkO1xufVxuXG4vKipcbiAqIFJlc29sdmUgTGFrZWJhc2UgUG9zdGdyZXMgY29ubmVjdGlvbiBpbmZvLCB3cml0ZSB0aGUgcmVzb2x2ZWQgdmFsdWVzIHRvXG4gKiBgcHJvY2Vzcy5lbnZgLCBhbmQgcmV0dXJuIHRoZSByZWNvcmQuIFVzZWQgYnkge0BsaW5rIGF1dG9Db25maWd1cmV9OyBjYWxsXG4gKiB7QGxpbmsgYXBwbHlMYWtlYmFzZUVudn0gZGlyZWN0bHkgd2hlbiBmaW5lciBjb250cm9sIGlzIG5lZWRlZCAoYSBkaWZmZXJlbnRcbiAqIGBhdXRvQ3JlYXRlYCBwb2xpY3ksIG9yIGEgY2FsbGVyIHRoYXQgd2FudHMgdGhlIGVudiB3aXRob3V0IGJvb3RpbmcgQXBwS2l0KS5cbiAqL1xuYXN5bmMgZnVuY3Rpb24gYXV0b0NvbmZpZ3VyZUxha2ViYXNlKFxuICBwcm92aXNpb246IGJvb2xlYW4sXG4gIHNpZ25hbDogQWJvcnRTaWduYWwsXG4pOiBQcm9taXNlPExha2ViYXNlQ29ubmVjdGlvbj4ge1xuICBjb25zdCB7IHJlc29sdmVkLCB1c2VyIH0gPSBhd2FpdCBhcHBseUxha2ViYXNlRW52KHVuZGVmaW5lZCwgc2lnbmFsKTtcbiAgbG9nZ2VyLmluZm8oXCJlbnYgdXBkYXRlZFwiLCB7IC4uLnJlZGFjdExha2ViYXNlQ29ubmVjdGlvbihyZXNvbHZlZCksIHVzZXIgfSk7XG4gIGlmIChwcm92aXNpb24pIHtcbiAgICBhd2FpdCBwcm92aXNpb25DYWNoZVNjaGVtYSh1c2VyLCBsb2dnZXIpO1xuICB9XG4gIHJldHVybiByZXNvbHZlZDtcbn1cblxuZnVuY3Rpb24gcmVkYWN0TGFrZWJhc2VDb25uZWN0aW9uKHJlc29sdmVkOiBMYWtlYmFzZUNvbm5lY3Rpb24pOiBSZWNvcmQ8c3RyaW5nLCB1bmtub3duPiB7XG4gIHJldHVybiB7XG4gICAgcHJvamVjdDogcmVzb2x2ZWQucHJvamVjdCxcbiAgICBicmFuY2g6IHJlc29sdmVkLmJyYW5jaCxcbiAgICBlbmRwb2ludDogcmVzb2x2ZWQuZW5kcG9pbnQsXG4gICAgZGF0YWJhc2U6IHJlc29sdmVkLmRhdGFiYXNlLFxuICAgIGhvc3Q6IHJlc29sdmVkLmhvc3QsXG4gICAgcG9ydDogcmVzb2x2ZWQucG9ydCxcbiAgICBzc2xNb2RlOiByZXNvbHZlZC5zc2xNb2RlLFxuICB9O1xufVxuXG4vKiogQnVpbGQgdGhlIHtAbGluayBSZXNvbHZlZEFwcEVudn0gaW50ZXJjZXB0b3JzIHJlYWQsIGZyb20gdGhlIGF1dG8tY29uZmlnIHJlc3VsdC4gKi9cbmZ1bmN0aW9uIHJlc29sdmVkQXBwRW52KGxha2ViYXNlOiBMYWtlYmFzZUNvbm5lY3Rpb24gfCB1bmRlZmluZWQpOiBSZXNvbHZlZEFwcEVudiB7XG4gIHJldHVybiB7XG4gICAgLi4uKGxha2ViYXNlID8geyBsYWtlYmFzZSB9IDoge30pLFxuICAgIC4uLihwcm9jZXNzLmVudi5EQVRBQlJJQ0tTX0hPU1QgPyB7IGRhdGFicmlja3NIb3N0OiBwcm9jZXNzLmVudi5EQVRBQlJJQ0tTX0hPU1QgfSA6IHt9KSxcbiAgfTtcbn1cblxuLyoqIE5vcm1hbGl6ZSB0aGUgYGludGVyY2VwdG9yPzogSW50ZXJjZXB0b3IgfCBJbnRlcmNlcHRvcltdYCBvcHRpb24gdG8gYW4gYXJyYXkuICovXG5mdW5jdGlvbiBpbnRlcmNlcHRvckxpc3QoaW50ZXJjZXB0b3I6IENyZWF0ZUFwcENvbmZpZ1tcImludGVyY2VwdG9yXCJdKTogSW50ZXJjZXB0b3JbXSB7XG4gIGlmICghaW50ZXJjZXB0b3IpIHJldHVybiBbXTtcbiAgcmV0dXJuIEFycmF5LmlzQXJyYXkoaW50ZXJjZXB0b3IpID8gaW50ZXJjZXB0b3IgOiBbaW50ZXJjZXB0b3JdO1xufVxuXG4vKipcbiAqIEF1dG8tY29uZmlndXJpbmcgZHJvcC1pbiBmb3IgQXBwS2l0J3MgYGNyZWF0ZUFwcGA6IHNhbWUgY29uZmlnLCBzYW1lIHR5cGVkXG4gKiBwbHVnaW4tZXhwb3J0IG1hcCwgd2l0aCB7QGxpbmsgYXV0b0NvbmZpZ3VyZX0gcnVuIGZpcnN0LlxuICpcbiAqIFdoZW4ge0BsaW5rIENyZWF0ZUFwcENvbmZpZy5pbnRlcmNlcHRvcn1zIGFyZSBnaXZlbiwgZWFjaCBpcyBpbnZva2VkIHdpdGggYW5cbiAqIHtAbGluayBJbnRlcmNlcHRvckNvbnRleHR9IEFGVEVSIGF1dG8tY29uZmlndXJhdGlvbiBjb21wdXRlcyB0aGUgZW52IGFuZCBCRUZPUkVcbiAqIEFwcEtpdCBib290cyAtIHNvIGFuIGludGVyY2VwdG9yIGNhbiByZWFkIHRoZSByZXNvbHZlZCBjb25uZWN0aW9uLCByZWdpc3RlclxuICogbGlmZWN5Y2xlIGhhbmRsZXJzLCBhbmQgYGJpbmRQcm9jZXNzYCBhIGNoaWxkLiBBIGhpZGRlbiB7QGxpbmsgbGlmZWN5Y2xlQnJpZGdlfVxuICogcGx1Z2luIGlzIGluamVjdGVkIHNvIHRob3NlIGBvbkxpZmVjeWNsZWAgaGFuZGxlcnMgZmlyZSBvbiB0aGUgZ2VudWluZSBBcHBLaXRcbiAqIGV2ZW50czsgaXQgaGFzIG5vIGV4cG9ydHMsIHNvIHRoZSByZXR1cm5lZCB7QGxpbmsgUGx1Z2luTWFwfSBpcyB1bmNoYW5nZWQuXG4gKlxuICogQGV4YW1wbGVcbiAqIGltcG9ydCB7IGNyZWF0ZUFwcCB9IGZyb20gXCJAZGJ4LXRvb2xzL2FwcGtpdFwiO1xuICogaW1wb3J0IHsgbGFrZWJhc2UsIHNlcnZlciB9IGZyb20gXCJAZGF0YWJyaWNrcy9hcHBraXRcIjtcbiAqXG4gKiBjb25zdCBhcHAgPSBhd2FpdCBjcmVhdGVBcHAuY3JlYXRlQXBwKHsgcGx1Z2luczogW3NlcnZlcigpLCBsYWtlYmFzZSgpXSB9KTtcbiAqL1xuZXhwb3J0IGFzeW5jIGZ1bmN0aW9uIGNyZWF0ZUFwcDxUIGV4dGVuZHMgQXBwS2l0UGx1Z2lucz4oXG4gIGNvbmZpZz86IENyZWF0ZUFwcENvbmZpZzxUPixcbik6IFByb21pc2U8UGx1Z2luTWFwPFQ+PiB7XG4gIGNvbnN0IGxha2ViYXNlID0gYXdhaXQgYXV0b0NvbmZpZ3VyZShjb25maWcpO1xuICBjb25zdCBhcHBDb25maWcgPSB7IC4uLmNvbmZpZyB9O1xuICBkZWxldGUgYXBwQ29uZmlnLmF1dG9Db25maWd1cmU7XG4gIGRlbGV0ZSBhcHBDb25maWcuaW50ZXJjZXB0b3I7XG5cbiAgY29uc3QgaW50ZXJjZXB0b3JzID0gaW50ZXJjZXB0b3JMaXN0KGNvbmZpZz8uaW50ZXJjZXB0b3IpO1xuICBpZiAoaW50ZXJjZXB0b3JzLmxlbmd0aCA9PT0gMCkge1xuICAgIHJldHVybiBhcHBraXRDcmVhdGVBcHA8VD4oYXBwQ29uZmlnKTtcbiAgfVxuXG4gIC8vIEJ1aWxkIHRoZSBjb250ZXh0IGZyb20gdGhlIGNvbXB1dGVkIGVudiwgcnVuIGVhY2ggaW50ZXJjZXB0b3IgKHRoZXkgcmVnaXN0ZXJcbiAgLy8gbGlmZWN5Y2xlIGhhbmRsZXJzICsgYmluZCBwcm9jZXNzZXMpLCB0aGVuIGluamVjdCB0aGUgYnJpZGdlIHRoYXQgcmVsYXlzIHRoZVxuICAvLyBSRUFMIEFwcEtpdCBsaWZlY3ljbGUgaW50byBgcnVudGltZS5lbWl0TGlmZWN5Y2xlYCBkdXJpbmcgaXRzIGBzZXR1cCgpYC5cbiAgY29uc3QgcnVudGltZTogSW50ZXJjZXB0b3JSdW50aW1lID0gY3JlYXRlSW50ZXJjZXB0b3JDb250ZXh0KHJlc29sdmVkQXBwRW52KGxha2ViYXNlKSk7XG4gIGZvciAoY29uc3QgaW50ZXJjZXB0b3Igb2YgaW50ZXJjZXB0b3JzKSB7XG4gICAgYXdhaXQgaW50ZXJjZXB0b3IocnVudGltZS5jb250ZXh0KTtcbiAgfVxuICAvLyBBcHBlbmQgdGhlIGJyaWRnZSB0byB0aGUgcGx1Z2lucyB0dXBsZS4gSXQgaXMgaGlkZGVuIGFuZCBleHBvcnRzIG5vdGhpbmcsIHNvXG4gIC8vIHRoZSByZXR1cm5lZCBtYXAgc3RpbGwgbWF0Y2hlcyBgUGx1Z2luTWFwPFQ+YDsgdGhlIGNhc3QgKHRocm91Z2ggYHVua25vd25gKSBpc1xuICAvLyBvbmx5IGJlY2F1c2UgYXBwZW5kaW5nIHdpZGVucyB0aGUgdHVwbGUgdHlwZSBiZXlvbmQgYFRgLlxuICBhcHBDb25maWcucGx1Z2lucyA9IFsuLi4oYXBwQ29uZmlnLnBsdWdpbnMgPz8gW10pLCBsaWZlY3ljbGVCcmlkZ2UoeyBydW50aW1lIH0pXSBhcyB1bmtub3duIGFzIFQ7XG4gIHJldHVybiBhcHBraXRDcmVhdGVBcHA8VD4oYXBwQ29uZmlnKTtcbn1cbiJdfQ==
|
package/src/create-app.ts
DELETED
|
@@ -1,230 +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
|
-
|
|
24
|
-
import { createApp as appkitCreateApp } from "@databricks/appkit";
|
|
25
|
-
// AppKit's root barrel re-exports `PluginData` but not `PluginMap`; the package
|
|
26
|
-
// publishes this subpath for exactly that type.
|
|
27
|
-
import type { PluginMap } from "@databricks/appkit/dist/shared/src/plugin";
|
|
28
|
-
import { async, log } from "@dbx-tools/shared-core";
|
|
29
|
-
|
|
30
|
-
import {
|
|
31
|
-
createInterceptorContext,
|
|
32
|
-
type Interceptor,
|
|
33
|
-
type InterceptorRuntime,
|
|
34
|
-
lifecycleBridge,
|
|
35
|
-
type ResolvedAppEnv,
|
|
36
|
-
} from "./interceptor.ts";
|
|
37
|
-
import { applyLakebaseEnv, type LakebaseConnection } from "./lakebase-resolver.ts";
|
|
38
|
-
import { provisionCacheSchema } from "./provision.ts";
|
|
39
|
-
|
|
40
|
-
type AppKitCreateAppConfig = NonNullable<Parameters<typeof appkitCreateApp>[0]>;
|
|
41
|
-
type AppKitPlugins = NonNullable<AppKitCreateAppConfig["plugins"]>;
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* What auto-configuration does before AppKit boots.
|
|
45
|
-
*
|
|
46
|
-
* - `"provision"`: resolve the Lakebase connection into `process.env`, then
|
|
47
|
-
* grant the AppKit cache schema to the connecting role.
|
|
48
|
-
* - `"env"`: resolve the Lakebase connection into `process.env` only.
|
|
49
|
-
*
|
|
50
|
-
* Omit it to get `"provision"` gated on a `lakebase` plugin being registered;
|
|
51
|
-
* set it explicitly to run regardless of the plugin list, or pass `false` to
|
|
52
|
-
* skip auto-configuration entirely.
|
|
53
|
-
*
|
|
54
|
-
* Set it EXPLICITLY on an app that has no `lakebase()` plugin but still wants
|
|
55
|
-
* AppKit's PERSISTENT cache. AppKit picks Lakebase for `CacheManager` only when
|
|
56
|
-
* `createLakebasePool()` can build a pool, and that reads `LAKEBASE_ENDPOINT` /
|
|
57
|
-
* `PGHOST` / `PGDATABASE` straight from `process.env` - so without this step the
|
|
58
|
-
* cache silently falls back to in-memory, and anything it holds (a session
|
|
59
|
-
* signing key, a one-time code) is lost on restart. `"env"` is the right mode
|
|
60
|
-
* there: the app SP cannot grant on the cache schema anyway.
|
|
61
|
-
*/
|
|
62
|
-
export type AutoConfigureMode = "env" | "provision";
|
|
63
|
-
|
|
64
|
-
/** AppKit's `createApp` config plus the dbx-tools auto-configuration switch. */
|
|
65
|
-
export type CreateAppConfig<T extends AppKitPlugins = AppKitPlugins> = Omit<
|
|
66
|
-
AppKitCreateAppConfig,
|
|
67
|
-
"plugins" | "onPluginsReady"
|
|
68
|
-
> & {
|
|
69
|
-
plugins?: T;
|
|
70
|
-
onPluginsReady?: (appkit: PluginMap<T>) => void | Promise<void>;
|
|
71
|
-
/** Auto-configuration to run before AppKit boots. Defaults to `"provision"`. */
|
|
72
|
-
autoConfigure?: AutoConfigureMode | false;
|
|
73
|
-
/**
|
|
74
|
-
* One or many {@link Interceptor}s handed an {@link InterceptorContext} once
|
|
75
|
-
* auto-configuration has computed the env. Each receives the resolved env, an
|
|
76
|
-
* AppKit-lifecycle hook, and `bindProcess` for concurrently-style supervision -
|
|
77
|
-
* see `./interceptor`. The tunnel is the primary consumer.
|
|
78
|
-
*/
|
|
79
|
-
interceptor?: Interceptor | Interceptor[];
|
|
80
|
-
};
|
|
81
|
-
|
|
82
|
-
const logger = log.logger("create-app");
|
|
83
|
-
|
|
84
|
-
const LAKEBASE_PLUGIN = "lakebase";
|
|
85
|
-
const DEFAULT_AUTO_CONFIGURE: AutoConfigureMode = "provision";
|
|
86
|
-
|
|
87
|
-
/**
|
|
88
|
-
* Upper bound on boot-time auto-configuration. It runs as the service principal
|
|
89
|
-
* before any plugin is constructed, so it sits outside AppKit's interceptor
|
|
90
|
-
* chain and inherits no timeout, retry, or telemetry from it. The budget covers
|
|
91
|
-
* the resolver's own worst case (create a project, then wait for its endpoint)
|
|
92
|
-
* plus the cache-schema grants.
|
|
93
|
-
*/
|
|
94
|
-
const AUTO_CONFIGURE_TIMEOUT_MS = 11 * 60_000;
|
|
95
|
-
|
|
96
|
-
function usesPlugin<T extends AppKitPlugins>(
|
|
97
|
-
config: CreateAppConfig<T> | undefined,
|
|
98
|
-
name: string,
|
|
99
|
-
): boolean {
|
|
100
|
-
return Boolean(config?.plugins?.some((entry) => entry.name === name));
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Run enabled auto-configuration steps without calling AppKit's `createApp`.
|
|
105
|
-
*
|
|
106
|
-
* Lakebase Postgres resolves when {@link CreateAppConfig.autoConfigure} is set
|
|
107
|
-
* explicitly or a `lakebase` plugin is listed in `config.plugins`. `signal`
|
|
108
|
-
* cancels the resolution; it is combined with an internal boot timeout either
|
|
109
|
-
* way.
|
|
110
|
-
*
|
|
111
|
-
* @example
|
|
112
|
-
* import { createApp } from "@dbx-tools/appkit";
|
|
113
|
-
*
|
|
114
|
-
* // Populate PGHOST / PGDATABASE / LAKEBASE_ENDPOINT without booting AppKit.
|
|
115
|
-
* await createApp.autoConfigure({ autoConfigure: "env" });
|
|
116
|
-
*/
|
|
117
|
-
export async function autoConfigure<T extends AppKitPlugins>(
|
|
118
|
-
config?: CreateAppConfig<T>,
|
|
119
|
-
signal?: AbortSignal,
|
|
120
|
-
): Promise<LakebaseConnection | undefined> {
|
|
121
|
-
const mode = config?.autoConfigure ?? DEFAULT_AUTO_CONFIGURE;
|
|
122
|
-
const explicit = config?.autoConfigure !== undefined;
|
|
123
|
-
const lakebasePluginPresent = usesPlugin(config, LAKEBASE_PLUGIN);
|
|
124
|
-
if (mode === false || !(explicit || lakebasePluginPresent)) {
|
|
125
|
-
logger.info("ready", {
|
|
126
|
-
autoConfigure: mode,
|
|
127
|
-
lakebasePluginPresent,
|
|
128
|
-
provisioned: false,
|
|
129
|
-
skippedReason: mode === false ? "disabled" : "no lakebase plugin",
|
|
130
|
-
});
|
|
131
|
-
return undefined;
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const controller = new AbortController();
|
|
135
|
-
async.tieAbortSignal(controller, signal);
|
|
136
|
-
async.tieAbortSignal(controller, AbortSignal.timeout(AUTO_CONFIGURE_TIMEOUT_MS));
|
|
137
|
-
|
|
138
|
-
const provision = mode === "provision";
|
|
139
|
-
const resolved = await autoConfigureLakebase(provision, controller.signal);
|
|
140
|
-
logger.info("ready", { autoConfigure: mode, lakebasePluginPresent, provisioned: provision });
|
|
141
|
-
return resolved;
|
|
142
|
-
}
|
|
143
|
-
|
|
144
|
-
/**
|
|
145
|
-
* Resolve Lakebase Postgres connection info, write the resolved values to
|
|
146
|
-
* `process.env`, and return the record. Used by {@link autoConfigure}; call
|
|
147
|
-
* {@link applyLakebaseEnv} directly when finer control is needed (a different
|
|
148
|
-
* `autoCreate` policy, or a caller that wants the env without booting AppKit).
|
|
149
|
-
*/
|
|
150
|
-
async function autoConfigureLakebase(
|
|
151
|
-
provision: boolean,
|
|
152
|
-
signal: AbortSignal,
|
|
153
|
-
): Promise<LakebaseConnection> {
|
|
154
|
-
const { resolved, user } = await applyLakebaseEnv(undefined, signal);
|
|
155
|
-
logger.info("env updated", { ...redactLakebaseConnection(resolved), user });
|
|
156
|
-
if (provision) {
|
|
157
|
-
await provisionCacheSchema(user, logger);
|
|
158
|
-
}
|
|
159
|
-
return resolved;
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
function redactLakebaseConnection(resolved: LakebaseConnection): Record<string, unknown> {
|
|
163
|
-
return {
|
|
164
|
-
project: resolved.project,
|
|
165
|
-
branch: resolved.branch,
|
|
166
|
-
endpoint: resolved.endpoint,
|
|
167
|
-
database: resolved.database,
|
|
168
|
-
host: resolved.host,
|
|
169
|
-
port: resolved.port,
|
|
170
|
-
sslMode: resolved.sslMode,
|
|
171
|
-
};
|
|
172
|
-
}
|
|
173
|
-
|
|
174
|
-
/** Build the {@link ResolvedAppEnv} interceptors read, from the auto-config result. */
|
|
175
|
-
function resolvedAppEnv(lakebase: LakebaseConnection | undefined): ResolvedAppEnv {
|
|
176
|
-
return {
|
|
177
|
-
...(lakebase ? { lakebase } : {}),
|
|
178
|
-
...(process.env.DATABRICKS_HOST ? { databricksHost: process.env.DATABRICKS_HOST } : {}),
|
|
179
|
-
};
|
|
180
|
-
}
|
|
181
|
-
|
|
182
|
-
/** Normalize the `interceptor?: Interceptor | Interceptor[]` option to an array. */
|
|
183
|
-
function interceptorList(interceptor: CreateAppConfig["interceptor"]): Interceptor[] {
|
|
184
|
-
if (!interceptor) return [];
|
|
185
|
-
return Array.isArray(interceptor) ? interceptor : [interceptor];
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
/**
|
|
189
|
-
* Auto-configuring drop-in for AppKit's `createApp`: same config, same typed
|
|
190
|
-
* plugin-export map, with {@link autoConfigure} run first.
|
|
191
|
-
*
|
|
192
|
-
* When {@link CreateAppConfig.interceptor}s are given, each is invoked with an
|
|
193
|
-
* {@link InterceptorContext} AFTER auto-configuration computes the env and BEFORE
|
|
194
|
-
* AppKit boots - so an interceptor can read the resolved connection, register
|
|
195
|
-
* lifecycle handlers, and `bindProcess` a child. A hidden {@link lifecycleBridge}
|
|
196
|
-
* plugin is injected so those `onLifecycle` handlers fire on the genuine AppKit
|
|
197
|
-
* events; it has no exports, so the returned {@link PluginMap} is unchanged.
|
|
198
|
-
*
|
|
199
|
-
* @example
|
|
200
|
-
* import { createApp } from "@dbx-tools/appkit";
|
|
201
|
-
* import { lakebase, server } from "@databricks/appkit";
|
|
202
|
-
*
|
|
203
|
-
* const app = await createApp.createApp({ plugins: [server(), lakebase()] });
|
|
204
|
-
*/
|
|
205
|
-
export async function createApp<T extends AppKitPlugins>(
|
|
206
|
-
config?: CreateAppConfig<T>,
|
|
207
|
-
): Promise<PluginMap<T>> {
|
|
208
|
-
const lakebase = await autoConfigure(config);
|
|
209
|
-
const appConfig = { ...config };
|
|
210
|
-
delete appConfig.autoConfigure;
|
|
211
|
-
delete appConfig.interceptor;
|
|
212
|
-
|
|
213
|
-
const interceptors = interceptorList(config?.interceptor);
|
|
214
|
-
if (interceptors.length === 0) {
|
|
215
|
-
return appkitCreateApp<T>(appConfig);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
// Build the context from the computed env, run each interceptor (they register
|
|
219
|
-
// lifecycle handlers + bind processes), then inject the bridge that relays the
|
|
220
|
-
// REAL AppKit lifecycle into `runtime.emitLifecycle` during its `setup()`.
|
|
221
|
-
const runtime: InterceptorRuntime = createInterceptorContext(resolvedAppEnv(lakebase));
|
|
222
|
-
for (const interceptor of interceptors) {
|
|
223
|
-
await interceptor(runtime.context);
|
|
224
|
-
}
|
|
225
|
-
// Append the bridge to the plugins tuple. It is hidden and exports nothing, so
|
|
226
|
-
// the returned map still matches `PluginMap<T>`; the cast (through `unknown`) is
|
|
227
|
-
// only because appending widens the tuple type beyond `T`.
|
|
228
|
-
appConfig.plugins = [...(appConfig.plugins ?? []), lifecycleBridge({ runtime })] as unknown as T;
|
|
229
|
-
return appkitCreateApp<T>(appConfig);
|
|
230
|
-
}
|