@databricks/appkit 0.20.2 → 0.20.3

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.
Files changed (91) hide show
  1. package/CLAUDE.md +5 -13
  2. package/NOTICE.md +3 -6
  3. package/dist/app/index.js.map +1 -1
  4. package/dist/appkit/package.js +1 -1
  5. package/dist/cli/commands/plugin/add-resource/add-resource.js.map +1 -1
  6. package/dist/connectors/files/client.js.map +1 -1
  7. package/dist/connectors/files/defaults.js +1 -1
  8. package/dist/connectors/files/defaults.js.map +1 -1
  9. package/dist/connectors/files/index.js +1 -1
  10. package/dist/connectors/genie/client.js.map +1 -1
  11. package/dist/connectors/genie/index.d.ts +0 -1
  12. package/dist/connectors/genie/index.js +0 -2
  13. package/dist/connectors/index.js +1 -3
  14. package/dist/connectors/lakebase/index.js.map +1 -1
  15. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  16. package/dist/context/index.d.ts +2 -2
  17. package/dist/context/index.js +0 -1
  18. package/dist/context/index.js.map +1 -1
  19. package/dist/index.d.ts +2 -1
  20. package/dist/logging/index.js +0 -13
  21. package/dist/logging/logger.js.map +1 -1
  22. package/dist/logging/sampling.js.map +1 -1
  23. package/dist/plugins/files/defaults.js +0 -2
  24. package/dist/plugins/files/defaults.js.map +1 -1
  25. package/dist/plugins/files/index.js +0 -1
  26. package/dist/plugins/genie/index.d.ts +1 -1
  27. package/dist/plugins/genie/types.d.ts +1 -1
  28. package/dist/plugins/genie/types.d.ts.map +1 -1
  29. package/dist/plugins/index.d.ts +2 -3
  30. package/dist/plugins/index.js +1 -2
  31. package/dist/plugins/lakebase/index.d.ts +1 -1
  32. package/dist/plugins/lakebase/index.js +1 -1
  33. package/dist/plugins/lakebase/lakebase.d.ts +1 -1
  34. package/dist/plugins/lakebase/lakebase.d.ts.map +1 -1
  35. package/dist/plugins/lakebase/lakebase.js +1 -1
  36. package/dist/plugins/lakebase/lakebase.js.map +1 -1
  37. package/dist/plugins/server/index.js.map +1 -1
  38. package/dist/plugins/server/utils.js.map +1 -1
  39. package/dist/registry/index.d.ts +3 -2
  40. package/dist/registry/manifest-loader.d.ts +4 -3
  41. package/dist/registry/manifest-loader.d.ts.map +1 -1
  42. package/dist/registry/types.d.ts +19 -57
  43. package/dist/registry/types.d.ts.map +1 -1
  44. package/dist/registry/types.generated.d.ts +1 -1
  45. package/dist/registry/types.js +7 -2
  46. package/dist/registry/types.js.map +1 -1
  47. package/dist/schemas/plugin-manifest.generated.d.ts +178 -0
  48. package/dist/schemas/plugin-manifest.generated.d.ts.map +1 -0
  49. package/dist/schemas/plugin-manifest.schema.json +2 -0
  50. package/dist/shared/src/cache.d.ts +1 -1
  51. package/dist/shared/src/execute.d.ts +7 -1
  52. package/dist/shared/src/execute.d.ts.map +1 -1
  53. package/dist/shared/src/index.d.ts +2 -1
  54. package/dist/shared/src/plugin.d.ts +19 -48
  55. package/dist/shared/src/plugin.d.ts.map +1 -1
  56. package/dist/shared/src/schemas/plugin-manifest.generated.d.ts +178 -0
  57. package/dist/shared/src/schemas/plugin-manifest.generated.d.ts.map +1 -0
  58. package/dist/stream/arrow-stream-processor.js.map +1 -1
  59. package/dist/stream/index.d.ts +1 -3
  60. package/dist/stream/index.js +0 -3
  61. package/dist/stream/stream-manager.js +1 -1
  62. package/dist/stream/types.js.map +1 -1
  63. package/dist/telemetry/config.js +1 -0
  64. package/dist/telemetry/config.js.map +1 -1
  65. package/dist/telemetry/index.d.ts +1 -1
  66. package/dist/telemetry/noop.js.map +1 -1
  67. package/dist/telemetry/types.d.ts +1 -1
  68. package/dist/type-generator/query-registry.js.map +1 -1
  69. package/dist/utils/path-exclusions.js.map +1 -1
  70. package/docs/api/appkit/Interface.CacheConfig.md +1 -1
  71. package/docs/api/appkit/Interface.PluginManifest.md +104 -6
  72. package/docs/api/appkit/Interface.ResourceEntry.md +5 -7
  73. package/docs/api/appkit/Interface.ResourceFieldEntry.md +2 -0
  74. package/docs/api/appkit/Interface.ResourceRequirement.md +63 -7
  75. package/docs/api/appkit/Interface.StreamExecutionSettings.md +1 -1
  76. package/docs/api/appkit/TypeAlias.PluginData.md +2 -0
  77. package/docs/api/appkit/TypeAlias.ToPlugin.md +2 -0
  78. package/docs/api/appkit.md +6 -6
  79. package/llms.txt +5 -13
  80. package/package.json +2 -2
  81. package/dist/stream/arrow-stream-processor.d.ts +0 -1
  82. package/dist/stream/buffers.d.ts +0 -1
  83. package/dist/stream/types.d.ts +0 -3
  84. package/docs/api/appkit-ui/data/AreaChart.md +0 -79
  85. package/docs/api/appkit-ui/data/BarChart.md +0 -74
  86. package/docs/api/appkit-ui/data/DonutChart.md +0 -72
  87. package/docs/api/appkit-ui/data/HeatmapChart.md +0 -91
  88. package/docs/api/appkit-ui/data/LineChart.md +0 -77
  89. package/docs/api/appkit-ui/data/PieChart.md +0 -72
  90. package/docs/api/appkit-ui/data/RadarChart.md +0 -74
  91. package/docs/api/appkit-ui/data/ScatterChart.md +0 -76
package/dist/index.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ import { ResourceFieldEntry } from "./shared/src/schemas/plugin-manifest.generated.js";
1
2
  import { BasePluginConfig, IAppRouter, PluginData, ToPlugin } from "./shared/src/plugin.js";
2
3
  import { CacheConfig } from "./shared/src/cache.js";
3
4
  import { StreamExecutionSettings } from "./shared/src/execute.js";
@@ -24,7 +25,7 @@ import { Plugin } from "./plugin/plugin.js";
24
25
  import { toPlugin } from "./plugin/to-plugin.js";
25
26
  import "./plugin/index.js";
26
27
  import { ResourcePermission, ResourceType } from "./registry/types.generated.js";
