@databricks/appkit 0.74.1 → 0.75.1
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 +4 -1
- package/dist/appkit/package.js +1 -1
- package/dist/beta.d.ts +5 -5
- package/dist/beta.js +5 -4
- package/dist/cache/index.js +2 -2
- package/dist/cache/storage/persistent.js +1 -1
- package/dist/cli/commands/agent/eval.js +78 -6
- package/dist/cli/commands/agent/eval.js.map +1 -1
- package/dist/cli/commands/registry/add.js +1 -1
- package/dist/cli/commands/registry/config-writer.js +1 -1
- package/dist/cli/index.js +4 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/connectors/index.js +1 -1
- package/dist/connectors/jobs/client.js +1 -1
- package/dist/connectors/lakebase/index.d.ts +2 -2
- package/dist/connectors/lakebase/index.d.ts.map +1 -1
- package/dist/connectors/lakebase/index.js +25 -2
- package/dist/connectors/lakebase/index.js.map +1 -1
- package/dist/connectors/mcp/client.js +1 -1
- package/dist/connectors/sql-warehouse/client.js +1 -1
- package/dist/context/service-context.js +1 -1
- package/dist/core/appkit.js +1 -1
- package/dist/database/errors.js +1 -1
- package/dist/evals/discover.d.ts +8 -1
- package/dist/evals/discover.d.ts.map +1 -1
- package/dist/evals/discover.js +11 -1
- package/dist/evals/discover.js.map +1 -1
- package/dist/evals/index.d.ts +3 -3
- package/dist/evals/index.js +2 -2
- package/dist/evals/run-evals.d.ts +9 -2
- package/dist/evals/run-evals.d.ts.map +1 -1
- package/dist/evals/run-evals.js +13 -2
- package/dist/evals/run-evals.js.map +1 -1
- package/dist/evals/types.d.ts +40 -2
- package/dist/evals/types.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/plugin/dev-reader.js +1 -1
- package/dist/plugin/interceptors/retry.js +1 -1
- package/dist/plugin/plugin.js +1 -1
- package/dist/plugins/analytics/analytics.js +1 -1
- package/dist/plugins/analytics/result-delivery.js +1 -1
- package/dist/plugins/beta-exports.generated.d.ts +1 -3
- package/dist/plugins/beta-exports.generated.js +0 -2
- package/dist/plugins/database/config.js +14 -0
- package/dist/plugins/database/config.js.map +1 -0
- package/dist/plugins/database/database.d.ts +22 -5
- package/dist/plugins/database/database.d.ts.map +1 -1
- package/dist/plugins/database/database.js +23 -8
- package/dist/plugins/database/database.js.map +1 -1
- package/dist/plugins/database/lifecycle.js +3 -3
- package/dist/plugins/database/lifecycle.js.map +1 -1
- package/dist/plugins/database/load-schema.js +40 -0
- package/dist/plugins/database/load-schema.js.map +1 -0
- package/dist/plugins/database/manifest.js +1 -1
- package/dist/plugins/database/types.d.ts +12 -3
- package/dist/plugins/database/types.d.ts.map +1 -1
- package/dist/plugins/files/plugin.js +1 -1
- package/dist/plugins/jobs/plugin.js +1 -1
- package/dist/plugins/lakebase/lakebase.js +1 -1
- package/dist/plugins/server/index.js +1 -1
- package/dist/plugins/server/vite-dev-server.js +1 -1
- package/dist/registry/manifest-loader.d.ts +1 -1
- package/dist/registry/manifest-loader.js +1 -1
- package/dist/registry/resource-registry.js +1 -1
- package/dist/shared/src/schemas/manifest.d.ts +33 -33
- package/dist/stream/arrow-stream-processor.js +1 -1
- package/dist/stream/stream-manager.js +1 -1
- package/docs/api/appkit/Function.database.md +30 -29
- package/docs/api/appkit/Function.findRootEvalConfig.md +18 -0
- package/docs/api/appkit/Function.loadRootEvalConfig.md +18 -0
- package/docs/api/appkit/Interface.EvalWebServer.md +47 -0
- package/docs/api/appkit/TypeAlias.IDatabaseConfig.md +8 -6
- package/docs/api/appkit.md +4 -1
- package/docs/plugins/database.md +46 -18
- package/llms.txt +4 -1
- package/package.json +1 -1
- package/sbom.cdx.json +1 -1
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
import { Plugin } from "../../plugin/plugin.js";
|
|
2
2
|
import "../../plugin/index.js";
|
|
3
|
+
import { assertFinalizedSchema } from "../../database/schema-builder/define-schema.js";
|
|
3
4
|
import { DatabasePluginError, databaseSetupFailed } from "../../database/errors.js";
|
|
5
|
+
import { assertDatabaseConfig } from "./config.js";
|
|
4
6
|
import { compileCrudTables } from "./crud/contract.js";
|
|
5
7
|
import { resolveCrudExposure } from "./crud/exposure.js";
|
|
6
8
|
import { routeOutcome } from "./crud/response.js";
|
|
7
9
|
import { createCreateHandler, createDeleteHandler, createDetailHandler, createListHandler, createUpdateHandler } from "./crud/routes.js";
|
|
8
10
|
import { createDatabaseState } from "./lifecycle.js";
|
|
11
|
+
import { loadDefaultDatabaseSchema } from "./load-schema.js";
|
|
9
12
|
import manifest_default from "./manifest.js";
|
|
10
13
|
|
|
11
14
|
//#region src/plugins/database/database.ts
|
|
@@ -21,9 +24,10 @@ var DatabasePlugin = class extends Plugin {
|
|
|
21
24
|
tables: [],
|
|
22
25
|
writes: /* @__PURE__ */ new Map()
|
|
23
26
|
};
|
|
24
|
-
|
|
27
|
+
resolvedSchema = null;
|
|
28
|
+
constructor(config = {}) {
|
|
29
|
+
assertDatabaseConfig(config);
|
|
25
30
|
super({ schema: config.schema });
|
|
26
|
-
if ("crudRoutes" in config) throw databaseSetupFailed("\"crudRoutes\" was renamed to \"api\". Use api: false to disable generated routes or api: { writes: false } for reads only.");
|
|
27
31
|
this.config = {
|
|
28
32
|
schema: config.schema,
|
|
29
33
|
api: config.api,
|
|
@@ -34,14 +38,23 @@ var DatabasePlugin = class extends Plugin {
|
|
|
34
38
|
async setup() {
|
|
35
39
|
if (this.draining || this.state) throw databaseSetupFailed();
|
|
36
40
|
if (!this.setupPromise) this.setupPromise = (async () => {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
41
|
+
const schema = this.config.schema === void 0 ? await loadDefaultDatabaseSchema() : this.config.schema;
|
|
42
|
+
try {
|
|
43
|
+
assertFinalizedSchema(schema);
|
|
44
|
+
} catch {
|
|
45
|
+
throw databaseSetupFailed("schema must be a finalized AppKit schema created with defineSchema().");
|
|
46
|
+
}
|
|
47
|
+
if (this.draining) throw databaseSetupFailed();
|
|
48
|
+
const exposure = resolveCrudExposure(this.config.api, Object.keys(schema.$tables));
|
|
49
|
+
for (const name of Object.keys(this.hooks() ?? {})) if (!Object.hasOwn(schema.$tables, name)) throw databaseSetupFailed(`hooks names undeclared table ${JSON.stringify(name)}. Use a table declared in schema.`);
|
|
50
|
+
const candidate = await createDatabaseState(schema, (operation, options) => this.execute(operation, options), this.hooks());
|
|
40
51
|
if (this.draining) {
|
|
41
52
|
candidate.deactivate();
|
|
42
53
|
await candidate.pool.end().catch(() => void 0);
|
|
43
54
|
throw databaseSetupFailed();
|
|
44
55
|
}
|
|
56
|
+
this.resolvedSchema = schema;
|
|
57
|
+
this.exposure = exposure;
|
|
45
58
|
this.state = candidate;
|
|
46
59
|
})();
|
|
47
60
|
return this.setupPromise;
|
|
@@ -49,7 +62,9 @@ var DatabasePlugin = class extends Plugin {
|
|
|
49
62
|
/** Register generated CRUD, subject to the configured table and write restrictions. */
|
|
50
63
|
injectRoutes(router) {
|
|
51
64
|
if (this.exposure.tables.length === 0) return;
|
|
52
|
-
const
|
|
65
|
+
const schema = this.resolvedSchema;
|
|
66
|
+
if (!schema) throw databaseSetupFailed();
|
|
67
|
+
const tables = compileCrudTables(Object.fromEntries(this.exposure.tables.map((name) => [name, schema.$tables[name]])));
|
|
53
68
|
const hooks = this.hooks();
|
|
54
69
|
const entities = () => this.exports();
|
|
55
70
|
for (const table of tables.values()) {
|
|
@@ -138,8 +153,8 @@ var DatabasePlugin = class extends Plugin {
|
|
|
138
153
|
});
|
|
139
154
|
}
|
|
140
155
|
};
|
|
141
|
-
|
|
142
|
-
|
|
156
|
+
function database(config = {}) {
|
|
157
|
+
assertDatabaseConfig(config);
|
|
143
158
|
return {
|
|
144
159
|
plugin: DatabasePlugin,
|
|
145
160
|
config,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"database.js","names":["manifest"],"sources":["../../../src/plugins/database/database.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePluginConfig, PluginConstructor } from \"shared\";\n\nimport {\n DatabasePluginError,\n databaseSetupFailed,\n} from \"../../database/errors\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { Plugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { compileCrudTables } from \"./crud/contract\";\nimport { type CrudExposure, resolveCrudExposure } from \"./crud/exposure\";\nimport { routeOutcome } from \"./crud/response\";\nimport {\n type CrudEntity,\n type CrudOperation,\n type CrudRouteDeps,\n createCreateHandler,\n createDeleteHandler,\n createDetailHandler,\n createListHandler,\n createUpdateHandler,\n} from \"./crud/routes\";\nimport type { DatabaseExports } from \"./entity-types\";\nimport { createDatabaseState, type DatabaseState } from \"./lifecycle\";\nimport manifest from \"./manifest.json\";\nimport type { DatabaseHooks, IDatabaseConfig } from \"./types\";\n\n/** Schema-driven database plugin */\nexport class DatabasePlugin<TSchema extends Schema> extends Plugin<\n IDatabaseConfig<TSchema>\n> {\n /** Plugin metadata and required PostgreSQL resource. */\n static manifest = manifest as PluginManifest<\"database\">;\n declare protected config: IDatabaseConfig<TSchema>;\n private state: DatabaseState | null = null;\n private setupPromise: Promise<void> | null = null;\n private draining = false;\n private shutdownPromise: Promise<void> | null = null;\n private exposure: CrudExposure = { tables: [], writes: new Map() };\n\n constructor(config: IDatabaseConfig<TSchema>) {\n super({ schema: config.schema });\n // Do not silently turn a previous opt-out into the default full API.\n if (\"crudRoutes\" in config) {\n throw databaseSetupFailed(\n '\"crudRoutes\" was renamed to \"api\". Use api: false to disable generated routes or api: { writes: false } for reads only.',\n );\n }\n this.config = {\n schema: config.schema,\n api: config.api,\n hooks: config.hooks,\n };\n }\n\n /** Build and verify one candidate state before publishing its exports. */\n async setup(): Promise<void> {\n if (this.draining || this.state) throw databaseSetupFailed();\n if (!this.setupPromise) {\n const attempt = (async () => {\n this.exposure = resolveCrudExposure(\n this.config.api,\n Object.keys(this.config.schema.$tables),\n );\n // A hook key naming no declared table would silently never run.\n for (const name of Object.keys(this.hooks() ?? {})) {\n if (!Object.hasOwn(this.config.schema.$tables, name)) {\n throw databaseSetupFailed(\n `hooks names undeclared table ${JSON.stringify(name)}. Use a table declared in schema.`,\n );\n }\n }\n const candidate = await createDatabaseState(\n this.config.schema,\n (operation, options) => this.execute(operation, options),\n this.hooks(),\n );\n if (this.draining) {\n // Setup may finish while shutdown is waiting; never publish that state.\n candidate.deactivate();\n await candidate.pool.end().catch(() => undefined);\n throw databaseSetupFailed();\n }\n this.state = candidate;\n })();\n this.setupPromise = attempt;\n }\n return this.setupPromise;\n }\n\n /** Register generated CRUD, subject to the configured table and write restrictions. */\n injectRoutes(router: express.Router): void {\n if (this.exposure.tables.length === 0) return;\n const tables = compileCrudTables(\n Object.fromEntries(\n this.exposure.tables.map((name) => [\n name,\n this.config.schema.$tables[name],\n ]),\n ),\n );\n const hooks = this.hooks();\n // Every exposed name is a declared table, so its export is an entity client.\n const entities = () =>\n this.exports() as unknown as Record<string, CrudEntity>;\n\n for (const table of tables.values()) {\n const deps: CrudRouteDeps = {\n table,\n entity: () => entities()[table.name],\n serialize: hooks?.[table.name]?.serialize,\n runRouteSpan: (operation, route, run) =>\n this.runRouteSpan(table.name, operation, route, run),\n };\n this.route(router, {\n name: `${table.name}.list`,\n method: \"get\",\n path: `/${table.name}`,\n handler: createListHandler(deps),\n });\n const writes = this.exposure.writes.get(table.name);\n if (writes?.has(\"create\")) {\n this.route(router, {\n name: `${table.name}.create`,\n method: \"post\",\n path: `/${table.name}`,\n handler: createCreateHandler(deps),\n });\n }\n // Addressing one row needs a public key.\n if (!table.primaryKey) continue;\n this.route(router, {\n name: `${table.name}.detail`,\n method: \"get\",\n path: `/${table.name}/:id`,\n handler: createDetailHandler(deps),\n });\n if (writes?.has(\"update\")) {\n this.route(router, {\n name: `${table.name}.update`,\n method: \"patch\",\n path: `/${table.name}/:id`,\n handler: createUpdateHandler(deps),\n });\n }\n if (writes?.has(\"delete\")) {\n this.route(router, {\n name: `${table.name}.delete`,\n method: \"delete\",\n path: `/${table.name}/:id`,\n handler: createDeleteHandler(deps),\n });\n }\n }\n }\n\n /** Typed hook keys are schema table names, which routing addresses at runtime. */\n private hooks(): DatabaseHooks | undefined {\n return this.config.hooks as DatabaseHooks | undefined;\n }\n\n /** Return the typed database API only while the plugin is active. */\n exports() {\n if (!this.state || this.draining)\n throw new DatabasePluginError(\"INTERNAL\", \"read\");\n // AppKit binds exported functions onto this object on every access.\n return Object.assign(\n Object.create(null),\n this.state.exports,\n ) as DatabaseExports;\n }\n\n /** Stop new work, wait for setup, and close the owned pool exactly once. */\n async shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.draining = true;\n this.shutdownPromise = (async () => {\n await this.setupPromise?.catch(() => undefined);\n const state = this.state;\n state?.deactivate();\n this.state = null;\n if (state) {\n try {\n await state.pool.end();\n } catch {\n throw new DatabasePluginError(\"INTERNAL\", \"shutdown\");\n }\n }\n })();\n return this.shutdownPromise;\n }\n\n /** Trace one generated route with allowlisted, low-cardinality attributes. */\n private runRouteSpan(\n table: string,\n operation: CrudOperation,\n route: string,\n run: () => Promise<void>,\n ): Promise<void> {\n return this.telemetry.startActiveSpan(\n \"database.crud.route\",\n {\n attributes: {\n table_name: table,\n operation,\n \"http.route\": `/api/${this.name}${route}`,\n },\n },\n async (span) => {\n try {\n await run();\n span.setAttribute(\"outcome\", \"success\");\n } catch (error) {\n span.setAttribute(\"outcome\", routeOutcome(error));\n throw error;\n } finally {\n span.end();\n }\n },\n );\n }\n}\n\n/** Create a typed database plugin registration for a finalized schema. */\nexport function database<TSchema extends Schema>(\n config: IDatabaseConfig<TSchema>,\n) {\n return {\n plugin: DatabasePlugin as unknown as PluginConstructor<\n BasePluginConfig,\n DatabasePlugin<TSchema>\n >,\n config,\n name: \"database\" as const,\n };\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"database.js","names":["manifest"],"sources":["../../../src/plugins/database/database.ts"],"sourcesContent":["import type express from \"express\";\nimport type { BasePluginConfig, PluginConstructor } from \"shared\";\n\nimport {\n DatabasePluginError,\n databaseSetupFailed,\n} from \"../../database/errors\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { Plugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { assertDatabaseConfig } from \"./config\";\nimport { compileCrudTables } from \"./crud/contract\";\nimport { type CrudExposure, resolveCrudExposure } from \"./crud/exposure\";\nimport { routeOutcome } from \"./crud/response\";\nimport {\n type CrudEntity,\n type CrudOperation,\n type CrudRouteDeps,\n createCreateHandler,\n createDeleteHandler,\n createDetailHandler,\n createListHandler,\n createUpdateHandler,\n} from \"./crud/routes\";\nimport type { DatabaseExports } from \"./entity-types\";\nimport { createDatabaseState, type DatabaseState } from \"./lifecycle\";\nimport { loadDefaultDatabaseSchema } from \"./load-schema\";\nimport manifest from \"./manifest.json\";\nimport type {\n DatabaseHooks,\n DefaultDatabaseSchema,\n IDatabaseConfig,\n} from \"./types\";\n\n/** Schema-driven database plugin */\nexport class DatabasePlugin<\n TSchema extends Schema = DefaultDatabaseSchema,\n> extends Plugin<IDatabaseConfig<TSchema>> {\n /** Plugin metadata and required PostgreSQL resource. */\n static manifest = manifest as PluginManifest<\"database\">;\n declare protected config: IDatabaseConfig<TSchema>;\n private state: DatabaseState | null = null;\n private setupPromise: Promise<void> | null = null;\n private draining = false;\n private shutdownPromise: Promise<void> | null = null;\n private exposure: CrudExposure = { tables: [], writes: new Map() };\n private resolvedSchema: Schema | null = null;\n\n constructor(config: IDatabaseConfig<TSchema> = {}) {\n assertDatabaseConfig(config);\n super({ schema: config.schema });\n this.config = {\n schema: config.schema,\n api: config.api,\n hooks: config.hooks,\n };\n }\n\n /** Build and verify one candidate state before publishing its exports. */\n async setup(): Promise<void> {\n if (this.draining || this.state) throw databaseSetupFailed();\n if (!this.setupPromise) {\n const attempt = (async () => {\n const schema =\n this.config.schema === undefined\n ? await loadDefaultDatabaseSchema()\n : this.config.schema;\n try {\n assertFinalizedSchema(schema);\n } catch {\n throw databaseSetupFailed(\n \"schema must be a finalized AppKit schema created with defineSchema().\",\n );\n }\n if (this.draining) throw databaseSetupFailed();\n const exposure = resolveCrudExposure(\n this.config.api,\n Object.keys(schema.$tables),\n );\n // A hook key naming no declared table would silently never run.\n for (const name of Object.keys(this.hooks() ?? {})) {\n if (!Object.hasOwn(schema.$tables, name)) {\n throw databaseSetupFailed(\n `hooks names undeclared table ${JSON.stringify(name)}. Use a table declared in schema.`,\n );\n }\n }\n const candidate = await createDatabaseState(\n schema,\n (operation, options) => this.execute(operation, options),\n this.hooks(),\n );\n if (this.draining) {\n // Setup may finish while shutdown is waiting; never publish that state.\n candidate.deactivate();\n await candidate.pool.end().catch(() => undefined);\n throw databaseSetupFailed();\n }\n this.resolvedSchema = schema;\n this.exposure = exposure;\n this.state = candidate;\n })();\n this.setupPromise = attempt;\n }\n return this.setupPromise;\n }\n\n /** Register generated CRUD, subject to the configured table and write restrictions. */\n injectRoutes(router: express.Router): void {\n if (this.exposure.tables.length === 0) return;\n const schema = this.resolvedSchema;\n if (!schema) throw databaseSetupFailed();\n const tables = compileCrudTables(\n Object.fromEntries(\n this.exposure.tables.map((name) => [name, schema.$tables[name]]),\n ),\n );\n const hooks = this.hooks();\n // Every exposed name is a declared table, so its export is an entity client.\n const entities = () =>\n this.exports() as unknown as Record<string, CrudEntity>;\n\n for (const table of tables.values()) {\n const deps: CrudRouteDeps = {\n table,\n entity: () => entities()[table.name],\n serialize: hooks?.[table.name]?.serialize,\n runRouteSpan: (operation, route, run) =>\n this.runRouteSpan(table.name, operation, route, run),\n };\n this.route(router, {\n name: `${table.name}.list`,\n method: \"get\",\n path: `/${table.name}`,\n handler: createListHandler(deps),\n });\n const writes = this.exposure.writes.get(table.name);\n if (writes?.has(\"create\")) {\n this.route(router, {\n name: `${table.name}.create`,\n method: \"post\",\n path: `/${table.name}`,\n handler: createCreateHandler(deps),\n });\n }\n // Addressing one row needs a public key.\n if (!table.primaryKey) continue;\n this.route(router, {\n name: `${table.name}.detail`,\n method: \"get\",\n path: `/${table.name}/:id`,\n handler: createDetailHandler(deps),\n });\n if (writes?.has(\"update\")) {\n this.route(router, {\n name: `${table.name}.update`,\n method: \"patch\",\n path: `/${table.name}/:id`,\n handler: createUpdateHandler(deps),\n });\n }\n if (writes?.has(\"delete\")) {\n this.route(router, {\n name: `${table.name}.delete`,\n method: \"delete\",\n path: `/${table.name}/:id`,\n handler: createDeleteHandler(deps),\n });\n }\n }\n }\n\n /** Typed hook keys are schema table names, which routing addresses at runtime. */\n private hooks(): DatabaseHooks | undefined {\n return this.config.hooks as DatabaseHooks | undefined;\n }\n\n /** Return the typed database API only while the plugin is active. */\n exports() {\n if (!this.state || this.draining)\n throw new DatabasePluginError(\"INTERNAL\", \"read\");\n // AppKit binds exported functions onto this object on every access.\n return Object.assign(\n Object.create(null),\n this.state.exports,\n ) as DatabaseExports;\n }\n\n /** Stop new work, wait for setup, and close the owned pool exactly once. */\n async shutdown(): Promise<void> {\n if (this.shutdownPromise) return this.shutdownPromise;\n this.draining = true;\n this.shutdownPromise = (async () => {\n await this.setupPromise?.catch(() => undefined);\n const state = this.state;\n state?.deactivate();\n this.state = null;\n if (state) {\n try {\n await state.pool.end();\n } catch {\n throw new DatabasePluginError(\"INTERNAL\", \"shutdown\");\n }\n }\n })();\n return this.shutdownPromise;\n }\n\n /** Trace one generated route with allowlisted, low-cardinality attributes. */\n private runRouteSpan(\n table: string,\n operation: CrudOperation,\n route: string,\n run: () => Promise<void>,\n ): Promise<void> {\n return this.telemetry.startActiveSpan(\n \"database.crud.route\",\n {\n attributes: {\n table_name: table,\n operation,\n \"http.route\": `/api/${this.name}${route}`,\n },\n },\n async (span) => {\n try {\n await run();\n span.setAttribute(\"outcome\", \"success\");\n } catch (error) {\n span.setAttribute(\"outcome\", routeOutcome(error));\n throw error;\n } finally {\n span.end();\n }\n },\n );\n }\n}\n\ntype DatabaseRegistration<TSchema extends Schema> = {\n plugin: PluginConstructor<BasePluginConfig, DatabasePlugin<TSchema>>;\n config: IDatabaseConfig<TSchema>;\n name: \"database\";\n};\n\n/**\n * Create the database plugin. Omit configuration to load\n * `config/database/schema.ts`, or supply a typed schema override.\n */\nexport function database<TSchema extends Schema>(\n config: IDatabaseConfig<TSchema> & { readonly schema: TSchema },\n): DatabaseRegistration<TSchema> & {\n config: IDatabaseConfig<TSchema> & { readonly schema: TSchema };\n};\n/**\n * Register the database plugin with opinionated defaults and full HTTP CRUD.\n * By default, setup loads the named `schema` export from the application's\n * `config/database/schema.ts`. Registration itself performs no file or database I/O.\n */\nexport function database<TSchema extends Schema = DefaultDatabaseSchema>(\n config?: IDatabaseConfig<TSchema>,\n): DatabaseRegistration<TSchema>;\nexport function database<TSchema extends Schema = DefaultDatabaseSchema>(\n config: IDatabaseConfig<TSchema> = {},\n): DatabaseRegistration<TSchema> {\n assertDatabaseConfig(config);\n return {\n plugin: DatabasePlugin as unknown as PluginConstructor<\n BasePluginConfig,\n DatabasePlugin<TSchema>\n >,\n config,\n name: \"database\" as const,\n };\n}\n"],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createLogger } from "../../logging/logger.js";
|
|
2
1
|
import { DatabaseValidationError } from "../../errors/database-validation.js";
|
|
3
2
|
import "../../errors/index.js";
|
|
4
|
-
import {
|
|
3
|
+
import { createLogger } from "../../logging/logger.js";
|
|
4
|
+
import { initializeLakebasePool } from "../../connectors/lakebase/index.js";
|
|
5
5
|
import { assertFinalizedSchema } from "../../database/schema-builder/define-schema.js";
|
|
6
6
|
import { DatabasePluginError, classifyDatabaseError } from "../../database/errors.js";
|
|
7
7
|
import { IDLE_IN_TRANSACTION_TIMEOUT_MS, STATEMENT_TIMEOUT_MS, TRANSACTION_TIMEOUT_MS } from "./defaults.js";
|
|
@@ -112,7 +112,7 @@ async function createDatabaseState(schema, execute, hooks) {
|
|
|
112
112
|
if (!active) throw new DatabasePluginError("INTERNAL", "runtime");
|
|
113
113
|
};
|
|
114
114
|
try {
|
|
115
|
-
pool =
|
|
115
|
+
pool = await initializeLakebasePool({
|
|
116
116
|
statement_timeout: STATEMENT_TIMEOUT_MS,
|
|
117
117
|
idle_in_transaction_session_timeout: IDLE_IN_TRANSACTION_TIMEOUT_MS
|
|
118
118
|
});
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"lifecycle.js","names":[],"sources":["../../../src/plugins/database/lifecycle.ts"],"sourcesContent":["import {
|
|
1
|
+
{"version":3,"file":"lifecycle.js","names":[],"sources":["../../../src/plugins/database/lifecycle.ts"],"sourcesContent":["import { initializeLakebasePool } from \"../../connectors/lakebase\";\nimport {\n classifyDatabaseError,\n DatabasePluginError,\n} from \"../../database/errors\";\nimport type { DataPath } from \"../../database/runtime\";\nimport {\n createDrizzleDataPath,\n createDrizzleDb,\n} from \"../../database/runtime/engine/drizzle-data-path\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\nimport { DatabaseValidationError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport {\n IDLE_IN_TRANSACTION_TIMEOUT_MS,\n STATEMENT_TIMEOUT_MS,\n TRANSACTION_TIMEOUT_MS,\n} from \"./defaults\";\nimport { EntityClient, type EntityExecute } from \"./entity-client\";\nimport type {\n DatabaseExports,\n SqlTag,\n TransactionClient,\n} from \"./entity-types\";\nimport { createMutationScope, type MutationScope } from \"./scope\";\nimport type { DatabaseHooks } from \"./types\";\n\nconst logger = createLogger(\"database\");\n\n/** Resources owned by one successfully initialized plugin instance. */\nexport interface DatabaseState {\n readonly pool: Awaited<ReturnType<typeof initializeLakebasePool>>;\n readonly exports: DatabaseExports;\n readonly deactivate: () => void;\n}\n\ninterface ExportContext {\n readonly schema: Schema;\n readonly getDataPath: () => DataPath;\n readonly execute: EntityExecute;\n readonly assertActive: () => void;\n readonly scope: MutationScope;\n readonly hooks?: DatabaseHooks;\n readonly transactionBound: boolean;\n}\n\n/** Execute value-only SQL on the bound DataPath without per-statement retries. */\nfunction buildSql(context: ExportContext): SqlTag {\n return async <T = Record<string, unknown>>(\n strings: TemplateStringsArray,\n ...values: unknown[]\n ) => {\n try {\n context.assertActive();\n if (context.transactionBound) {\n context.scope.consumeTransactionOperation();\n }\n // DataPath accepts value interpolation only and keeps Drizzle private.\n return await context.getDataPath().raw<T>(strings, ...values);\n } catch (error) {\n throw classifyDatabaseError(error, \"read\");\n }\n };\n}\n\n/** Build entities and SQL bound to either the root pool or one transaction. */\nfunction buildTransactionClient(context: ExportContext): TransactionClient {\n const result: Record<string, unknown> = Object.create(null);\n for (const [name, table] of Object.entries(context.schema.$tables)) {\n result[name] = new EntityClient({\n table,\n getDataPath: context.getDataPath,\n execute: context.execute,\n assertActive: context.assertActive,\n scope: context.scope,\n hooks: context.hooks?.[name],\n transactionBound: context.transactionBound,\n runInTransaction: (run) =>\n runTransaction(context, (tx) => run(entityOf(tx, name))),\n });\n }\n result.sql = buildSql(context);\n return result as TransactionClient;\n}\n\n/** Typegen owns the entity keys, so reaching one by table name stays dynamic. */\nfunction entityOf(tx: TransactionClient, name: string): EntityClient {\n return (tx as unknown as Record<string, EntityClient>)[name];\n}\n\n/** Reject from inside the driver callback so the driver rolls back on expiry. */\nfunction runWithTransactionDeadline<T>(run: () => Promise<T>): Promise<T> {\n let timer: ReturnType<typeof setTimeout>;\n const deadline = new Promise<never>((_, reject) => {\n timer = setTimeout(\n () => reject(new DatabasePluginError(\"TRANSIENT\", \"transaction\")),\n TRANSACTION_TIMEOUT_MS,\n );\n timer.unref?.();\n });\n return Promise.race([Promise.resolve().then(run), deadline]).finally(() =>\n clearTimeout(timer),\n );\n}\n\n/**\n * Run inside one transaction, joining the active one rather than nesting:\n * PostgreSQL has no nested transaction, and a savepoint would let part of a\n * hooked mutation survive a rollback.\n */\nfunction runTransaction<T>(\n context: ExportContext,\n callback: (tx: TransactionClient) => Promise<T>,\n): Promise<T> {\n context.assertActive();\n const active = context.scope.activeTransaction();\n if (active) return callback(active);\n return context.getDataPath().transaction(async (txDataPath) => {\n let open = true;\n const assertTransactionActive = () => {\n context.assertActive();\n if (!open) throw new DatabasePluginError(\"INTERNAL\", \"transaction\");\n };\n // The outer transaction owns execution; inner clients use its connection.\n const directExecute: EntityExecute = async (operation) => ({\n ok: true,\n data: await operation(),\n });\n const tx = buildTransactionClient({\n ...context,\n getDataPath: () => txDataPath,\n execute: directExecute,\n assertActive: assertTransactionActive,\n transactionBound: true,\n });\n try {\n return await runWithTransactionDeadline(() =>\n context.scope.runWithTransaction(tx, () => callback(tx)),\n );\n } finally {\n // Captured clients must not outlive the transaction callback.\n open = false;\n }\n });\n}\n\n/** Add the transaction entry point to the root database surface. */\nfunction buildDatabaseExports(context: ExportContext): DatabaseExports {\n const result = buildTransactionClient(context) as DatabaseExports;\n result.transaction = async <T>(\n callback: (tx: TransactionClient) => Promise<T>,\n ) => {\n try {\n return await runTransaction(context, callback);\n } catch (error) {\n if (error instanceof DatabaseValidationError) throw error;\n throw classifyDatabaseError(error, \"transaction\");\n }\n };\n return result;\n}\n\n/** Validate the schema, create one pool-backed API, and verify connectivity. */\nexport async function createDatabaseState<TSchema extends Schema>(\n schema: TSchema,\n execute: EntityExecute,\n hooks?: DatabaseHooks,\n): Promise<DatabaseState> {\n try {\n assertFinalizedSchema(schema);\n } catch (error) {\n logger.error(\"Database schema failed validation: %O\", error);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n\n let active = true;\n let pool: DatabaseState[\"pool\"] | undefined;\n const assertActive = () => {\n if (!active) throw new DatabasePluginError(\"INTERNAL\", \"runtime\");\n };\n try {\n pool = await initializeLakebasePool({\n statement_timeout: STATEMENT_TIMEOUT_MS,\n idle_in_transaction_session_timeout: IDLE_IN_TRANSACTION_TIMEOUT_MS,\n });\n const db = createDrizzleDb(pool, schema);\n const dataPath = createDrizzleDataPath(db, schema, {\n columnAccess: \"trusted\",\n });\n const exports = buildDatabaseExports({\n schema,\n getDataPath: () => dataPath,\n execute,\n assertActive,\n // One scope per state keeps concurrent instances from sharing a transaction.\n scope: createMutationScope(),\n hooks,\n transactionBound: false,\n });\n // Do not publish exports until an authenticated statement succeeds.\n await dataPath.raw`select 1`;\n return {\n pool,\n exports,\n deactivate: () => {\n active = false;\n },\n };\n } catch (error) {\n logger.error(\"Database setup failed: %O\", error);\n active = false;\n await pool?.end().catch(() => undefined);\n throw new DatabasePluginError(\"SETUP_FAILED\", \"setup\");\n }\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { assertFinalizedSchema } from "../../database/schema-builder/define-schema.js";
|
|
2
|
+
import { databaseSetupFailed } from "../../database/errors.js";
|
|
3
|
+
import { stat } from "node:fs/promises";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/plugins/database/load-schema.ts
|
|
7
|
+
/** Same application-relative convention used by appkit generate-types. */
|
|
8
|
+
const DEFAULT_SCHEMA_FILE = "config/database/schema.ts";
|
|
9
|
+
/** Load application-owned declarations during setup, never during registration. */
|
|
10
|
+
async function loadDefaultDatabaseSchema(root = process.cwd()) {
|
|
11
|
+
const file = path.resolve(root, DEFAULT_SCHEMA_FILE);
|
|
12
|
+
const label = JSON.stringify(file);
|
|
13
|
+
let isFile;
|
|
14
|
+
try {
|
|
15
|
+
isFile = (await stat(file)).isFile();
|
|
16
|
+
} catch {
|
|
17
|
+
throw databaseSetupFailed(`Cannot read the default schema at ${label}. Create ${DEFAULT_SCHEMA_FILE} with a named "schema" export, or pass database({ schema }). The path is relative to the application's working directory.`);
|
|
18
|
+
}
|
|
19
|
+
if (!isFile) throw databaseSetupFailed(`The default schema at ${label} must be a file.`);
|
|
20
|
+
let module;
|
|
21
|
+
try {
|
|
22
|
+
const { createJiti } = await import("jiti");
|
|
23
|
+
module = await createJiti(import.meta.url, { moduleCache: false }).import(file);
|
|
24
|
+
} catch {
|
|
25
|
+
throw databaseSetupFailed(`Could not load the default schema at ${label}. Check the module and its imports, or pass database({ schema }).`);
|
|
26
|
+
}
|
|
27
|
+
if (typeof module !== "object" || module === null || !Object.hasOwn(module, "schema")) throw databaseSetupFailed(`The default schema module at ${label} must export a named "schema" created with defineSchema().`);
|
|
28
|
+
let schema;
|
|
29
|
+
try {
|
|
30
|
+
schema = module.schema;
|
|
31
|
+
assertFinalizedSchema(schema);
|
|
32
|
+
} catch {
|
|
33
|
+
throw databaseSetupFailed(`The "schema" export from ${label} must be a finalized AppKit schema created with defineSchema().`);
|
|
34
|
+
}
|
|
35
|
+
return schema;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
//#endregion
|
|
39
|
+
export { loadDefaultDatabaseSchema };
|
|
40
|
+
//# sourceMappingURL=load-schema.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"load-schema.js","names":[],"sources":["../../../src/plugins/database/load-schema.ts"],"sourcesContent":["import { stat } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport { databaseSetupFailed } from \"../../database/errors\";\nimport type { Schema } from \"../../database/schema-builder\";\nimport { assertFinalizedSchema } from \"../../database/schema-builder/define-schema\";\n\n/** Same application-relative convention used by appkit generate-types. */\nconst DEFAULT_SCHEMA_FILE = \"config/database/schema.ts\";\n\n/** Load application-owned declarations during setup, never during registration. */\nexport async function loadDefaultDatabaseSchema(\n root = process.cwd(),\n): Promise<Schema> {\n const file = path.resolve(root, DEFAULT_SCHEMA_FILE);\n const label = JSON.stringify(file);\n let isFile: boolean;\n try {\n isFile = (await stat(file)).isFile();\n } catch {\n throw databaseSetupFailed(\n `Cannot read the default schema at ${label}. Create ${DEFAULT_SCHEMA_FILE} with a named \"schema\" export, or pass database({ schema }). The path is relative to the application's working directory.`,\n );\n }\n if (!isFile) {\n throw databaseSetupFailed(`The default schema at ${label} must be a file.`);\n }\n\n let module: unknown;\n try {\n // Jiti also loads TypeScript declarations in a plain Node production run.\n // No module cache: a fresh plugin instance must not inherit a stale schema.\n const { createJiti } = await import(\"jiti\");\n module = await createJiti(import.meta.url, { moduleCache: false }).import(\n file,\n );\n } catch {\n // Import errors can contain credentials, row values, or application internals.\n throw databaseSetupFailed(\n `Could not load the default schema at ${label}. Check the module and its imports, or pass database({ schema }).`,\n );\n }\n if (\n typeof module !== \"object\" ||\n module === null ||\n !Object.hasOwn(module, \"schema\")\n ) {\n throw databaseSetupFailed(\n `The default schema module at ${label} must export a named \"schema\" created with defineSchema().`,\n );\n }\n let schema: unknown;\n try {\n schema = (module as { schema: unknown }).schema;\n assertFinalizedSchema(schema);\n } catch {\n throw databaseSetupFailed(\n `The \"schema\" export from ${label} must be a finalized AppKit schema created with defineSchema().`,\n );\n }\n return schema;\n}\n"],"mappings":";;;;;;;AAQA,MAAM,sBAAsB;;AAG5B,eAAsB,0BACpB,OAAO,QAAQ,KAAK,EACH;CACjB,MAAM,OAAO,KAAK,QAAQ,MAAM,oBAAoB;CACpD,MAAM,QAAQ,KAAK,UAAU,KAAK;CAClC,IAAI;AACJ,KAAI;AACF,YAAU,MAAM,KAAK,KAAK,EAAE,QAAQ;SAC9B;AACN,QAAM,oBACJ,qCAAqC,MAAM,WAAW,oBAAoB,2HAC3E;;AAEH,KAAI,CAAC,OACH,OAAM,oBAAoB,yBAAyB,MAAM,kBAAkB;CAG7E,IAAI;AACJ,KAAI;EAGF,MAAM,EAAE,eAAe,MAAM,OAAO;AACpC,WAAS,MAAM,WAAW,OAAO,KAAK,KAAK,EAAE,aAAa,OAAO,CAAC,CAAC,OACjE,KACD;SACK;AAEN,QAAM,oBACJ,wCAAwC,MAAM,mEAC/C;;AAEH,KACE,OAAO,WAAW,YAClB,WAAW,QACX,CAAC,OAAO,OAAO,QAAQ,SAAS,CAEhC,OAAM,oBACJ,gCAAgC,MAAM,4DACvC;CAEH,IAAI;AACJ,KAAI;AACF,WAAU,OAA+B;AACzC,wBAAsB,OAAO;SACvB;AACN,QAAM,oBACJ,4BAA4B,MAAM,iEACnC;;AAEH,QAAO"}
|
|
@@ -1,7 +1,11 @@
|
|
|
1
|
+
import { DatabaseRegistry } from "../../database/contract/registry.js";
|
|
1
2
|
import { Schema } from "../../database/schema-builder/types.js";
|
|
2
3
|
import { EntityMutationHooks } from "./hooks.js";
|
|
3
4
|
|
|
4
5
|
//#region src/plugins/database/types.d.ts
|
|
6
|
+
type RegistryTableName = Extract<keyof DatabaseRegistry, string>;
|
|
7
|
+
/** @internal Infer conventional schema names from typegen, when available. */
|
|
8
|
+
type DefaultDatabaseSchema = Schema<[RegistryTableName] extends [never] ? string : RegistryTableName>;
|
|
5
9
|
/** Table names declared by one finalized schema. */
|
|
6
10
|
type SchemaTableName<TSchema extends Schema> = Extract<keyof TSchema["$tables"], string>;
|
|
7
11
|
/** Generated HTTP write operations. */
|
|
@@ -48,8 +52,13 @@ type EntityHooks<TTable extends string = string> = EntityMutationHooks<TTable> &
|
|
|
48
52
|
readonly serialize?: ReadSerializer;
|
|
49
53
|
};
|
|
50
54
|
/** Configuration for one schema-bound DatabasePlugin instance. */
|
|
51
|
-
type IDatabaseConfig<TSchema extends Schema> = {
|
|
52
|
-
|
|
55
|
+
type IDatabaseConfig<TSchema extends Schema = DefaultDatabaseSchema> = {
|
|
56
|
+
/**
|
|
57
|
+
* Explicit schema override. When omitted, setup loads the named `schema`
|
|
58
|
+
* export from `config/database/schema.ts` under the application's working
|
|
59
|
+
* directory. Missing or invalid declarations fail setup before pool creation.
|
|
60
|
+
*/
|
|
61
|
+
readonly schema?: TSchema;
|
|
53
62
|
/**
|
|
54
63
|
* Generated HTTP CRUD is enabled for all tables by default, using the app's
|
|
55
64
|
* service principal. Every admitted caller receives the enabled operations;
|
|
@@ -66,5 +75,5 @@ type IDatabaseConfig<TSchema extends Schema> = {
|
|
|
66
75
|
readonly hooks?: { readonly [TTable in SchemaTableName<TSchema>]?: EntityHooks<TTable> };
|
|
67
76
|
};
|
|
68
77
|
//#endregion
|
|
69
|
-
export { DatabaseApiConfig, DatabaseApiWriteOperation, DatabaseApiWritesConfig, EntityHooks, IDatabaseConfig, ReadSerializer, ReadSerializerContext };
|
|
78
|
+
export { DatabaseApiConfig, DatabaseApiWriteOperation, DatabaseApiWritesConfig, DefaultDatabaseSchema, EntityHooks, IDatabaseConfig, ReadSerializer, ReadSerializerContext };
|
|
70
79
|
//# sourceMappingURL=types.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/database/types.ts"],"mappings":";;;;;
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/database/types.ts"],"mappings":";;;;;KAIK,iBAAA,GAAoB,OAAA,OAAc,gBAAA;;KAG3B,qBAAA,GAAwB,MAAA,EACjC,iBAAA,6BAA8C,iBAAA;;KAIrC,eAAA,iBAAgC,MAAA,IAAU,OAAA,OAC9C,OAAA;AANR;AAAA,KAWY,yBAAA;;KAGA,uBAAA,iBAAwC,MAAA;EAbH,6EAiBlC,MAAA,YAAkB,eAAA,CAAgB,OAAA,KAlBP;EAAA,SAoB3B,UAAA,YAAsB,yBAAA;AAAA;;;;;AAfrC;;;;;;;;;;;;;KAmCY,iBAAA,iBAAkC,MAAA;EA7BlC,6EAiCG,MAAA,YAAkB,eAAA,CAAgB,OAAA;WAElC,MAAA,GAAS,uBAAA,CAAwB,OAAA;AAAA;AAhChD;AAAA,UAoCiB,qBAAA;EAAA,SACN,MAAA;EAAA,SACA,SAAA;AAAA;;;;;;KAQC,cAAA,IACV,GAAA,EAAK,MAAA,mBACL,OAAA,EAAS,qBAAA,KACN,MAAA;;KAGO,WAAA,mCACV,mBAAA,CAAoB,MAAA;EAAA,SACT,SAAA,GAAY,cAAA;AAAA;;KAOb,eAAA,iBAAgC,MAAA,GAAS,qBAAA;EAnCxB;;;;;EAAA,SAyClB,MAAA,GAAS,OAAA;EAnCI;;;;;;;;;;;;EAAA,SAgDb,GAAA,GAAM,iBAAA,CAAkB,OAAA;EAAA,SACxB,KAAA,yBACa,eAAA,CAAgB,OAAA,KAAY,WAAA,CAAY,MAAA;AAAA"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { ApiError } from "../../shared/src/workspace-client/errors.js";
|
|
2
|
-
import { createLogger } from "../../logging/logger.js";
|
|
3
2
|
import { AuthenticationError } from "../../errors/authentication.js";
|
|
4
3
|
import "../../errors/index.js";
|
|
5
4
|
import { ServiceContext } from "../../context/service-context.js";
|
|
5
|
+
import { createLogger } from "../../logging/logger.js";
|
|
6
6
|
import { isUserContext } from "../../context/user-context.js";
|
|
7
7
|
import { getCurrentUserId, getExecutionContext, getWorkspaceClient, runInUserContext } from "../../context/execution-context.js";
|
|
8
8
|
import "../../context/index.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
import { createLogger } from "../../logging/logger.js";
|
|
2
1
|
import { ExecutionError } from "../../errors/execution.js";
|
|
3
2
|
import { ValidationError } from "../../errors/validation.js";
|
|
4
3
|
import "../../errors/index.js";
|
|
4
|
+
import { createLogger } from "../../logging/logger.js";
|
|
5
5
|
import { getCurrentUserId, getWorkspaceClient } from "../../context/execution-context.js";
|
|
6
6
|
import "../../context/index.js";
|
|
7
7
|
import { Plugin } from "../../plugin/plugin.js";
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { createWorkspaceClient } from "../../shared/src/workspace-client/factory.js";
|
|
2
|
+
import { getClientOptions } from "../../context/client-options.js";
|
|
2
3
|
import { createLogger } from "../../logging/logger.js";
|
|
3
4
|
import { createLakebasePoolManager } from "../../connectors/lakebase/pool-manager.js";
|
|
4
|
-
import { getClientOptions } from "../../context/client-options.js";
|
|
5
5
|
import { getUserContext } from "../../context/execution-context.js";
|
|
6
6
|
import { RoutingPool } from "../../connectors/lakebase/routing-pool.js";
|
|
7
7
|
import { createLakebasePool, getLakebaseOrmConfig, getLakebasePgConfig, getUsernameWithApiLookup } from "../../connectors/lakebase/index.js";
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { camelToKebab } from "../../shared/src/naming.js";
|
|
2
|
-
import { createLogger } from "../../logging/logger.js";
|
|
3
2
|
import { AppKitError } from "../../errors/base.js";
|
|
4
3
|
import { ServerError } from "../../errors/server.js";
|
|
5
4
|
import "../../errors/index.js";
|
|
5
|
+
import { createLogger } from "../../logging/logger.js";
|
|
6
6
|
import { instrumentations } from "../../telemetry/instrumentations.js";
|
|
7
7
|
import "../../telemetry/index.js";
|
|
8
8
|
import { TelemetryReporter } from "../../internal-telemetry/reporter.js";
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createLogger } from "../../logging/logger.js";
|
|
2
1
|
import { ServerError } from "../../errors/server.js";
|
|
3
2
|
import "../../errors/index.js";
|
|
3
|
+
import { createLogger } from "../../logging/logger.js";
|
|
4
4
|
import { mergeConfigDedup } from "../../utils/vite-config-merge.js";
|
|
5
5
|
import { BaseServer } from "./base-server.js";
|
|
6
6
|
import { appKitServingTypesPlugin } from "../../type-generator/serving/vite-plugin.js";
|
|
@@ -53,8 +53,8 @@ declare function getPluginManifest(plugin: PluginConstructor): PluginManifest;
|
|
|
53
53
|
*/
|
|
54
54
|
declare function getResourceRequirements(plugin: PluginConstructor): {
|
|
55
55
|
required: boolean;
|
|
56
|
-
type: ResourceType;
|
|
57
56
|
description: string;
|
|
57
|
+
type: ResourceType;
|
|
58
58
|
permission: ResourcePermission;
|
|
59
59
|
alias: string;
|
|
60
60
|
resourceKey: string;
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { pluginManifestSchema } from "../shared/src/schemas/manifest.js";
|
|
2
|
-
import { createLogger } from "../logging/logger.js";
|
|
3
2
|
import { ConfigurationError } from "../errors/configuration.js";
|
|
4
3
|
import "../errors/index.js";
|
|
4
|
+
import { createLogger } from "../logging/logger.js";
|
|
5
5
|
import { PERMISSIONS_BY_TYPE, ResourceType } from "./types.generated.js";
|
|
6
6
|
import "./types.js";
|
|
7
7
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { createLogger } from "../logging/logger.js";
|
|
2
1
|
import { ConfigurationError } from "../errors/configuration.js";
|
|
3
2
|
import "../errors/index.js";
|
|
3
|
+
import { createLogger } from "../logging/logger.js";
|
|
4
4
|
import { formatWarningBanner } from "../utils/banner.js";
|
|
5
5
|
import { PERMISSION_HIERARCHY_BY_TYPE } from "./types.generated.js";
|
|
6
6
|
import "./types.js";
|