27
- import { ConfigSchema, PluginManifest, ResourceEntry, ResourceFieldEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
28
+ import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./registry/types.js";
28
29
  import { getPluginManifest, getResourceRequirements } from "./registry/manifest-loader.js";
29
30
  import { ResourceRegistry } from "./registry/resource-registry.js";
30
31
  import "./registry/index.js";
@@ -1,16 +1,3 @@
1
- import { DEFAULT_SAMPLING_CONFIG, shouldSample } from "./sampling.js";
2
- import { WideEvent } from "./wide-event.js";
3
- import { WideEventEmitter } from "./wide-event-emitter.js";
4
1
  import { createLogger } from "./logger.js";
5
- import { AppKitError } from "../errors/base.js";
6
- import { AuthenticationError } from "../errors/authentication.js";
7
- import { ConfigurationError } from "../errors/configuration.js";
8
- import { ConnectionError } from "../errors/connection.js";
9
- import { ExecutionError } from "../errors/execution.js";
10
- import { InitializationError } from "../errors/initialization.js";
11
- import { ServerError } from "../errors/server.js";
12
- import { TunnelError } from "../errors/tunnel.js";
13
- import { ValidationError } from "../errors/validation.js";
14
- import "@opentelemetry/api";
15
2
 
16
3
  export { };
@@ -1 +1 @@
1
- {"version":3,"file":"logger.js","names":["createObug"],"sources":["../../src/logging/logger.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { format } from \"node:util\";\nimport { trace } from \"@opentelemetry/api\";\nimport type { NextFunction, Request, Response } from \"express\";\nimport { createDebug as createObug } from \"obug\";\nimport { DEFAULT_SAMPLING_CONFIG, shouldSample } from \"./sampling\";\nimport { WideEvent } from \"./wide-event\";\nimport { WideEventEmitter } from \"./wide-event-emitter\";\n\n/**\n * Logger interface for AppKit components\n */\nexport interface Logger {\n /** Debug output (disabled by default, enable via DEBUG env var) */\n debug(message: string, ...args: unknown[]): void;\n debug(req: Request, message: string, ...args: unknown[]): void;\n\n /** Info output (always visible, for operational messages) */\n info(message: string, ...args: unknown[]): void;\n info(req: Request, message: string, ...args: unknown[]): void;\n\n /** Warning output (always visible, for degraded states) */\n warn(message: string, ...args: unknown[]): void;\n warn(req: Request, message: string, ...args: unknown[]): void;\n\n /** Error output (always visible, for failures) */\n error(message: string, ...args: unknown[]): void;\n error(req: Request, message: string, ...args: unknown[]): void;\n\n /** Get request-scoped WideEvent (from AsyncLocalStorage or explicit req) */\n event(req?: Request): WideEvent | undefined;\n}\n\n// AsyncLocalStorage for WideEvent context propagation\nconst eventStorage = new AsyncLocalStorage<WideEvent>();\n\n// WeakMap to store WideEvent per request (for explicit req usage)\nconst eventsByRequest = new WeakMap<Request, WideEvent>();\n\n// Global emitter instance\nconst emitter = new WideEventEmitter();\n\nconst MAX_REQUEST_ID_LENGTH = 128;\n\n/**\n * Sanitize a request ID from user headers\n */\nfunction sanitizeRequestId(id: string): string {\n const sanitized = id.replace(/[^a-zA-Z0-9_.-]/g, \"\");\n return sanitized.slice(0, MAX_REQUEST_ID_LENGTH);\n}\n\n/**\n * Generate a request ID from the request\n */\nfunction generateRequestId(req: Request): string {\n const existingId =\n req.headers[\"x-request-id\"] ||\n req.headers[\"x-correlation-id\"] ||\n req.headers[\"x-amzn-trace-id\"];\n\n if (existingId && typeof existingId === \"string\" && existingId.length > 0) {\n const sanitized = sanitizeRequestId(existingId);\n if (sanitized.length > 0) {\n return sanitized;\n }\n }\n\n return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Create a WideEvent for a request\n */\nfunction createEventForRequest(req: Request): WideEvent {\n const requestId = generateRequestId(req);\n const wideEvent = new WideEvent(requestId);\n\n // extract path from request (strip query string)\n const rawPath = req.path || req.url || req.originalUrl;\n const path = rawPath?.split(\"?\")[0];\n wideEvent.set(\"method\", req.method).set(\"path\", path);\n\n // extract user id from request headers (sanitized)\n const rawUserId = req.headers[\"x-forwarded-user\"];\n if (rawUserId && typeof rawUserId === \"string\" && rawUserId.length > 0) {\n const userId = rawUserId.replace(/[^a-zA-Z0-9_@.-]/g, \"\").slice(0, 128);\n if (userId.length > 0) {\n wideEvent.setUser({ id: userId });\n }\n }\n\n // extract trace id from active span for distributed tracing\n const currentSpan = trace.getActiveSpan();\n const spanContext = currentSpan?.spanContext();\n if (spanContext?.traceId) {\n wideEvent.set(\"trace_id\", spanContext.traceId);\n\n const debugLogger = createObug(\"appkit:logger:event\", { useColors: true });\n debugLogger(\n \"WideEvent created: %s %s (reqId: %s, traceId: %s)\",\n req.method,\n path,\n requestId.substring(0, 8),\n spanContext.traceId.substring(0, 8),\n );\n }\n\n // Update service scope\n if (wideEvent.data.service) {\n wideEvent.data.service = {\n ...wideEvent.data.service,\n name: \"appkit\",\n };\n }\n\n return wideEvent;\n}\n\n/**\n * Setup response lifecycle handlers for WideEvent finalization\n */\nfunction setupResponseHandlers(req: Request, wideEvent: WideEvent): void {\n const res = req.res as Response | undefined;\n if (!res) return;\n\n res.once(\"finish\", () => {\n // finalize the event with status code\n const finalizedData = wideEvent.finalize(res.statusCode || 200);\n\n // emit to OpenTelemetry if sampled\n if (shouldSample(finalizedData, DEFAULT_SAMPLING_CONFIG)) {\n emitter.emit(finalizedData);\n }\n\n // clean up the WeakMap\n eventsByRequest.delete(req);\n });\n\n res.once(\"close\", () => {\n if (!res.writableFinished) {\n // request was aborted - just cleanup\n eventsByRequest.delete(req);\n }\n });\n}\n\n/**\n * Express middleware that establishes AsyncLocalStorage context for WideEvent.\n * This properly scopes the context to the entire request lifecycle using run().\n *\n * @example\n * ```typescript\n * import { wideEventMiddleware } from \"@databricks/appkit\";\n *\n * app.use(wideEventMiddleware);\n * ```\n */\nexport function wideEventMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n\n // run() scopes the context to this request's entire async chain\n eventStorage.run(wideEvent, next);\n}\n\n/**\n * Get or create a WideEvent for the given request.\n * If called within wideEventMiddleware context, returns the event from AsyncLocalStorage.\n * Otherwise creates a new event for the request.\n */\nfunction getOrCreateEvent(req: Request): WideEvent {\n // first check if we already have an event\n let wideEvent = eventsByRequest.get(req);\n\n if (!wideEvent) {\n // check if we are in a middleware context\n const alsEvent = eventStorage.getStore();\n if (alsEvent) {\n // store the event in the WeakMap\n eventsByRequest.set(req, alsEvent);\n return alsEvent;\n }\n\n // no middleware context - create event directly\n wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n }\n\n return wideEvent;\n}\n\n/**\n * Get current WideEvent from AsyncLocalStorage or request\n */\nfunction getCurrentEvent(req?: Request): WideEvent | undefined {\n // if req provided, use it\n if (req) {\n return getOrCreateEvent(req);\n }\n\n // otherwise, get from AsyncLocalStorage\n return eventStorage.getStore();\n}\n\n/**\n * Check if the first argument is an Express Request\n */\nfunction isRequest(arg: unknown): arg is Request {\n return (\n typeof arg === \"object\" &&\n arg !== null &&\n \"method\" in arg &&\n \"path\" in arg &&\n typeof (arg as Request).method === \"string\"\n );\n}\n\n/**\n * Create a logger instance for a specific scope\n * @param scope - The scope identifier (e.g., \"connectors:lakebase\")\n * @returns Logger instance with debug, info, warn, and error methods\n *\n * @example\n * ```typescript\n * const logger = createLogger(\"connectors:lakebase\");\n *\n * // Regular logging (no request tracking)\n * logger.debug(\"Connection established with pool size: %d\", poolSize);\n * logger.info(\"Server started on port %d\", port);\n *\n * // Request-scoped logging (tracks in WideEvent)\n * logger.debug(req, \"Processing query: %s\", queryId);\n * logger.error(req, \"Query failed: %O\", error);\n *\n * // Get WideEvent - works in route handlers (with req) or interceptors (from context)\n * const event = logger.event(req); // In route handler\n * const event = logger.event(); // In interceptor (gets from AsyncLocalStorage)\n * event?.setComponent(\"analytics\", \"executeQuery\");\n * ```\n */\nexport function createLogger(scope: string): Logger {\n const debug = createObug(`appkit:${scope}`, { useColors: true });\n const prefix = `[appkit:${scope}]`;\n\n function debugLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n debug(message, ...logArgs);\n getOrCreateEvent(req).addLog(\"debug\", formatted);\n } else {\n debug(reqOrMessage, ...args);\n }\n }\n\n function infoLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.log(prefix, formatted);\n getOrCreateEvent(req).addLog(\"info\", formatted);\n } else {\n console.log(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function warnLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.warn(prefix, formatted);\n getOrCreateEvent(req).addLog(\"warn\", formatted);\n } else {\n console.warn(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function errorLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.error(prefix, formatted);\n getOrCreateEvent(req).addLog(\"error\", formatted);\n } else {\n console.error(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function event(req?: Request): WideEvent | undefined {\n return getCurrentEvent(req);\n }\n\n return {\n debug: debugLog as Logger[\"debug\"],\n info: infoLog as Logger[\"info\"],\n warn: warnLog as Logger[\"warn\"],\n error: errorLog as Logger[\"error\"],\n event,\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"logger.js","names":["createObug"],"sources":["../../src/logging/logger.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { format } from \"node:util\";\nimport { trace } from \"@opentelemetry/api\";\nimport type { NextFunction, Request, Response } from \"express\";\nimport { createDebug as createObug } from \"obug\";\nimport { DEFAULT_SAMPLING_CONFIG, shouldSample } from \"./sampling\";\nimport { WideEvent } from \"./wide-event\";\nimport { WideEventEmitter } from \"./wide-event-emitter\";\n\n/**\n * Logger interface for AppKit components\n */\ninterface Logger {\n /** Debug output (disabled by default, enable via DEBUG env var) */\n debug(message: string, ...args: unknown[]): void;\n debug(req: Request, message: string, ...args: unknown[]): void;\n\n /** Info output (always visible, for operational messages) */\n info(message: string, ...args: unknown[]): void;\n info(req: Request, message: string, ...args: unknown[]): void;\n\n /** Warning output (always visible, for degraded states) */\n warn(message: string, ...args: unknown[]): void;\n warn(req: Request, message: string, ...args: unknown[]): void;\n\n /** Error output (always visible, for failures) */\n error(message: string, ...args: unknown[]): void;\n error(req: Request, message: string, ...args: unknown[]): void;\n\n /** Get request-scoped WideEvent (from AsyncLocalStorage or explicit req) */\n event(req?: Request): WideEvent | undefined;\n}\n\n// AsyncLocalStorage for WideEvent context propagation\nconst eventStorage = new AsyncLocalStorage<WideEvent>();\n\n// WeakMap to store WideEvent per request (for explicit req usage)\nconst eventsByRequest = new WeakMap<Request, WideEvent>();\n\n// Global emitter instance\nconst emitter = new WideEventEmitter();\n\nconst MAX_REQUEST_ID_LENGTH = 128;\n\n/**\n * Sanitize a request ID from user headers\n */\nfunction sanitizeRequestId(id: string): string {\n const sanitized = id.replace(/[^a-zA-Z0-9_.-]/g, \"\");\n return sanitized.slice(0, MAX_REQUEST_ID_LENGTH);\n}\n\n/**\n * Generate a request ID from the request\n */\nfunction generateRequestId(req: Request): string {\n const existingId =\n req.headers[\"x-request-id\"] ||\n req.headers[\"x-correlation-id\"] ||\n req.headers[\"x-amzn-trace-id\"];\n\n if (existingId && typeof existingId === \"string\" && existingId.length > 0) {\n const sanitized = sanitizeRequestId(existingId);\n if (sanitized.length > 0) {\n return sanitized;\n }\n }\n\n return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Create a WideEvent for a request\n */\nfunction createEventForRequest(req: Request): WideEvent {\n const requestId = generateRequestId(req);\n const wideEvent = new WideEvent(requestId);\n\n // extract path from request (strip query string)\n const rawPath = req.path || req.url || req.originalUrl;\n const path = rawPath?.split(\"?\")[0];\n wideEvent.set(\"method\", req.method).set(\"path\", path);\n\n // extract user id from request headers (sanitized)\n const rawUserId = req.headers[\"x-forwarded-user\"];\n if (rawUserId && typeof rawUserId === \"string\" && rawUserId.length > 0) {\n const userId = rawUserId.replace(/[^a-zA-Z0-9_@.-]/g, \"\").slice(0, 128);\n if (userId.length > 0) {\n wideEvent.setUser({ id: userId });\n }\n }\n\n // extract trace id from active span for distributed tracing\n const currentSpan = trace.getActiveSpan();\n const spanContext = currentSpan?.spanContext();\n if (spanContext?.traceId) {\n wideEvent.set(\"trace_id\", spanContext.traceId);\n\n const debugLogger = createObug(\"appkit:logger:event\", { useColors: true });\n debugLogger(\n \"WideEvent created: %s %s (reqId: %s, traceId: %s)\",\n req.method,\n path,\n requestId.substring(0, 8),\n spanContext.traceId.substring(0, 8),\n );\n }\n\n // Update service scope\n if (wideEvent.data.service) {\n wideEvent.data.service = {\n ...wideEvent.data.service,\n name: \"appkit\",\n };\n }\n\n return wideEvent;\n}\n\n/**\n * Setup response lifecycle handlers for WideEvent finalization\n */\nfunction setupResponseHandlers(req: Request, wideEvent: WideEvent): void {\n const res = req.res as Response | undefined;\n if (!res) return;\n\n res.once(\"finish\", () => {\n // finalize the event with status code\n const finalizedData = wideEvent.finalize(res.statusCode || 200);\n\n // emit to OpenTelemetry if sampled\n if (shouldSample(finalizedData, DEFAULT_SAMPLING_CONFIG)) {\n emitter.emit(finalizedData);\n }\n\n // clean up the WeakMap\n eventsByRequest.delete(req);\n });\n\n res.once(\"close\", () => {\n if (!res.writableFinished) {\n // request was aborted - just cleanup\n eventsByRequest.delete(req);\n }\n });\n}\n\n/**\n * Express middleware that establishes AsyncLocalStorage context for WideEvent.\n * This properly scopes the context to the entire request lifecycle using run().\n *\n * @example\n * ```typescript\n * import { wideEventMiddleware } from \"@databricks/appkit\";\n *\n * app.use(wideEventMiddleware);\n * ```\n */\nfunction _wideEventMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n\n // run() scopes the context to this request's entire async chain\n eventStorage.run(wideEvent, next);\n}\n\n/**\n * Get or create a WideEvent for the given request.\n * If called within wideEventMiddleware context, returns the event from AsyncLocalStorage.\n * Otherwise creates a new event for the request.\n */\nfunction getOrCreateEvent(req: Request): WideEvent {\n // first check if we already have an event\n let wideEvent = eventsByRequest.get(req);\n\n if (!wideEvent) {\n // check if we are in a middleware context\n const alsEvent = eventStorage.getStore();\n if (alsEvent) {\n // store the event in the WeakMap\n eventsByRequest.set(req, alsEvent);\n return alsEvent;\n }\n\n // no middleware context - create event directly\n wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n }\n\n return wideEvent;\n}\n\n/**\n * Get current WideEvent from AsyncLocalStorage or request\n */\nfunction getCurrentEvent(req?: Request): WideEvent | undefined {\n // if req provided, use it\n if (req) {\n return getOrCreateEvent(req);\n }\n\n // otherwise, get from AsyncLocalStorage\n return eventStorage.getStore();\n}\n\n/**\n * Check if the first argument is an Express Request\n */\nfunction isRequest(arg: unknown): arg is Request {\n return (\n typeof arg === \"object\" &&\n arg !== null &&\n \"method\" in arg &&\n \"path\" in arg &&\n typeof (arg as Request).method === \"string\"\n );\n}\n\n/**\n * Create a logger instance for a specific scope\n * @param scope - The scope identifier (e.g., \"connectors:lakebase\")\n * @returns Logger instance with debug, info, warn, and error methods\n *\n * @example\n * ```typescript\n * const logger = createLogger(\"connectors:lakebase\");\n *\n * // Regular logging (no request tracking)\n * logger.debug(\"Connection established with pool size: %d\", poolSize);\n * logger.info(\"Server started on port %d\", port);\n *\n * // Request-scoped logging (tracks in WideEvent)\n * logger.debug(req, \"Processing query: %s\", queryId);\n * logger.error(req, \"Query failed: %O\", error);\n *\n * // Get WideEvent - works in route handlers (with req) or interceptors (from context)\n * const event = logger.event(req); // In route handler\n * const event = logger.event(); // In interceptor (gets from AsyncLocalStorage)\n * event?.setComponent(\"analytics\", \"executeQuery\");\n * ```\n */\nexport function createLogger(scope: string): Logger {\n const debug = createObug(`appkit:${scope}`, { useColors: true });\n const prefix = `[appkit:${scope}]`;\n\n function debugLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n debug(message, ...logArgs);\n getOrCreateEvent(req).addLog(\"debug\", formatted);\n } else {\n debug(reqOrMessage, ...args);\n }\n }\n\n function infoLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.log(prefix, formatted);\n getOrCreateEvent(req).addLog(\"info\", formatted);\n } else {\n console.log(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function warnLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.warn(prefix, formatted);\n getOrCreateEvent(req).addLog(\"warn\", formatted);\n } else {\n console.warn(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function errorLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.error(prefix, formatted);\n getOrCreateEvent(req).addLog(\"error\", formatted);\n } else {\n console.error(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function event(req?: Request): WideEvent | undefined {\n return getCurrentEvent(req);\n }\n\n return {\n debug: debugLog as Logger[\"debug\"],\n info: infoLog as Logger[\"info\"],\n warn: warnLog as Logger[\"warn\"],\n error: errorLog as Logger[\"error\"],\n event,\n };\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"sampling.js","names":[],"sources":["../../src/logging/sampling.ts"],"sourcesContent":["import { shouldExcludePath } from \"../utils/path-exclusions\";\nimport type { WideEventData } from \"./wide-event\";\n\n/**\n * Sampling configuration for WideEvents\n */\nexport interface SamplingConfig {\n /** Always sample if any of these conditions are true */\n alwaysSampleIf: {\n /** Sample if event has errors */\n hasErrors: boolean;\n /** Sample if status code >= this value (e.g., 400) */\n statusCodeGte: number;\n /** Sample if duration >= this value in ms (e.g., 5000) */\n durationGte: number;\n /** Sample if cache was used (hit or miss tracked) */\n hasCacheInfo: boolean;\n };\n\n /** Sample rate for normal requests (0-1, e.g., 0.1 = 10%) */\n sampleRate: number;\n}\n\n/**\n * Get sample rate from environment variable or default to 1.0 (100%)\n */\nfunction getSampleRate(): number {\n const envRate = process.env.APPKIT_SAMPLE_RATE;\n if (envRate) {\n const parsed = parseFloat(envRate);\n if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) {\n return parsed;\n }\n }\n return 1;\n}\n\n/**\n * Default sampling configuration\n */\nexport const DEFAULT_SAMPLING_CONFIG: SamplingConfig = {\n alwaysSampleIf: {\n hasErrors: true,\n statusCodeGte: 400,\n durationGte: 5000, // 5 seconds\n hasCacheInfo: true, // Always sample requests with cache info (hit or miss)\n },\n sampleRate: getSampleRate(),\n};\n\n/**\n * Simple hash function for deterministic sampling\n */\nfunction hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash);\n}\n\n/**\n * Determine if a WideEvent should be sampled based on configuration.\n * Uses shared path exclusions from utils/path-exclusions.ts.\n */\nexport function shouldSample(\n event: WideEventData,\n config: SamplingConfig = DEFAULT_SAMPLING_CONFIG,\n): boolean {\n // Check exclusions first using shared path exclusions\n if (shouldExcludePath(event.path)) {\n return false;\n }\n\n // Always sample if has errors\n if (config.alwaysSampleIf.hasErrors && event.error) {\n return true;\n }\n\n // Always sample if status code >= threshold\n if (\n config.alwaysSampleIf.statusCodeGte &&\n event.status_code &&\n event.status_code >= config.alwaysSampleIf.statusCodeGte\n ) {\n return true;\n }\n\n // Always sample if duration >= threshold\n if (\n config.alwaysSampleIf.durationGte &&\n event.duration_ms &&\n event.duration_ms >= config.alwaysSampleIf.durationGte\n ) {\n return true;\n }\n\n // Always sample if cache info is present (cache hit or miss)\n if (\n config.alwaysSampleIf.hasCacheInfo &&\n event.execution?.cache_hit !== undefined\n ) {\n return true;\n }\n\n // Sample based on sample rate\n if (config.sampleRate >= 1) {\n return true;\n }\n\n if (config.sampleRate <= 0) {\n return false;\n }\n\n // Deterministic sampling based on request ID\n const hash = hashString(event.request_id);\n return hash % 100 < config.sampleRate * 100;\n}\n"],"mappings":";;;;;;AA0BA,SAAS,gBAAwB;CAC/B,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,SAAS;EACX,MAAM,SAAS,WAAW,QAAQ;AAClC,MAAI,CAAC,OAAO,MAAM,OAAO,IAAI,UAAU,KAAK,UAAU,EACpD,QAAO;;AAGX,QAAO;;;;;AAMT,MAAa,0BAA0C;CACrD,gBAAgB;EACd,WAAW;EACX,eAAe;EACf,aAAa;EACb,cAAc;EACf;CACD,YAAY,eAAe;CAC5B;;;;AAKD,SAAS,WAAW,KAAqB;CACvC,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,UAAQ,QAAQ,KAAK,OAAO;AAC5B,SAAO,OAAO;;AAEhB,QAAO,KAAK,IAAI,KAAK;;;;;;AAOvB,SAAgB,aACd,OACA,SAAyB,yBAChB;AAET,KAAI,kBAAkB,MAAM,KAAK,CAC/B,QAAO;AAIT,KAAI,OAAO,eAAe,aAAa,MAAM,MAC3C,QAAO;AAIT,KACE,OAAO,eAAe,iBACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,cAE3C,QAAO;AAIT,KACE,OAAO,eAAe,eACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,YAE3C,QAAO;AAIT,KACE,OAAO,eAAe,gBACtB,MAAM,WAAW,cAAc,OAE/B,QAAO;AAIT,KAAI,OAAO,cAAc,EACvB,QAAO;AAGT,KAAI,OAAO,cAAc,EACvB,QAAO;AAKT,QADa,WAAW,MAAM,WAAW,GAC3B,MAAM,OAAO,aAAa"}
1
+ {"version":3,"file":"sampling.js","names":[],"sources":["../../src/logging/sampling.ts"],"sourcesContent":["import { shouldExcludePath } from \"../utils/path-exclusions\";\nimport type { WideEventData } from \"./wide-event\";\n\n/**\n * Sampling configuration for WideEvents\n */\ninterface SamplingConfig {\n /** Always sample if any of these conditions are true */\n alwaysSampleIf: {\n /** Sample if event has errors */\n hasErrors: boolean;\n /** Sample if status code >= this value (e.g., 400) */\n statusCodeGte: number;\n /** Sample if duration >= this value in ms (e.g., 5000) */\n durationGte: number;\n /** Sample if cache was used (hit or miss tracked) */\n hasCacheInfo: boolean;\n };\n\n /** Sample rate for normal requests (0-1, e.g., 0.1 = 10%) */\n sampleRate: number;\n}\n\n/**\n * Get sample rate from environment variable or default to 1.0 (100%)\n */\nfunction getSampleRate(): number {\n const envRate = process.env.APPKIT_SAMPLE_RATE;\n if (envRate) {\n const parsed = parseFloat(envRate);\n if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) {\n return parsed;\n }\n }\n return 1;\n}\n\n/**\n * Default sampling configuration\n */\nexport const DEFAULT_SAMPLING_CONFIG: SamplingConfig = {\n alwaysSampleIf: {\n hasErrors: true,\n statusCodeGte: 400,\n durationGte: 5000, // 5 seconds\n hasCacheInfo: true, // Always sample requests with cache info (hit or miss)\n },\n sampleRate: getSampleRate(),\n};\n\n/**\n * Simple hash function for deterministic sampling\n */\nfunction hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash);\n}\n\n/**\n * Determine if a WideEvent should be sampled based on configuration.\n * Uses shared path exclusions from utils/path-exclusions.ts.\n */\nexport function shouldSample(\n event: WideEventData,\n config: SamplingConfig = DEFAULT_SAMPLING_CONFIG,\n): boolean {\n // Check exclusions first using shared path exclusions\n if (shouldExcludePath(event.path)) {\n return false;\n }\n\n // Always sample if has errors\n if (config.alwaysSampleIf.hasErrors && event.error) {\n return true;\n }\n\n // Always sample if status code >= threshold\n if (\n config.alwaysSampleIf.statusCodeGte &&\n event.status_code &&\n event.status_code >= config.alwaysSampleIf.statusCodeGte\n ) {\n return true;\n }\n\n // Always sample if duration >= threshold\n if (\n config.alwaysSampleIf.durationGte &&\n event.duration_ms &&\n event.duration_ms >= config.alwaysSampleIf.durationGte\n ) {\n return true;\n }\n\n // Always sample if cache info is present (cache hit or miss)\n if (\n config.alwaysSampleIf.hasCacheInfo &&\n event.execution?.cache_hit !== undefined\n ) {\n return true;\n }\n\n // Sample based on sample rate\n if (config.sampleRate >= 1) {\n return true;\n }\n\n if (config.sampleRate <= 0) {\n return false;\n }\n\n // Deterministic sampling based on request ID\n const hash = hashString(event.request_id);\n return hash % 100 < config.sampleRate * 100;\n}\n"],"mappings":";;;;;;AA0BA,SAAS,gBAAwB;CAC/B,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,SAAS;EACX,MAAM,SAAS,WAAW,QAAQ;AAClC,MAAI,CAAC,OAAO,MAAM,OAAO,IAAI,UAAU,KAAK,UAAU,EACpD,QAAO;;AAGX,QAAO;;;;;AAMT,MAAa,0BAA0C;CACrD,gBAAgB;EACd,WAAW;EACX,eAAe;EACf,aAAa;EACb,cAAc;EACf;CACD,YAAY,eAAe;CAC5B;;;;AAKD,SAAS,WAAW,KAAqB;CACvC,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,UAAQ,QAAQ,KAAK,OAAO;AAC5B,SAAO,OAAO;;AAEhB,QAAO,KAAK,IAAI,KAAK;;;;;;AAOvB,SAAgB,aACd,OACA,SAAyB,yBAChB;AAET,KAAI,kBAAkB,MAAM,KAAK,CAC/B,QAAO;AAIT,KAAI,OAAO,eAAe,aAAa,MAAM,MAC3C,QAAO;AAIT,KACE,OAAO,eAAe,iBACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,cAE3C,QAAO;AAIT,KACE,OAAO,eAAe,eACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,YAE3C,QAAO;AAIT,KACE,OAAO,eAAe,gBACtB,MAAM,WAAW,cAAc,OAE/B,QAAO;AAIT,KAAI,OAAO,cAAc,EACvB,QAAO;AAGT,KAAI,OAAO,cAAc,EACvB,QAAO;AAKT,QADa,WAAW,MAAM,WAAW,GAC3B,MAAM,OAAO,aAAa"}
@@ -1,5 +1,3 @@
1
- import { EXTENSION_CONTENT_TYPES, FILES_MAX_READ_SIZE } from "../../connectors/files/defaults.js";
2
-
3
1
  //#region src/plugins/files/defaults.ts
4
2
  /**
5
3
  * Execution defaults for read-tier operations (list, read, exists, metadata, preview).
@@ -1 +1 @@
1
- {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/files/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/**\n * Execution defaults for read-tier operations (list, read, exists, metadata, preview).\n * Cache 60s (ttl in seconds)\n * Retry 3x with 1s backoff\n * Timeout 30s\n **/\nexport const FILES_READ_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: true,\n ttl: 60,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for download-tier operations (download, raw).\n * No cache\n * Retry 3x with 1s backoff\n * Timeout 30s (stream start only)\n **/\nexport const FILES_DOWNLOAD_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n /**\n * @info this timeout is for the stream to start, not for the full download.\n */\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for write-tier operations (upload, mkdir, delete).\n * No cache\n * No retry\n * Timeout 600s.\n **/\nexport const FILES_WRITE_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: false,\n },\n timeout: 600_000,\n};\n\n/**\n * Default maximum upload size in bytes (5 GB).\n * This matches the Databricks Files API v2 per-file limit.\n */\nexport const FILES_MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024; // 5 GB\n\nexport {\n EXTENSION_CONTENT_TYPES,\n FILES_MAX_READ_SIZE,\n} from \"../../connectors/files/defaults\";\n"],"mappings":";;;;;;;;;AAQA,MAAa,sBAA2C;CACtD,OAAO;EACL,SAAS;EACT,KAAK;EACN;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CACD,SAAS;CACV;;;;;;;AAQD,MAAa,0BAA+C;CAC1D,OAAO,EACL,SAAS,OACV;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CAID,SAAS;CACV;;;;;;;AAQD,MAAa,uBAA4C;CACvD,OAAO,EACL,SAAS,OACV;CACD,OAAO,EACL,SAAS,OACV;CACD,SAAS;CACV;;;;;AAMD,MAAa,wBAAwB,IAAI,OAAO,OAAO"}
1
+ {"version":3,"file":"defaults.js","names":[],"sources":["../../../src/plugins/files/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\n/**\n * Execution defaults for read-tier operations (list, read, exists, metadata, preview).\n * Cache 60s (ttl in seconds)\n * Retry 3x with 1s backoff\n * Timeout 30s\n **/\nexport const FILES_READ_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: true,\n ttl: 60,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for download-tier operations (download, raw).\n * No cache\n * Retry 3x with 1s backoff\n * Timeout 30s (stream start only)\n **/\nexport const FILES_DOWNLOAD_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: true,\n initialDelay: 1000,\n attempts: 3,\n },\n /**\n * @info this timeout is for the stream to start, not for the full download.\n */\n timeout: 30_000,\n};\n\n/**\n * Execution defaults for write-tier operations (upload, mkdir, delete).\n * No cache\n * No retry\n * Timeout 600s.\n **/\nexport const FILES_WRITE_DEFAULTS: PluginExecuteConfig = {\n cache: {\n enabled: false,\n },\n retry: {\n enabled: false,\n },\n timeout: 600_000,\n};\n\n/**\n * Default maximum upload size in bytes (5 GB).\n * This matches the Databricks Files API v2 per-file limit.\n */\nexport const FILES_MAX_UPLOAD_SIZE = 5 * 1024 * 1024 * 1024; // 5 GB\n"],"mappings":";;;;;;;AAQA,MAAa,sBAA2C;CACtD,OAAO;EACL,SAAS;EACT,KAAK;EACN;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CACD,SAAS;CACV;;;;;;;AAQD,MAAa,0BAA+C;CAC1D,OAAO,EACL,SAAS,OACV;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CAID,SAAS;CACV;;;;;;;AAQD,MAAa,uBAA4C;CACvD,OAAO,EACL,SAAS,OACV;CACD,OAAO,EACL,SAAS,OACV;CACD,SAAS;CACV;;;;;AAMD,MAAa,wBAAwB,IAAI,OAAO,OAAO"}
@@ -1,4 +1,3 @@
1
- import { EXTENSION_CONTENT_TYPES, FILES_MAX_READ_SIZE } from "../../connectors/files/defaults.js";
2
1
  import { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS } from "./defaults.js";
3
2
  import { FilesPlugin, files } from "./plugin.js";
4
3
 
@@ -1,4 +1,4 @@
1
- import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../../shared/src/genie.js";
1
+ import { GenieStreamEvent } from "../../shared/src/genie.js";
2
2
  import { GenieConversationHistoryResponse } from "../../connectors/genie/types.js";
3
3
  import { IGenieConfig } from "./types.js";
4
4
  import { GeniePlugin, genie } from "./genie.js";
@@ -1,5 +1,5 @@
1
1
  import { BasePluginConfig } from "../../shared/src/plugin.js";
2
- import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../../shared/src/genie.js";
2
+ import { GenieStreamEvent } from "../../shared/src/genie.js";
3
3
  import "../../shared/src/index.js";
4
4
  import { GenieConversationHistoryResponse } from "../../connectors/genie/types.js";
5
5
  import "../../connectors/genie/index.js";
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/genie/types.ts"],"mappings":";;;;;;;UAUiB,YAAA,SAAqB,gBAAA;;EAEpC,MAAA,GAAS,MAAA;;EAET,OAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../../src/plugins/genie/types.ts"],"mappings":";;;;;;;UAMiB,YAAA,SAAqB,gBAAA;;EAEpC,MAAA,GAAS,MAAA;;EAET,OAAA;AAAA"}
@@ -1,4 +1,4 @@
1
- import { GenieAttachmentResponse, GenieMessageResponse, GenieStreamEvent } from "../shared/src/genie.js";
1
+ import { GenieStreamEvent } from "../shared/src/genie.js";
2
2
  import { IAnalyticsConfig } from "./analytics/types.js";
3
3
  import { AnalyticsPlugin, analytics } from "./analytics/analytics.js";
4
4
  import "./analytics/index.js";
@@ -10,7 +10,6 @@ import { IGenieConfig } from "./genie/types.js";
10
10
  import { GeniePlugin, genie } from "./genie/genie.js";
11
11
  import "./genie/index.js";
12
12
  import { ILakebaseConfig } from "./lakebase/types.js";
13
- import { LakebasePlugin, lakebase } from "./lakebase/lakebase.js";
13
+ import { lakebase } from "./lakebase/lakebase.js";
14
14
  import "./lakebase/index.js";
15
- import { ServerConfig } from "./server/types.js";
16
15
  import { ServerPlugin, server } from "./server/index.js";
@@ -1,4 +1,3 @@
1
- import { EXTENSION_CONTENT_TYPES, FILES_MAX_READ_SIZE } from "../connectors/files/defaults.js";
2
1
  import { AnalyticsPlugin, analytics } from "./analytics/analytics.js";
3
2
  import "./analytics/index.js";
4
3
  import { FILES_DOWNLOAD_DEFAULTS, FILES_MAX_UPLOAD_SIZE, FILES_READ_DEFAULTS, FILES_WRITE_DEFAULTS } from "./files/defaults.js";
@@ -6,7 +5,7 @@ import { FilesPlugin, files } from "./files/plugin.js";
6
5
  import "./files/index.js";
7
6
  import { GeniePlugin, genie } from "./genie/genie.js";
8
7
  import "./genie/index.js";
9
- import { LakebasePlugin, lakebase } from "./lakebase/lakebase.js";
8
+ import { lakebase } from "./lakebase/lakebase.js";
10
9
  import "./lakebase/index.js";
11
10
  import { ServerPlugin, server } from "./server/index.js";
12
11
 
@@ -1,2 +1,2 @@
1
1
  import { ILakebaseConfig } from "./types.js";
2
- import { LakebasePlugin, lakebase } from "./lakebase.js";
2
+ import { lakebase } from "./lakebase.js";
@@ -1,3 +1,3 @@
1
- import { LakebasePlugin, lakebase } from "./lakebase.js";
1
+ import { lakebase } from "./lakebase.js";
2
2
 
3
3
  export { };
@@ -114,5 +114,5 @@ declare class LakebasePlugin extends Plugin {
114
114
  */
115
115
  declare const lakebase: ToPlugin<typeof LakebasePlugin, ILakebaseConfig, "lakebase">;
116
116
  //#endregion
117
- export { LakebasePlugin, lakebase };
117
+ export { lakebase };
118
118
  //# sourceMappingURL=lakebase.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AAgCA;;;;;;;;;cAAa,cAAA,SAAuB,MAAA;EA4C/B;EAAA,OA1CI,QAAA,EAAuB,cAAA;EAAA,UAEZ,MAAA,EAAQ,eAAA;EAAA,QAClB,IAAA;cAEI,MAAA,EAAQ,eAAA;EAqCjB;;;;;;;EAzBG,KAAA,CAAA,GAAK,OAAA;EAnB6B;;;;;;;;;;;;;;;EAyClC,KAAA,WAAgB,cAAA,OAAA,CACpB,IAAA,UACA,MAAA,eACC,OAAA,CAAQ,WAAA,CAAY,CAAA;EAFrB;;;;EAWF,qBAAA,CAAA;EAAA;;;;;;;;EAmBA,OAAA,CAAA;;sBA/BsB,cAAA,QAAc,IAAA,UACtB,MAAA,iBAEX,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;iBAtBsB,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgElC,QAAA,EAAQ,QAAA,QAAA,cAAA,EAAA,eAAA"}
1
+ {"version":3,"file":"lakebase.d.ts","names":[],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"mappings":";;;;;;;;;;;;;;;;;;;;AAW+C;;;;;;;;;cAqBzC,cAAA,SAAuB,MAAA;EA4CxB;EAAA,OA1CI,QAAA,EAAuB,cAAA;EAAA,UAEZ,MAAA,EAAQ,eAAA;EAAA,QAClB,IAAA;cAEI,MAAA,EAAQ,eAAA;EAqCjB;;;;;;;EAzBG,KAAA,CAAA,GAAK,OAAA;EAnBsB;;;;;;;;;;;;;;;EAyC3B,KAAA,WAAgB,cAAA,OAAA,CACpB,IAAA,UACA,MAAA,eACC,OAAA,CAAQ,WAAA,CAAY,CAAA;EAFrB;;;;EAWF,qBAAA,CAAA;EAAA;;;;;;;;EAmBA,OAAA,CAAA;;sBA/BsB,cAAA,QAAc,IAAA,UACtB,MAAA,iBAEX,OAAA,CAAQ,WAAA,CAAY,CAAA;;;;;;;;;;iBAtB6B,QAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAgEzC,QAAA,EAAQ,QAAA,QAAA,cAAA,EAAA,eAAA"}
@@ -103,5 +103,5 @@ var LakebasePlugin = class extends Plugin {
103
103
  const lakebase = toPlugin(LakebasePlugin);
104
104
 
105
105
  //#endregion
106
- export { LakebasePlugin, lakebase };
106
+ export { lakebase };
107
107
  //# sourceMappingURL=lakebase.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"lakebase.js","names":["manifest"],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"sourcesContent":["import type { Pool, QueryResult, QueryResultRow } from \"pg\";\nimport {\n createLakebasePool,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n} from \"../../connectors/lakebase\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport manifest from \"./manifest.json\";\nimport type { ILakebaseConfig } from \"./types\";\n\nconst logger = createLogger(\"lakebase\");\n\n/**\n * AppKit plugin for Databricks Lakebase Autoscaling.\n *\n * Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic\n * OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.\n *\n * @example\n * ```ts\n * import { createApp, lakebase, server } from \"@databricks/appkit\";\n *\n * const AppKit = await createApp({\n * plugins: [server(), lakebase()],\n * });\n *\n * const result = await AppKit.lakebase.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n * ```\n */\nexport class LakebasePlugin extends Plugin {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"lakebase\">;\n\n protected declare config: ILakebaseConfig;\n private pool: Pool | null = null;\n\n constructor(config: ILakebaseConfig) {\n super(config);\n this.config = config;\n }\n\n /**\n * Initializes the Lakebase connection pool.\n * Called automatically by AppKit during the plugin setup phase.\n *\n * Resolves the PostgreSQL username via {@link getUsernameWithApiLookup},\n * which tries config, env vars, and finally the Databricks workspace API.\n */\n async setup() {\n const poolConfig = this.config.pool;\n const user = await getUsernameWithApiLookup(poolConfig);\n this.pool = createLakebasePool({ ...poolConfig, user });\n logger.info(\"Lakebase pool initialized\");\n }\n\n /**\n * Executes a parameterized SQL query against the Lakebase pool.\n *\n * @param text - SQL query string, using `$1`, `$2`, ... placeholders\n * @param values - Parameter values corresponding to placeholders\n * @returns Query result with typed rows\n *\n * @example\n * ```ts\n * const result = await AppKit.lakebase.query<{ id: number; name: string }>(\n * \"SELECT id, name FROM users WHERE active = $1\",\n * [true],\n * );\n * ```\n */\n async query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n // biome-ignore lint/style/noNonNullAssertion: pool is guaranteed non-null after setup(), which AppKit always awaits before exposing the plugin API\n return this.pool!.query<T>(text, values);\n }\n\n /**\n * Gracefully drains and closes the connection pool.\n * Called automatically by AppKit during shutdown.\n */\n abortActiveOperations(): void {\n super.abortActiveOperations();\n if (this.pool) {\n logger.info(\"Closing Lakebase pool\");\n this.pool.end().catch((err) => {\n logger.error(\"Error closing Lakebase pool: %O\", err);\n });\n this.pool = null;\n }\n }\n\n /**\n * Returns the plugin's public API, accessible via `AppKit.lakebase`.\n *\n * - `pool` — The raw `pg.Pool` instance, for use with ORMs or advanced scenarios\n * - `query` — Convenience method for executing parameterized SQL queries\n * - `getOrmConfig()` — Returns a config object compatible with Drizzle, TypeORM, Sequelize, etc.\n * - `getPgConfig()` — Returns a `pg.PoolConfig` object for manual pool construction\n */\n exports() {\n return {\n // biome-ignore lint/style/noNonNullAssertion: pool is guaranteed non-null after setup(), which AppKit always awaits before exposing the plugin API\n pool: this.pool!,\n query: this.query.bind(this),\n getOrmConfig: () => getLakebaseOrmConfig(this.config.pool),\n getPgConfig: () => getLakebasePgConfig(this.config.pool),\n };\n }\n}\n\n/**\n * @internal\n */\nexport const lakebase = toPlugin(LakebasePlugin);\n"],"mappings":";;;;;;;;AAaA,MAAM,SAAS,aAAa,WAAW;;;;;;;;;;;;;;;;;;AAmBvC,IAAa,iBAAb,cAAoC,OAAO;;CAEzC,OAAO,WAAWA;CAGlB,AAAQ,OAAoB;CAE5B,YAAY,QAAyB;AACnC,QAAM,OAAO;AACb,OAAK,SAAS;;;;;;;;;CAUhB,MAAM,QAAQ;EACZ,MAAM,aAAa,KAAK,OAAO;EAC/B,MAAM,OAAO,MAAM,yBAAyB,WAAW;AACvD,OAAK,OAAO,mBAAmB;GAAE,GAAG;GAAY;GAAM,CAAC;AACvD,SAAO,KAAK,4BAA4B;;;;;;;;;;;;;;;;;CAkB1C,MAAM,MACJ,MACA,QACyB;AAEzB,SAAO,KAAK,KAAM,MAAS,MAAM,OAAO;;;;;;CAO1C,wBAA8B;AAC5B,QAAM,uBAAuB;AAC7B,MAAI,KAAK,MAAM;AACb,UAAO,KAAK,wBAAwB;AACpC,QAAK,KAAK,KAAK,CAAC,OAAO,QAAQ;AAC7B,WAAO,MAAM,mCAAmC,IAAI;KACpD;AACF,QAAK,OAAO;;;;;;;;;;;CAYhB,UAAU;AACR,SAAO;GAEL,MAAM,KAAK;GACX,OAAO,KAAK,MAAM,KAAK,KAAK;GAC5B,oBAAoB,qBAAqB,KAAK,OAAO,KAAK;GAC1D,mBAAmB,oBAAoB,KAAK,OAAO,KAAK;GACzD;;;;;;AAOL,MAAa,WAAW,SAAS,eAAe"}
1
+ {"version":3,"file":"lakebase.js","names":["manifest"],"sources":["../../../src/plugins/lakebase/lakebase.ts"],"sourcesContent":["import type { Pool, QueryResult, QueryResultRow } from \"pg\";\nimport {\n createLakebasePool,\n getLakebaseOrmConfig,\n getLakebasePgConfig,\n getUsernameWithApiLookup,\n} from \"../../connectors/lakebase\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport manifest from \"./manifest.json\";\nimport type { ILakebaseConfig } from \"./types\";\n\nconst logger = createLogger(\"lakebase\");\n\n/**\n * AppKit plugin for Databricks Lakebase Autoscaling.\n *\n * Wraps `@databricks/lakebase` to provide a standard `pg.Pool` with automatic\n * OAuth token refresh, integrated with AppKit's logger and OpenTelemetry setup.\n *\n * @example\n * ```ts\n * import { createApp, lakebase, server } from \"@databricks/appkit\";\n *\n * const AppKit = await createApp({\n * plugins: [server(), lakebase()],\n * });\n *\n * const result = await AppKit.lakebase.query(\"SELECT * FROM users WHERE id = $1\", [userId]);\n * ```\n */\nclass LakebasePlugin extends Plugin {\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"lakebase\">;\n\n protected declare config: ILakebaseConfig;\n private pool: Pool | null = null;\n\n constructor(config: ILakebaseConfig) {\n super(config);\n this.config = config;\n }\n\n /**\n * Initializes the Lakebase connection pool.\n * Called automatically by AppKit during the plugin setup phase.\n *\n * Resolves the PostgreSQL username via {@link getUsernameWithApiLookup},\n * which tries config, env vars, and finally the Databricks workspace API.\n */\n async setup() {\n const poolConfig = this.config.pool;\n const user = await getUsernameWithApiLookup(poolConfig);\n this.pool = createLakebasePool({ ...poolConfig, user });\n logger.info(\"Lakebase pool initialized\");\n }\n\n /**\n * Executes a parameterized SQL query against the Lakebase pool.\n *\n * @param text - SQL query string, using `$1`, `$2`, ... placeholders\n * @param values - Parameter values corresponding to placeholders\n * @returns Query result with typed rows\n *\n * @example\n * ```ts\n * const result = await AppKit.lakebase.query<{ id: number; name: string }>(\n * \"SELECT id, name FROM users WHERE active = $1\",\n * [true],\n * );\n * ```\n */\n async query<T extends QueryResultRow = any>(\n text: string,\n values?: unknown[],\n ): Promise<QueryResult<T>> {\n // biome-ignore lint/style/noNonNullAssertion: pool is guaranteed non-null after setup(), which AppKit always awaits before exposing the plugin API\n return this.pool!.query<T>(text, values);\n }\n\n /**\n * Gracefully drains and closes the connection pool.\n * Called automatically by AppKit during shutdown.\n */\n abortActiveOperations(): void {\n super.abortActiveOperations();\n if (this.pool) {\n logger.info(\"Closing Lakebase pool\");\n this.pool.end().catch((err) => {\n logger.error(\"Error closing Lakebase pool: %O\", err);\n });\n this.pool = null;\n }\n }\n\n /**\n * Returns the plugin's public API, accessible via `AppKit.lakebase`.\n *\n * - `pool` — The raw `pg.Pool` instance, for use with ORMs or advanced scenarios\n * - `query` — Convenience method for executing parameterized SQL queries\n * - `getOrmConfig()` — Returns a config object compatible with Drizzle, TypeORM, Sequelize, etc.\n * - `getPgConfig()` — Returns a `pg.PoolConfig` object for manual pool construction\n */\n exports() {\n return {\n // biome-ignore lint/style/noNonNullAssertion: pool is guaranteed non-null after setup(), which AppKit always awaits before exposing the plugin API\n pool: this.pool!,\n query: this.query.bind(this),\n getOrmConfig: () => getLakebaseOrmConfig(this.config.pool),\n getPgConfig: () => getLakebasePgConfig(this.config.pool),\n };\n }\n}\n\n/**\n * @internal\n */\nexport const lakebase = toPlugin(LakebasePlugin);\n"],"mappings":";;;;;;;;AAaA,MAAM,SAAS,aAAa,WAAW;;;;;;;;;;;;;;;;;;AAmBvC,IAAM,iBAAN,cAA6B,OAAO;;CAElC,OAAO,WAAWA;CAGlB,AAAQ,OAAoB;CAE5B,YAAY,QAAyB;AACnC,QAAM,OAAO;AACb,OAAK,SAAS;;;;;;;;;CAUhB,MAAM,QAAQ;EACZ,MAAM,aAAa,KAAK,OAAO;EAC/B,MAAM,OAAO,MAAM,yBAAyB,WAAW;AACvD,OAAK,OAAO,mBAAmB;GAAE,GAAG;GAAY;GAAM,CAAC;AACvD,SAAO,KAAK,4BAA4B;;;;;;;;;;;;;;;;;CAkB1C,MAAM,MACJ,MACA,QACyB;AAEzB,SAAO,KAAK,KAAM,MAAS,MAAM,OAAO;;;;;;CAO1C,wBAA8B;AAC5B,QAAM,uBAAuB;AAC7B,MAAI,KAAK,MAAM;AACb,UAAO,KAAK,wBAAwB;AACpC,QAAK,KAAK,KAAK,CAAC,OAAO,QAAQ;AAC7B,WAAO,MAAM,mCAAmC,IAAI;KACpD;AACF,QAAK,OAAO;;;;;;;;;;;CAYhB,UAAU;AACR,SAAO;GAEL,MAAM,KAAK;GACX,OAAO,KAAK,MAAM,KAAK,KAAK;GAC5B,oBAAoB,qBAAqB,KAAK,OAAO,KAAK;GAC1D,mBAAmB,oBAAoB,KAAK,OAAO,KAAK;GACzD;;;;;;AAOL,MAAa,WAAW,SAAS,eAAe"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["manifest"],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginPhase } from \"shared\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { instrumentations } from \"../../telemetry\";\nimport manifest from \"./manifest.json\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { getRoutes, type PluginEndpoints, printRoutes } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\nconst logger = createLogger(\"server\");\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), telemetryExamples(), analytics({})],\n * });\n * ```\n *\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n autoStart: true,\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"server\">;\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n private rawBodyPaths: Set<string> = new Set();\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n /** Setup the server plugin. */\n async setup() {\n if (this.shouldAutoStart()) {\n await this.start();\n }\n }\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /** Check if the server should auto start. */\n shouldAutoStart() {\n return this.config.autoStart;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(\n express.json({\n type: (req) => {\n // Skip JSON parsing for routes that declared skipBodyParsing\n // (e.g. file uploads where the raw body must flow through).\n // rawBodyPaths is populated by extendRoutes() below; the type\n // callback runs per-request so the set is already filled.\n const urlPath = req.url?.split(\"?\")[0];\n if (urlPath && this.rawBodyPaths.has(urlPath)) return false;\n const ct = req.headers[\"content-type\"] ?? \"\";\n return ct.includes(\"json\");\n },\n }),\n );\n\n const endpoints = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n printRoutes(allRoutes);\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server is not started or autoStart is true.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"get server\");\n }\n\n if (!this.server) {\n throw ServerError.notStarted();\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n * @throws {Error} If autoStart is true.\n */\n extend(fn: (app: express.Application) => void) {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"extend server\");\n }\n\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints.\n */\n private async extendRoutes(): Promise<PluginEndpoints> {\n const endpoints: PluginEndpoints = {};\n\n if (!this.config.plugins) return endpoints;\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n // Collect named endpoints from the plugin\n endpoints[plugin.name] = plugin.getEndpoints();\n\n // Collect paths that should skip body parsing\n if (\n plugin.getSkipBodyParsingPaths &&\n typeof plugin.getSkipBodyParsingPaths === \"function\"\n ) {\n for (const p of plugin.getSkipBodyParsingPaths()) {\n this.rawBodyPaths.add(p);\n }\n }\n }\n }\n\n return endpoints;\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(endpoints: PluginEndpoints) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(this.serverApplication, endpoints);\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n logger.debug(\"Static files: serving from %s\", fullPath);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n logger.info(\"Server running on http://%s:%d\", host, port);\n\n if (hasExplicitStaticPath) {\n logger.info(\"Mode: static (%s)\", this.config.staticPath);\n } else if (isDev) {\n logger.info(\"Mode: development (Vite HMR)\");\n } else {\n logger.info(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n logger.debug(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n logger.debug(\n \"Remote tunnel: %s; %s\",\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\",\n remoteServerController.isActive() ? \"active\" : \"inactive\",\n );\n }\n }\n\n private async _gracefulShutdown() {\n logger.info(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n logger.error(\n \"Error aborting operations for plugin %s: %O\",\n plugin.name,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n logger.debug(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n logger.debug(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n\n /**\n * Returns the public exports for the server plugin.\n * Exposes server management methods.\n */\n exports() {\n const self = this;\n return {\n /** Start the server */\n start: this.start,\n /** Extend the server with custom routes or middleware */\n extend(fn: (app: express.Application) => void) {\n self.extend(fn);\n return this;\n },\n /** Get the underlying HTTP server instance */\n getServer: this.getServer,\n /** Get the server configuration */\n getConfig: this.getConfig,\n };\n }\n}\n\nconst EXCLUDED_PLUGINS: string[] = [ServerPlugin.manifest.name];\n\n/**\n * @internal\n */\nexport const server = toPlugin(ServerPlugin);\n\n// Export manifest and types\nexport type { ServerConfig } from \"./types\";\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["manifest"],"sources":["../../../src/plugins/server/index.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport type { Server as HTTPServer } from \"node:http\";\nimport path from \"node:path\";\nimport dotenv from \"dotenv\";\nimport express from \"express\";\nimport type { PluginPhase } from \"shared\";\nimport { ServerError } from \"../../errors\";\nimport { createLogger } from \"../../logging/logger\";\nimport { Plugin, toPlugin } from \"../../plugin\";\nimport type { PluginManifest } from \"../../registry\";\nimport { instrumentations } from \"../../telemetry\";\nimport manifest from \"./manifest.json\";\nimport { RemoteTunnelController } from \"./remote-tunnel/remote-tunnel-controller\";\nimport { StaticServer } from \"./static-server\";\nimport type { ServerConfig } from \"./types\";\nimport { getRoutes, type PluginEndpoints, printRoutes } from \"./utils\";\nimport { ViteDevServer } from \"./vite-dev-server\";\n\ndotenv.config({ path: path.resolve(process.cwd(), \"./.env\") });\n\nconst logger = createLogger(\"server\");\n\n/**\n * Server plugin for the AppKit.\n *\n * This plugin is responsible for starting the server and serving the static files.\n * It also handles the remote tunneling for development purposes.\n *\n * @example\n * ```ts\n * createApp({\n * plugins: [server(), telemetryExamples(), analytics({})],\n * });\n * ```\n *\n */\nexport class ServerPlugin extends Plugin {\n public static DEFAULT_CONFIG = {\n autoStart: true,\n host: process.env.FLASK_RUN_HOST || \"0.0.0.0\",\n port: Number(process.env.DATABRICKS_APP_PORT) || 8000,\n };\n\n /** Plugin manifest declaring metadata and resource requirements */\n static manifest = manifest as PluginManifest<\"server\">;\n private serverApplication: express.Application;\n private server: HTTPServer | null;\n private viteDevServer?: ViteDevServer;\n private remoteTunnelController?: RemoteTunnelController;\n protected declare config: ServerConfig;\n private serverExtensions: ((app: express.Application) => void)[] = [];\n private rawBodyPaths: Set<string> = new Set();\n static phase: PluginPhase = \"deferred\";\n\n constructor(config: ServerConfig) {\n super(config);\n this.config = config;\n this.serverApplication = express();\n this.server = null;\n this.serverExtensions = [];\n this.telemetry.registerInstrumentations([\n instrumentations.http,\n instrumentations.express,\n ]);\n }\n\n /** Setup the server plugin. */\n async setup() {\n if (this.shouldAutoStart()) {\n await this.start();\n }\n }\n\n /** Get the server configuration. */\n getConfig() {\n const { plugins: _plugins, ...config } = this.config;\n\n return config;\n }\n\n /** Check if the server should auto start. */\n shouldAutoStart() {\n return this.config.autoStart;\n }\n\n /**\n * Start the server.\n *\n * This method starts the server and sets up the frontend.\n * It also sets up the remote tunneling if enabled.\n *\n * @returns The express application.\n */\n async start(): Promise<express.Application> {\n this.serverApplication.use(\n express.json({\n type: (req) => {\n // Skip JSON parsing for routes that declared skipBodyParsing\n // (e.g. file uploads where the raw body must flow through).\n // rawBodyPaths is populated by extendRoutes() below; the type\n // callback runs per-request so the set is already filled.\n const urlPath = req.url?.split(\"?\")[0];\n if (urlPath && this.rawBodyPaths.has(urlPath)) return false;\n const ct = req.headers[\"content-type\"] ?? \"\";\n return ct.includes(\"json\");\n },\n }),\n );\n\n const endpoints = await this.extendRoutes();\n\n for (const extension of this.serverExtensions) {\n extension(this.serverApplication);\n }\n\n // register remote tunnel controller (before static/vite)\n this.remoteTunnelController = new RemoteTunnelController(\n this.devFileReader,\n );\n this.serverApplication.use(this.remoteTunnelController.middleware);\n\n await this.setupFrontend(endpoints);\n\n const server = this.serverApplication.listen(\n this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port,\n this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host,\n () => this.logStartupInfo(),\n );\n\n this.server = server;\n\n // attach server to remote tunnel controller\n this.remoteTunnelController.setServer(server);\n\n process.on(\"SIGTERM\", () => this._gracefulShutdown());\n process.on(\"SIGINT\", () => this._gracefulShutdown());\n\n if (process.env.NODE_ENV === \"development\") {\n const allRoutes = getRoutes(this.serverApplication._router.stack);\n printRoutes(allRoutes);\n }\n return this.serverApplication;\n }\n\n /**\n * Get the low level node.js http server instance.\n *\n * Only use this method if you need to access the server instance for advanced usage like a custom websocket server, etc.\n *\n * @throws {Error} If the server is not started or autoStart is true.\n * @returns {HTTPServer} The server instance.\n */\n getServer(): HTTPServer {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"get server\");\n }\n\n if (!this.server) {\n throw ServerError.notStarted();\n }\n\n return this.server;\n }\n\n /**\n * Extend the server with custom routes or middleware.\n *\n * @param fn - A function that receives the express application.\n * @returns The server plugin instance for chaining.\n * @throws {Error} If autoStart is true.\n */\n extend(fn: (app: express.Application) => void) {\n if (this.shouldAutoStart()) {\n throw ServerError.autoStartConflict(\"extend server\");\n }\n\n this.serverExtensions.push(fn);\n return this;\n }\n\n /**\n * Setup the routes with the plugins.\n *\n * This method goes through all the plugins and injects the routes into the server application.\n * Returns a map of plugin names to their registered named endpoints.\n */\n private async extendRoutes(): Promise<PluginEndpoints> {\n const endpoints: PluginEndpoints = {};\n\n if (!this.config.plugins) return endpoints;\n\n this.serverApplication.get(\"/health\", (_, res) => {\n res.status(200).json({ status: \"ok\" });\n });\n this.registerEndpoint(\"health\", \"/health\");\n\n for (const plugin of Object.values(this.config.plugins)) {\n if (EXCLUDED_PLUGINS.includes(plugin.name)) continue;\n\n if (plugin?.injectRoutes && typeof plugin.injectRoutes === \"function\") {\n const router = express.Router();\n\n plugin.injectRoutes(router);\n\n const basePath = `/api/${plugin.name}`;\n this.serverApplication.use(basePath, router);\n\n // Collect named endpoints from the plugin\n endpoints[plugin.name] = plugin.getEndpoints();\n\n // Collect paths that should skip body parsing\n if (\n plugin.getSkipBodyParsingPaths &&\n typeof plugin.getSkipBodyParsingPaths === \"function\"\n ) {\n for (const p of plugin.getSkipBodyParsingPaths()) {\n this.rawBodyPaths.add(p);\n }\n }\n }\n }\n\n return endpoints;\n }\n\n /**\n * Setup frontend serving based on environment:\n * - If staticPath is explicitly provided: use static server\n * - Dev mode (no staticPath): Vite for HMR\n * - Production (no staticPath): Static files auto-detected\n */\n private async setupFrontend(endpoints: PluginEndpoints) {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n\n // explict static path provided\n if (hasExplicitStaticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n this.config.staticPath as string,\n endpoints,\n );\n staticServer.setup();\n return;\n }\n\n // auto-detection based on environment\n if (isDev) {\n this.viteDevServer = new ViteDevServer(this.serverApplication, endpoints);\n await this.viteDevServer.setup();\n return;\n }\n\n // auto-detection based on static path\n const staticPath = ServerPlugin.findStaticPath();\n if (staticPath) {\n const staticServer = new StaticServer(\n this.serverApplication,\n staticPath,\n endpoints,\n );\n\n staticServer.setup();\n }\n }\n\n private static findStaticPath() {\n const staticPaths = [\"dist\", \"client/dist\", \"build\", \"public\", \"out\"];\n const cwd = process.cwd();\n for (const p of staticPaths) {\n const fullPath = path.resolve(cwd, p);\n if (fs.existsSync(path.resolve(fullPath, \"index.html\"))) {\n logger.debug(\"Static files: serving from %s\", fullPath);\n return fullPath;\n }\n }\n return undefined;\n }\n\n private logStartupInfo() {\n const isDev = process.env.NODE_ENV === \"development\";\n const hasExplicitStaticPath = this.config.staticPath !== undefined;\n const port = this.config.port ?? ServerPlugin.DEFAULT_CONFIG.port;\n const host = this.config.host ?? ServerPlugin.DEFAULT_CONFIG.host;\n\n logger.info(\"Server running on http://%s:%d\", host, port);\n\n if (hasExplicitStaticPath) {\n logger.info(\"Mode: static (%s)\", this.config.staticPath);\n } else if (isDev) {\n logger.info(\"Mode: development (Vite HMR)\");\n } else {\n logger.info(\"Mode: production (static)\");\n }\n\n const remoteServerController = this.remoteTunnelController;\n if (!remoteServerController) {\n logger.debug(\"Remote tunnel: disabled (controller not initialized)\");\n } else {\n logger.debug(\n \"Remote tunnel: %s; %s\",\n remoteServerController.isAllowedByEnv() ? \"allowed\" : \"blocked\",\n remoteServerController.isActive() ? \"active\" : \"inactive\",\n );\n }\n }\n\n private async _gracefulShutdown() {\n logger.info(\"Starting graceful shutdown...\");\n\n if (this.viteDevServer) {\n await this.viteDevServer.close();\n }\n\n if (this.remoteTunnelController) {\n this.remoteTunnelController.cleanup();\n }\n\n // 1. abort active operations from plugins\n if (this.config.plugins) {\n for (const plugin of Object.values(this.config.plugins)) {\n if (plugin.abortActiveOperations) {\n try {\n plugin.abortActiveOperations();\n } catch (err) {\n logger.error(\n \"Error aborting operations for plugin %s: %O\",\n plugin.name,\n err,\n );\n }\n }\n }\n }\n\n // 2. close the server\n if (this.server) {\n this.server.close(() => {\n logger.debug(\"Server closed gracefully\");\n process.exit(0);\n });\n\n // 3. timeout to force shutdown after 15 seconds\n setTimeout(() => {\n logger.debug(\"Force shutdown after timeout\");\n process.exit(1);\n }, 15000);\n } else {\n process.exit(0);\n }\n }\n\n /**\n * Returns the public exports for the server plugin.\n * Exposes server management methods.\n */\n exports() {\n const self = this;\n return {\n /** Start the server */\n start: this.start,\n /** Extend the server with custom routes or middleware */\n extend(fn: (app: express.Application) => void) {\n self.extend(fn);\n return this;\n },\n /** Get the underlying HTTP server instance */\n getServer: this.getServer,\n /** Get the server configuration */\n getConfig: this.getConfig,\n };\n }\n}\n\nconst EXCLUDED_PLUGINS: string[] = [ServerPlugin.manifest.name];\n\n/**\n * @internal\n */\nexport const server = toPlugin(ServerPlugin);\n// Export manifest and types\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"utils.js","names":[],"sources":["../../../src/plugins/server/utils.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\n\nexport function parseCookies(\n req: http.IncomingMessage,\n): Record<string, string> {\n const cookieHeader = req.headers.cookie;\n if (!cookieHeader) return {};\n\n // Fast path: if there's no semicolon, there's only one cookie\n const semicolonIndex = cookieHeader.indexOf(\";\");\n if (semicolonIndex === -1) {\n const eqIndex = cookieHeader.indexOf(\"=\");\n if (eqIndex === -1) return {};\n return {\n [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1),\n };\n }\n\n // Multiple cookies: parse them all\n const cookies: Record<string, string> = {};\n const parts = cookieHeader.split(\";\");\n for (let i = 0; i < parts.length; i++) {\n const eqIndex = parts[i].indexOf(\"=\");\n if (eqIndex !== -1) {\n const key = parts[i].slice(0, eqIndex).trim();\n const value = parts[i].slice(eqIndex + 1);\n cookies[key] = value;\n }\n }\n return cookies;\n}\n\nexport function generateTunnelIdFromEmail(email?: string): string | undefined {\n if (!email) return undefined;\n\n const tunnelId = crypto\n .createHash(\"sha256\")\n .update(email)\n .digest(\"base64url\")\n .slice(0, 8);\n\n return tunnelId;\n}\n\nexport function getRoutes(stack: unknown[], basePath = \"\") {\n const routes: Array<{ path: string; methods: string[] }> = [];\n\n stack.forEach((layer: any) => {\n if (layer.route) {\n // normal route\n const path = basePath + layer.route.path;\n const methods = Object.keys(layer.route.methods).map((m) =>\n m.toUpperCase(),\n );\n routes.push({ path, methods });\n } else if (layer.name === \"router\" && layer.handle.stack) {\n // nested router\n const nestedBase =\n basePath +\n layer.regexp.source\n .replace(\"^\\\\\", \"\")\n .replace(\"\\\\/?(?=\\\\/|$)\", \"\")\n .replace(/\\\\\\//g, \"/\") // convert escaped slashes\n .replace(/\\$$/, \"\") || \"\";\n routes.push(...getRoutes(layer.handle.stack, nestedBase));\n }\n });\n\n return routes;\n}\n\nconst METHOD_COLORS: Record<string, (s: string) => string> = {\n GET: pc.green,\n POST: pc.blue,\n PUT: pc.yellow,\n PATCH: pc.yellow,\n DELETE: pc.red,\n HEAD: pc.magenta,\n OPTIONS: pc.magenta,\n};\n\nexport function printRoutes(\n routes: Array<{ path: string; methods: string[] }>,\n) {\n if (routes.length === 0) return;\n\n const rows = routes\n .flatMap((r) => r.methods.map((m) => ({ method: m, path: r.path })))\n .sort(\n (a, b) =>\n a.method.localeCompare(b.method) || a.path.localeCompare(b.path),\n );\n\n const maxMethodLen = Math.max(...rows.map((r) => r.method.length));\n const separator = pc.dim(\"─\".repeat(50));\n\n const colorizeParams = (p: string) =>\n p.replace(/(:[a-zA-Z_]\\w*)/g, (match) => pc.cyan(match));\n\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Registered Routes\")} ${pc.dim(`(${rows.length})`)}`,\n );\n console.log(` ${separator}`);\n\n for (const { method, path } of rows) {\n const colorize = METHOD_COLORS[method] || pc.white;\n const methodStr = colorize(pc.bold(method.padEnd(maxMethodLen)));\n console.log(` ${methodStr} ${colorizeParams(path)}`);\n }\n\n console.log(` ${separator}`);\n console.log(\"\");\n}\n\nexport function getQueries(configFolder: string) {\n const queriesFolder = path.join(configFolder, \"queries\");\n\n if (!fs.existsSync(queriesFolder)) {\n return {};\n }\n\n return Object.fromEntries(\n fs\n .readdirSync(queriesFolder)\n .filter((f) => path.extname(f) === \".sql\")\n .map((f) => [path.basename(f, \".sql\"), path.basename(f, \".sql\")]),\n );\n}\n\nimport type { PluginEndpoints } from \"shared\";\n\nexport type { PluginEndpoints };\n\nexport interface RuntimeConfig {\n appName: string;\n queries: Record<string, string>;\n endpoints: PluginEndpoints;\n}\n\nexport function getRuntimeConfig(\n endpoints: PluginEndpoints = {},\n): RuntimeConfig {\n const configFolder = path.join(process.cwd(), \"config\");\n\n return {\n appName: process.env.DATABRICKS_APP_NAME || \"\",\n queries: getQueries(configFolder),\n endpoints,\n };\n}\n\nexport function getConfigScript(endpoints: PluginEndpoints = {}): string {\n const config = getRuntimeConfig(endpoints);\n\n return `\n <script>\n window.__CONFIG__ = ${JSON.stringify(config)};\n </script>\n `;\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"utils.js","names":[],"sources":["../../../src/plugins/server/utils.ts"],"sourcesContent":["import crypto from \"node:crypto\";\nimport fs from \"node:fs\";\nimport type http from \"node:http\";\nimport path from \"node:path\";\nimport pc from \"picocolors\";\n\nexport function parseCookies(\n req: http.IncomingMessage,\n): Record<string, string> {\n const cookieHeader = req.headers.cookie;\n if (!cookieHeader) return {};\n\n // Fast path: if there's no semicolon, there's only one cookie\n const semicolonIndex = cookieHeader.indexOf(\";\");\n if (semicolonIndex === -1) {\n const eqIndex = cookieHeader.indexOf(\"=\");\n if (eqIndex === -1) return {};\n return {\n [cookieHeader.slice(0, eqIndex).trim()]: cookieHeader.slice(eqIndex + 1),\n };\n }\n\n // Multiple cookies: parse them all\n const cookies: Record<string, string> = {};\n const parts = cookieHeader.split(\";\");\n for (let i = 0; i < parts.length; i++) {\n const eqIndex = parts[i].indexOf(\"=\");\n if (eqIndex !== -1) {\n const key = parts[i].slice(0, eqIndex).trim();\n const value = parts[i].slice(eqIndex + 1);\n cookies[key] = value;\n }\n }\n return cookies;\n}\n\nexport function generateTunnelIdFromEmail(email?: string): string | undefined {\n if (!email) return undefined;\n\n const tunnelId = crypto\n .createHash(\"sha256\")\n .update(email)\n .digest(\"base64url\")\n .slice(0, 8);\n\n return tunnelId;\n}\n\nexport function getRoutes(stack: unknown[], basePath = \"\") {\n const routes: Array<{ path: string; methods: string[] }> = [];\n\n stack.forEach((layer: any) => {\n if (layer.route) {\n // normal route\n const path = basePath + layer.route.path;\n const methods = Object.keys(layer.route.methods).map((m) =>\n m.toUpperCase(),\n );\n routes.push({ path, methods });\n } else if (layer.name === \"router\" && layer.handle.stack) {\n // nested router\n const nestedBase =\n basePath +\n layer.regexp.source\n .replace(\"^\\\\\", \"\")\n .replace(\"\\\\/?(?=\\\\/|$)\", \"\")\n .replace(/\\\\\\//g, \"/\") // convert escaped slashes\n .replace(/\\$$/, \"\") || \"\";\n routes.push(...getRoutes(layer.handle.stack, nestedBase));\n }\n });\n\n return routes;\n}\n\nconst METHOD_COLORS: Record<string, (s: string) => string> = {\n GET: pc.green,\n POST: pc.blue,\n PUT: pc.yellow,\n PATCH: pc.yellow,\n DELETE: pc.red,\n HEAD: pc.magenta,\n OPTIONS: pc.magenta,\n};\n\nexport function printRoutes(\n routes: Array<{ path: string; methods: string[] }>,\n) {\n if (routes.length === 0) return;\n\n const rows = routes\n .flatMap((r) => r.methods.map((m) => ({ method: m, path: r.path })))\n .sort(\n (a, b) =>\n a.method.localeCompare(b.method) || a.path.localeCompare(b.path),\n );\n\n const maxMethodLen = Math.max(...rows.map((r) => r.method.length));\n const separator = pc.dim(\"─\".repeat(50));\n\n const colorizeParams = (p: string) =>\n p.replace(/(:[a-zA-Z_]\\w*)/g, (match) => pc.cyan(match));\n\n console.log(\"\");\n console.log(\n ` ${pc.bold(\"Registered Routes\")} ${pc.dim(`(${rows.length})`)}`,\n );\n console.log(` ${separator}`);\n\n for (const { method, path } of rows) {\n const colorize = METHOD_COLORS[method] || pc.white;\n const methodStr = colorize(pc.bold(method.padEnd(maxMethodLen)));\n console.log(` ${methodStr} ${colorizeParams(path)}`);\n }\n\n console.log(` ${separator}`);\n console.log(\"\");\n}\n\nexport function getQueries(configFolder: string) {\n const queriesFolder = path.join(configFolder, \"queries\");\n\n if (!fs.existsSync(queriesFolder)) {\n return {};\n }\n\n return Object.fromEntries(\n fs\n .readdirSync(queriesFolder)\n .filter((f) => path.extname(f) === \".sql\")\n .map((f) => [path.basename(f, \".sql\"), path.basename(f, \".sql\")]),\n );\n}\n\nimport type { PluginEndpoints } from \"shared\";\n\nexport type { PluginEndpoints };\n\ninterface RuntimeConfig {\n appName: string;\n queries: Record<string, string>;\n endpoints: PluginEndpoints;\n}\n\nfunction getRuntimeConfig(endpoints: PluginEndpoints = {}): RuntimeConfig {\n const configFolder = path.join(process.cwd(), \"config\");\n\n return {\n appName: process.env.DATABRICKS_APP_NAME || \"\",\n queries: getQueries(configFolder),\n endpoints,\n };\n}\n\nexport function getConfigScript(endpoints: PluginEndpoints = {}): string {\n const config = getRuntimeConfig(endpoints);\n\n return `\n <script>\n window.__CONFIG__ = ${JSON.stringify(config)};\n </script>\n `;\n}\n"],"mappings":"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"}
@@ -1,4 +1,5 @@
1
- import { AppPermission, DatabasePermission, ExperimentPermission, GenieSpacePermission, JobPermission, ResourcePermission, ResourceType, SecretPermission, ServingEndpointPermission, SqlWarehousePermission, UcConnectionPermission, UcFunctionPermission, VectorSearchIndexPermission, VolumePermission } from "./types.generated.js";
2
- import { ConfigSchema, PluginManifest, ResourceEntry, ResourceFieldEntry, ResourceRequirement, ValidationResult } from "./types.js";
1
+ import { ResourceFieldEntry } from "../shared/src/schemas/plugin-manifest.generated.js";
2
+ import { ResourcePermission, ResourceType } from "./types.generated.js";
3
+ import { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult } from "./types.js";
3
4
  import { getPluginManifest, getResourceRequirements } from "./manifest-loader.js";
4
5
  import { ResourceRegistry } from "./resource-registry.js";
@@ -1,7 +1,8 @@
1
+ import { ResourceFieldEntry } from "../shared/src/schemas/plugin-manifest.generated.js";
1
2
  import { PluginConstructor } from "../shared/src/plugin.js";
2
3
  import "../shared/src/index.js";
3
4
  import { ResourcePermission, ResourceType } from "./types.generated.js";
4
- import { PluginManifest, ResourceFieldEntry } from "./types.js";
5
+ import { PluginManifest } from "./types.js";
5
6
 
6
7
  //#region src/registry/manifest-loader.d.ts
7
8
  /**
@@ -33,12 +34,12 @@ declare function getPluginManifest(plugin: PluginConstructor): PluginManifest;
33
34
  */
34
35
  declare function getResourceRequirements(plugin: PluginConstructor): {
35
36
  required: boolean;
37
+ description: string;
38
+ fields: Record<string, ResourceFieldEntry>;
36
39
  type: ResourceType;
37
40
  alias: string;
38
41
  resourceKey: string;
39
- description: string;
40
42
  permission: ResourcePermission;
41
- fields: Record<string, ResourceFieldEntry>;
42
43
  }[];
43
44
  //#endregion
44
45
  export { getPluginManifest, getResourceRequirements };
@@ -1 +1 @@
1
- {"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;AA4DA;;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;AA4F9D;;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA"}
1
+ {"version":3,"file":"manifest-loader.d.ts","names":[],"sources":["../../src/registry/manifest-loader.ts"],"mappings":";;;;;;;;;;;AA4DA;;;;iBAAgB,iBAAA,CAAkB,MAAA,EAAQ,iBAAA,GAAoB,cAAA;;;;;AA4F9D;;;;;;;;;;;;;;iBAAgB,uBAAA,CAAwB,MAAA,EAAQ,iBAAA;;;yBAAiB,kBAAA"}
@@ -1,49 +1,23 @@
1
- import { AppPermission, DatabasePermission, ExperimentPermission, GenieSpacePermission, JobPermission, ResourcePermission, ResourceType, SecretPermission, ServingEndpointPermission, SqlWarehousePermission, UcConnectionPermission, UcFunctionPermission, VectorSearchIndexPermission, VolumePermission } from "./types.generated.js";
1
+ import { ResourceFieldEntry } from "../shared/src/schemas/plugin-manifest.generated.js";
2
+ import { PluginManifest as PluginManifest$1, ResourceRequirement as ResourceRequirement$1 } from "../shared/src/plugin.js";
3
+ import "../shared/src/index.js";
4
+ import { ResourcePermission, ResourceType } from "./types.generated.js";
2
5
  import { JSONSchema7 } from "json-schema";
3
6
 
4
7
  //#region src/registry/types.d.ts
5
- /**
6
- * Defines a single field for a resource. Each field has its own environment variable and optional description.
7
- * Single-value types use one key (e.g. id); multi-value types (database, secret) use multiple (e.g. instance_name, database_name or scope, key).
8
- */
9
- interface ResourceFieldEntry {
10
- /** Environment variable name for this field */
11
- env?: string;
12
- /** Human-readable description for this field */
13
- description?: string;
14
- /** When true, this field is excluded from Databricks bundle configuration (databricks.yml) generation. */
15
- bundleIgnore?: boolean;
16
- /** Example values showing the expected format for this field */
17
- examples?: string[];
18
- /** When true, this field is only generated for local .env files. The Databricks Apps platform auto-injects it at deploy time. */
19
- localOnly?: boolean;
20
- /** Static value for this field. Used when no prompted or resolved value exists. */
21
- value?: string;
22
- /** Named resolver prefixed by resource type (e.g., 'postgres:host'). The CLI resolves this value during the init prompt flow. */
23
- resolve?: string;
24
- }
25
8
  /**
26
9
  * Declares a resource requirement for a plugin.
27
10
  * Can be defined statically in a manifest or dynamically via getResourceRequirements().
11
+ * Narrows the generated base: type → ResourceType enum, permission → ResourcePermission union.
12
+ *
13
+ * @see `packages/shared/src/schemas/plugin-manifest.generated.ts` `ResourceRequirement` — generated base
14
+ * @see {@link SharedResourceRequirement} — shared re-export with runtime `fields` and `required`
28
15
  */
29
- interface ResourceRequirement {
30
- /** Type of Databricks resource required */
16
+ interface ResourceRequirement extends ResourceRequirement$1 {
17
+ /** Type of Databricks resource required (narrowed to enum) */
31
18
  type: ResourceType;
32
- /** Unique alias for this resource within the plugin (e.g., 'warehouse', 'secrets'). Used for UI/display. */
33
- alias: string;
34
- /** Stable key for machine use (env naming, composite keys, app.yaml). Required. */
35
- resourceKey: string;
36
- /** Human-readable description of why this resource is needed */
37
- description: string;
38
- /** Required permission level for the resource */
19
+ /** Required permission level for the resource (narrowed to union) */
39
20
  permission: ResourcePermission;
40
- /**
41
- * Map of field name to env and optional description.
42
- * Single-value types use one key (e.g. id); multi-value (database, secret) use multiple keys.
43
- */
44
- fields: Record<string, ResourceFieldEntry>;
45
- /** Whether this resource is required (true) or optional (false) */
46
- required: boolean;
47
21
  }
48
22
  /**
49
23
  * Internal representation of a resource in the registry.
@@ -84,16 +58,16 @@ type ConfigSchema = JSONSchema7;
84
58
  /**
85
59
  * Plugin manifest that declares metadata and resource requirements.
86
60
  * Attached to plugin classes as a static property.
61
+ * Extends the shared PluginManifest with strict resource types.
62
+ *
63
+ * @see `packages/shared/src/schemas/plugin-manifest.generated.ts` `PluginManifest` — generated base
64
+ * @see {@link SharedPluginManifest} — shared re-export with JSONSchema7 config
87
65
  */
88
- interface PluginManifest<TName extends string = string> {
66
+ interface PluginManifest<TName extends string = string> extends Omit<PluginManifest$1, "resources" | "config"> {
89
67
  /** Plugin identifier — the single source of truth for the plugin's name */
90
68
  name: TName;
91
- /** Human-readable display name for UI/CLI */
92
- displayName: string;
93
- /** Brief description of what the plugin does */
94
- description: string;
95
69
  /**
96
- * Resource requirements declaration
70
+ * Resource requirements declaration (with strict ResourceRequirement types)
97
71
  */
98
72
  resources: {
99
73
  /** Resources that must be available for the plugin to function */required: Omit<ResourceRequirement, "required">[]; /** Resources that enhance functionality but are not mandatory */
@@ -101,24 +75,12 @@ interface PluginManifest<TName extends string = string> {
101
75
  };
102
76
  /**
103
77
  * Configuration schema for the plugin.
104
- * Defines the shape and validation rules for plugin config.
78
+ * Uses JSONSchema7 instead of the generated ConfigSchema (which is too restrictive).
105
79
  */
106
80
  config?: {
107
81
  schema: ConfigSchema;
108
82
  };
109
- /**
110
- * When true, excluded from the template plugins manifest during sync.
111
- */
112
- hidden?: boolean;
113
- /**
114
- * Optional metadata for community plugins
115
- */
116
- author?: string;
117
- version?: string;
118
- repository?: string;
119
- keywords?: string[];
120
- license?: string;
121
83
  }
122
84
  //#endregion
123
- export { ConfigSchema, PluginManifest, ResourceEntry, ResourceFieldEntry, ResourceRequirement, ValidationResult };
85
+ export { ConfigSchema, PluginManifest, ResourceEntry, ResourceRequirement, ValidationResult };
124
86
  //# sourceMappingURL=types.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/registry/types.ts"],"mappings":";;;;;;;;UA0DiB,kBAAA;EAqBA;EAnBf,GAAA;;EAEA,WAAA;EA+BY;EA7BZ,YAAA;EAmCQ;EAjCR,QAAA;EAiCc;EA/Bd,SAAA;EAaM;EAXN,KAAA;EAiBA;EAfA,OAAA;AAAA;;;;;UAOe,mBAAA;EAuBP;EArBR,IAAA,EAAM,YAAA;EA4BS;EAzBf,KAAA;;EAGA,WAAA;EAqCmC;EAlCnC,WAAA;EAmBqC;EAhBrC,UAAA,EAAY,kBAAA;EAgB4C;;;;EAVxD,MAAA,EAAQ,MAAA,SAAe,kBAAA;EAkBd;EAfT,QAAA;AAAA;;;;AA4BF;UArBiB,aAAA,SAAsB,mBAAA;;EAErC,MAAA;EAqBA;EAlBA,QAAA;EAqBS;EAlBT,MAAA,GAAS,MAAA;EAqBJ;;;AAWP;;EAzBE,iBAAA,GAAoB,MAAA,SAAe,kBAAA;AAAA;;AA+BrC;;UAzBiB,gBAAA;EA2BT;EAzBN,KAAA;EAsCY;EAnCZ,OAAA,EAAS,aAAA;EAsCG;EAnCZ,GAAA,EAAK,aAAA;AAAA;;;;;;;KAWK,YAAA,GAAe,WAAA;;;;;UAMV,cAAA;EAyBf;EAvBA,IAAA,EAAM,KAAA;EAwBI;EArBV,WAAA;EAgCA;EA7BA,WAAA;EA+BA;;;EA1BA,SAAA;IA4BO,kEA1BL,QAAA,EAAU,IAAA,CAAK,mBAAA;IAGf,QAAA,EAAU,IAAA,CAAK,mBAAA;EAAA;;;;;EAOjB,MAAA;IACE,MAAA,EAAQ,YAAA;EAAA;;;;EAMV,MAAA;;;;EAKA,MAAA;EACA,OAAA;EACA,UAAA;EACA,QAAA;EACA,OAAA;AAAA"}
1
+ {"version":3,"file":"types.d.ts","names":[],"sources":["../../src/registry/types.ts"],"mappings":";;;;;;;;;;AAgEA;;;;;UAZiB,mBAAA,SAA4B,qBAAA;EAYN;EAVrC,IAAA,EAAM,YAAA;EAUkD;EAPxD,UAAA,EAAY,kBAAA;AAAA;;;;;UAOG,aAAA,SAAsB,mBAAA;EAeF;EAbnC,MAAA;EAaqD;EAVrD,QAAA;EAgB+B;EAb/B,MAAA,GAAS,MAAA;EAqBS;;;;;EAdlB,iBAAA,GAAoB,MAAA,SAAe,kBAAA;AAAA;;AAyBrC;;UAnBiB,gBAAA;EAmBU;EAjBzB,KAAA;EA2Be;EAxBf,OAAA,EAAS,aAAA;EAwBoB;EArB7B,GAAA,EAAK,aAAA;AAAA;;;;;;;KAWK,YAAA,GAAe,WAAA;;;;;;;;;UAUV,cAAA,wCACP,IAAA,CAAK,gBAAA;EAYX;EAVF,IAAA,EAAM,KAAA;EAUW;;;EALjB,SAAA;IAasB,kEAXpB,QAAA,EAAU,IAAA,CAAK,mBAAA;IAGf,QAAA,EAAU,IAAA,CAAK,mBAAA;EAAA;;;;;EAOjB,MAAA;IACE,MAAA,EAAQ,YAAA;EAAA;AAAA"}
@@ -44,5 +44,5 @@ type AppPermission = "CAN_USE";
44
44
  /** Union of all possible permission levels across all resource types. */
45
45
  type ResourcePermission = SecretPermission | JobPermission | SqlWarehousePermission | ServingEndpointPermission | VolumePermission | VectorSearchIndexPermission | UcFunctionPermission | UcConnectionPermission | DatabasePermission | PostgresPermission | GenieSpacePermission | ExperimentPermission | AppPermission;
46
46
  //#endregion
47
- export { AppPermission, DatabasePermission, ExperimentPermission, GenieSpacePermission, JobPermission, ResourcePermission, ResourceType, SecretPermission, ServingEndpointPermission, SqlWarehousePermission, UcConnectionPermission, UcFunctionPermission, VectorSearchIndexPermission, VolumePermission };
47
+ export { ResourcePermission, ResourceType };
48
48
  //# sourceMappingURL=types.generated.d.ts.map
@@ -8,8 +8,13 @@ import { PERMISSIONS_BY_TYPE, PERMISSION_HIERARCHY_BY_TYPE, ResourceType } from
8
8
  * which enables plugins to declare their Databricks resource requirements
9
9
  * in a machine-readable format.
10
10
  *
11
- * Resource types and permissions are generated from plugin-manifest.schema.json
12
- * (see types.generated.ts). Hand-written interfaces below define the registry API.
11
+ * Base interfaces (ResourceFieldEntry, ResourceRequirement, PluginManifest)
12
+ * are generated from plugin-manifest.schema.json and imported via shared.
13
+ * This module re-exports them with strict narrowing (ResourceType enum,
14
+ * ResourcePermission union) for appkit-internal use.
15
+ *
16
+ * Resource types, permissions, and hierarchy constants are generated by
17
+ * tools/generate-registry-types.ts into types.generated.ts.
13
18
  */
14
19
 
15
20
  //#endregion