@databricks/appkit 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/CLAUDE.md +1 -0
  2. package/dist/analytics/analytics.d.ts +11 -1
  3. package/dist/analytics/analytics.d.ts.map +1 -1
  4. package/dist/analytics/analytics.js +12 -5
  5. package/dist/analytics/analytics.js.map +1 -1
  6. package/dist/app/index.d.ts +11 -2
  7. package/dist/app/index.d.ts.map +1 -1
  8. package/dist/app/index.js +51 -37
  9. package/dist/app/index.js.map +1 -1
  10. package/dist/appkit/package.js +1 -1
  11. package/dist/cache/index.js +11 -10
  12. package/dist/cache/index.js.map +1 -1
  13. package/dist/cache/storage/memory.js +4 -2
  14. package/dist/cache/storage/memory.js.map +1 -1
  15. package/dist/cache/storage/persistent.js +7 -0
  16. package/dist/cache/storage/persistent.js.map +1 -1
  17. package/dist/connectors/lakebase/client.js +8 -4
  18. package/dist/connectors/lakebase/client.js.map +1 -1
  19. package/dist/connectors/sql-warehouse/client.js +5 -2
  20. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  21. package/dist/context/execution-context.d.ts +17 -0
  22. package/dist/context/execution-context.d.ts.map +1 -0
  23. package/dist/context/service-context.d.ts +21 -0
  24. package/dist/context/service-context.d.ts.map +1 -0
  25. package/dist/context/service-context.js +2 -6
  26. package/dist/context/service-context.js.map +1 -1
  27. package/dist/context/user-context.d.ts +29 -0
  28. package/dist/context/user-context.d.ts.map +1 -0
  29. package/dist/core/appkit.d.ts.map +1 -1
  30. package/dist/core/appkit.js +35 -12
  31. package/dist/core/appkit.js.map +1 -1
  32. package/dist/errors/authentication.js +3 -6
  33. package/dist/errors/authentication.js.map +1 -1
  34. package/dist/errors/base.js +4 -0
  35. package/dist/errors/base.js.map +1 -1
  36. package/dist/errors/configuration.js +3 -6
  37. package/dist/errors/configuration.js.map +1 -1
  38. package/dist/errors/connection.js +3 -6
  39. package/dist/errors/connection.js.map +1 -1
  40. package/dist/errors/execution.js +3 -6
  41. package/dist/errors/execution.js.map +1 -1
  42. package/dist/errors/initialization.js +3 -6
  43. package/dist/errors/initialization.js.map +1 -1
  44. package/dist/errors/server.js +3 -6
  45. package/dist/errors/server.js.map +1 -1
  46. package/dist/errors/tunnel.js +3 -6
  47. package/dist/errors/tunnel.js.map +1 -1
  48. package/dist/errors/validation.js +3 -6
  49. package/dist/errors/validation.js.map +1 -1
  50. package/dist/index.d.ts +2 -1
  51. package/dist/index.js +4 -1
  52. package/dist/index.js.map +1 -1
  53. package/dist/logging/wide-event-emitter.js +1 -3
  54. package/dist/logging/wide-event-emitter.js.map +1 -1
  55. package/dist/logging/wide-event.js +2 -0
  56. package/dist/logging/wide-event.js.map +1 -1
  57. package/dist/plugin/dev-reader.d.ts +1 -0
  58. package/dist/plugin/dev-reader.d.ts.map +1 -1
  59. package/dist/plugin/dev-reader.js +41 -6
  60. package/dist/plugin/dev-reader.js.map +1 -1
  61. package/dist/plugin/interceptors/retry.js +3 -0
  62. package/dist/plugin/interceptors/retry.js.map +1 -1
  63. package/dist/plugin/plugin.d.ts +10 -24
  64. package/dist/plugin/plugin.d.ts.map +1 -1
  65. package/dist/plugin/plugin.js +23 -30
  66. package/dist/plugin/plugin.js.map +1 -1
  67. package/dist/server/base-server.js +2 -0
  68. package/dist/server/base-server.js.map +1 -1
  69. package/dist/server/index.d.ts +23 -1
  70. package/dist/server/index.d.ts.map +1 -1
  71. package/dist/server/index.js +29 -13
  72. package/dist/server/index.js.map +1 -1
  73. package/dist/server/remote-tunnel/remote-tunnel-controller.js +30 -15
  74. package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
  75. package/dist/server/remote-tunnel/remote-tunnel-manager.js +27 -2
  76. package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  77. package/dist/server/static-server.js +1 -0
  78. package/dist/server/static-server.js.map +1 -1
  79. package/dist/server/vite-dev-server.js +1 -0
  80. package/dist/server/vite-dev-server.js.map +1 -1
  81. package/dist/shared/src/plugin.d.ts +25 -1
  82. package/dist/shared/src/plugin.d.ts.map +1 -1
  83. package/dist/stream/arrow-stream-processor.js +5 -10
  84. package/dist/stream/arrow-stream-processor.js.map +1 -1
  85. package/dist/stream/buffers.js +7 -0
  86. package/dist/stream/buffers.js.map +1 -1
  87. package/dist/stream/stream-manager.js +5 -0
  88. package/dist/stream/stream-manager.js.map +1 -1
  89. package/dist/stream/stream-registry.js +1 -0
  90. package/dist/stream/stream-registry.js.map +1 -1
  91. package/dist/stream/validator.js +2 -6
  92. package/dist/stream/validator.js.map +1 -1
  93. package/dist/telemetry/noop.js +1 -0
  94. package/dist/telemetry/noop.js.map +1 -1
  95. package/dist/telemetry/telemetry-manager.js +4 -6
  96. package/dist/telemetry/telemetry-manager.js.map +1 -1
  97. package/dist/telemetry/telemetry-provider.js +3 -0
  98. package/dist/telemetry/telemetry-provider.js.map +1 -1
  99. package/dist/type-generator/spinner.js +9 -11
  100. package/dist/type-generator/spinner.js.map +1 -1
  101. package/docs/docs/api/appkit/Class.AppKitError/index.html +5 -3
  102. package/docs/docs/api/appkit/Class.AppKitError.md +7 -0
  103. package/docs/docs/api/appkit/Class.AuthenticationError/index.html +3 -3
  104. package/docs/docs/api/appkit/Class.ConfigurationError/index.html +3 -3
  105. package/docs/docs/api/appkit/Class.ConnectionError/index.html +3 -3
  106. package/docs/docs/api/appkit/Class.ExecutionError/index.html +3 -3
  107. package/docs/docs/api/appkit/Class.InitializationError/index.html +3 -3
  108. package/docs/docs/api/appkit/Class.Plugin/index.html +28 -21
  109. package/docs/docs/api/appkit/Class.Plugin.md +34 -34
  110. package/docs/docs/api/appkit/Class.ServerError/index.html +3 -3
  111. package/docs/docs/api/appkit/Class.TunnelError/index.html +3 -3
  112. package/docs/docs/api/appkit/Class.ValidationError/index.html +3 -3
  113. package/docs/docs/api/appkit/Function.appKitTypesPlugin/index.html +3 -3
  114. package/docs/docs/api/appkit/Function.createApp/index.html +4 -4
  115. package/docs/docs/api/appkit/Function.getExecutionContext/index.html +26 -0
  116. package/docs/docs/api/appkit/Function.getExecutionContext.md +19 -0
  117. package/docs/docs/api/appkit/Function.isSQLTypeMarker/index.html +4 -4
  118. package/docs/docs/api/appkit/Interface.BasePluginConfig/index.html +3 -3
  119. package/docs/docs/api/appkit/Interface.CacheConfig/index.html +3 -3
  120. package/docs/docs/api/appkit/Interface.ITelemetry/index.html +3 -3
  121. package/docs/docs/api/appkit/Interface.StreamExecutionSettings/index.html +3 -3
  122. package/docs/docs/api/appkit/Interface.TelemetryConfig/index.html +3 -3
  123. package/docs/docs/api/appkit/TypeAlias.IAppRouter/index.html +3 -3
  124. package/docs/docs/api/appkit/Variable.sql/index.html +3 -3
  125. package/docs/docs/api/appkit/index.html +4 -4
  126. package/docs/docs/api/appkit-ui/data/AreaChart/index.html +2 -2
  127. package/docs/docs/api/appkit-ui/data/BarChart/index.html +2 -2
  128. package/docs/docs/api/appkit-ui/data/DataTable/index.html +2 -2
  129. package/docs/docs/api/appkit-ui/data/DonutChart/index.html +2 -2
  130. package/docs/docs/api/appkit-ui/data/HeatmapChart/index.html +2 -2
  131. package/docs/docs/api/appkit-ui/data/LineChart/index.html +2 -2
  132. package/docs/docs/api/appkit-ui/data/PieChart/index.html +2 -2
  133. package/docs/docs/api/appkit-ui/data/RadarChart/index.html +2 -2
  134. package/docs/docs/api/appkit-ui/data/ScatterChart/index.html +2 -2
  135. package/docs/docs/api/appkit-ui/index.html +2 -2
  136. package/docs/docs/api/appkit-ui/styling/index.html +2 -2
  137. package/docs/docs/api/appkit-ui/ui/Accordion/index.html +2 -2
  138. package/docs/docs/api/appkit-ui/ui/Alert/index.html +2 -2
  139. package/docs/docs/api/appkit-ui/ui/AlertDialog/index.html +2 -2
  140. package/docs/docs/api/appkit-ui/ui/AspectRatio/index.html +2 -2
  141. package/docs/docs/api/appkit-ui/ui/Avatar/index.html +2 -2
  142. package/docs/docs/api/appkit-ui/ui/Badge/index.html +2 -2
  143. package/docs/docs/api/appkit-ui/ui/Breadcrumb/index.html +2 -2
  144. package/docs/docs/api/appkit-ui/ui/Button/index.html +2 -2
  145. package/docs/docs/api/appkit-ui/ui/ButtonGroup/index.html +2 -2
  146. package/docs/docs/api/appkit-ui/ui/Calendar/index.html +2 -2
  147. package/docs/docs/api/appkit-ui/ui/Card/index.html +2 -2
  148. package/docs/docs/api/appkit-ui/ui/Carousel/index.html +2 -2
  149. package/docs/docs/api/appkit-ui/ui/ChartContainer/index.html +2 -2
  150. package/docs/docs/api/appkit-ui/ui/Checkbox/index.html +2 -2
  151. package/docs/docs/api/appkit-ui/ui/Collapsible/index.html +2 -2
  152. package/docs/docs/api/appkit-ui/ui/Command/index.html +2 -2
  153. package/docs/docs/api/appkit-ui/ui/ContextMenu/index.html +2 -2
  154. package/docs/docs/api/appkit-ui/ui/Dialog/index.html +2 -2
  155. package/docs/docs/api/appkit-ui/ui/Drawer/index.html +2 -2
  156. package/docs/docs/api/appkit-ui/ui/DropdownMenu/index.html +2 -2
  157. package/docs/docs/api/appkit-ui/ui/Empty/index.html +2 -2
  158. package/docs/docs/api/appkit-ui/ui/Field/index.html +2 -2
  159. package/docs/docs/api/appkit-ui/ui/FormControl/index.html +2 -2
  160. package/docs/docs/api/appkit-ui/ui/HoverCard/index.html +2 -2
  161. package/docs/docs/api/appkit-ui/ui/Input/index.html +2 -2
  162. package/docs/docs/api/appkit-ui/ui/InputGroup/index.html +2 -2
  163. package/docs/docs/api/appkit-ui/ui/InputOTP/index.html +2 -2
  164. package/docs/docs/api/appkit-ui/ui/Item/index.html +2 -2
  165. package/docs/docs/api/appkit-ui/ui/Kbd/index.html +2 -2
  166. package/docs/docs/api/appkit-ui/ui/Label/index.html +2 -2
  167. package/docs/docs/api/appkit-ui/ui/Menubar/index.html +2 -2
  168. package/docs/docs/api/appkit-ui/ui/NavigationMenu/index.html +2 -2
  169. package/docs/docs/api/appkit-ui/ui/Pagination/index.html +2 -2
  170. package/docs/docs/api/appkit-ui/ui/Popover/index.html +2 -2
  171. package/docs/docs/api/appkit-ui/ui/Progress/index.html +2 -2
  172. package/docs/docs/api/appkit-ui/ui/RadioGroup/index.html +2 -2
  173. package/docs/docs/api/appkit-ui/ui/ResizableHandle/index.html +2 -2
  174. package/docs/docs/api/appkit-ui/ui/ScrollArea/index.html +2 -2
  175. package/docs/docs/api/appkit-ui/ui/Select/index.html +2 -2
  176. package/docs/docs/api/appkit-ui/ui/Separator/index.html +2 -2
  177. package/docs/docs/api/appkit-ui/ui/Sheet/index.html +2 -2
  178. package/docs/docs/api/appkit-ui/ui/Sidebar/index.html +2 -2
  179. package/docs/docs/api/appkit-ui/ui/Skeleton/index.html +2 -2
  180. package/docs/docs/api/appkit-ui/ui/Slider/index.html +2 -2
  181. package/docs/docs/api/appkit-ui/ui/Spinner/index.html +2 -2
  182. package/docs/docs/api/appkit-ui/ui/Switch/index.html +2 -2
  183. package/docs/docs/api/appkit-ui/ui/Table/index.html +2 -2
  184. package/docs/docs/api/appkit-ui/ui/Tabs/index.html +2 -2
  185. package/docs/docs/api/appkit-ui/ui/Textarea/index.html +2 -2
  186. package/docs/docs/api/appkit-ui/ui/Toaster/index.html +2 -2
  187. package/docs/docs/api/appkit-ui/ui/Toggle/index.html +2 -2
  188. package/docs/docs/api/appkit-ui/ui/ToggleGroup/index.html +2 -2
  189. package/docs/docs/api/appkit-ui/ui/Tooltip/index.html +2 -2
  190. package/docs/docs/api/appkit.md +6 -5
  191. package/docs/docs/api/index.html +2 -2
  192. package/docs/docs/app-management/index.html +4 -4
  193. package/docs/docs/app-management.md +2 -2
  194. package/docs/docs/architecture/index.html +2 -2
  195. package/docs/docs/category/development/index.html +2 -2
  196. package/docs/docs/configuration/index.html +2 -2
  197. package/docs/docs/core-principles/index.html +2 -2
  198. package/docs/docs/development/index.html +4 -4
  199. package/docs/docs/development/llm-guide/index.html +2 -2
  200. package/docs/docs/development/local-development/index.html +4 -4
  201. package/docs/docs/development/local-development.md +2 -2
  202. package/docs/docs/development/project-setup/index.html +2 -2
  203. package/docs/docs/development/remote-bridge/index.html +4 -4
  204. package/docs/docs/development/remote-bridge.md +2 -2
  205. package/docs/docs/development/type-generation/index.html +2 -2
  206. package/docs/docs/development.md +2 -2
  207. package/docs/docs/index.html +4 -4
  208. package/docs/docs/plugins/index.html +7 -3
  209. package/docs/docs/plugins.md +36 -11
  210. package/docs/docs.md +2 -2
  211. package/llms.txt +1 -0
  212. package/package.json +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../src/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n ) {\n super(app, endpoints);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAG3C,YACE,KACA,YACA,YAA6B,EAAE,EAC/B;AACA,QAAM,KAAK,UAAU;AACrB,OAAK,aAAa;;;CAIpB,QAAQ;AACN,OAAK,IAAI,IACPA,QAAc,OAAO,KAAK,YAAY,EACpC,OAAO,OACR,CAAC,CACH;AAED,OAAK,IAAI,IAAI,MAAM,KAAK,KAAK,SAAS;AACpC,OAAI,IAAI,KAAK,WAAW,OAAO,IAAI,IAAI,KAAK,WAAW,SAAS,CAC9D,QAAO,MAAM;AAEf,QAAK,WAAW,IAAI;IACpB;;;CAIJ,AAAQ,WAAW,KAAuB;EACxC,MAAM,YAAY,KAAK,KAAK,KAAK,YAAY,aAAa;AAE1D,MAAI,CAAC,GAAG,WAAW,UAAU,EAAE;AAC7B,OAAI,OAAO,IAAI,CAAC,KAAK,uBAAuB;AAC5C;;EAGF,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,SAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,MAAI,KAAK,KAAK"}
1
+ {"version":3,"file":"static-server.js","names":["expressStatic"],"sources":["../../src/server/static-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport expressStatic from \"express\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\n/**\n * Static server for the AppKit.\n *\n * Serves pre-built static files in production mode. Handles SPA routing\n * by serving index.html for non-API routes and injects runtime configuration.\n *\n * @example\n * ```ts\n * const staticServer = new StaticServer(app, staticPath, endpoints);\n * staticServer.setup();\n * ```\n */\nexport class StaticServer extends BaseServer {\n private staticPath: string;\n\n constructor(\n app: express.Application,\n staticPath: string,\n endpoints: PluginEndpoints = {},\n ) {\n super(app, endpoints);\n this.staticPath = staticPath;\n }\n\n /** Setup the static server. */\n setup() {\n this.app.use(\n expressStatic.static(this.staticPath, {\n index: false,\n }),\n );\n\n this.app.get(\"*\", (req, res, next) => {\n if (req.path.startsWith(\"/api\") || req.path.startsWith(\"/query\")) {\n return next();\n }\n this.serveIndex(res);\n });\n }\n\n /** Serve the index.html file. */\n private serveIndex(res: express.Response) {\n const indexPath = path.join(this.staticPath, \"index.html\");\n\n if (!fs.existsSync(indexPath)) {\n res.status(404).send(\"index.html not found\");\n return;\n }\n\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n res.send(html);\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;AAmBA,IAAa,eAAb,cAAkC,WAAW;CAC3C,AAAQ;CAER,YACE,KACA,YACA,YAA6B,EAAE,EAC/B;AACA,QAAM,KAAK,UAAU;AACrB,OAAK,aAAa;;;CAIpB,QAAQ;AACN,OAAK,IAAI,IACPA,QAAc,OAAO,KAAK,YAAY,EACpC,OAAO,OACR,CAAC,CACH;AAED,OAAK,IAAI,IAAI,MAAM,KAAK,KAAK,SAAS;AACpC,OAAI,IAAI,KAAK,WAAW,OAAO,IAAI,IAAI,KAAK,WAAW,SAAS,CAC9D,QAAO,MAAM;AAEf,QAAK,WAAW,IAAI;IACpB;;;CAIJ,AAAQ,WAAW,KAAuB;EACxC,MAAM,YAAY,KAAK,KAAK,KAAK,YAAY,aAAa;AAE1D,MAAI,CAAC,GAAG,WAAW,UAAU,EAAE;AAC7B,OAAI,OAAO,IAAI,CAAC,KAAK,uBAAuB;AAC5C;;EAGF,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,SAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,MAAI,KAAK,KAAK"}
@@ -23,6 +23,7 @@ const logger = createLogger("server:vite");
23
23
  * ```
24
24
  */
25
25
  var ViteDevServer = class extends BaseServer {
26
+ vite;
26
27
  constructor(app, endpoints = {}) {
27
28
  super(app, endpoints);
28
29
  this.vite = null;
@@ -1 +1 @@
1
- {"version":3,"file":"vite-dev-server.js","names":[],"sources":["../../src/server/vite-dev-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport type { ViteDevServer as ViteDevServerType } from \"vite\";\nimport { mergeConfigDedup } from \"@/utils\";\nimport { ServerError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { appKitTypesPlugin } from \"../type-generator/vite-plugin\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\nconst logger = createLogger(\"server:vite\");\n\n/**\n * Vite dev server for the AppKit.\n *\n * This class is responsible for serving the Vite dev server for the development server.\n * It also handles the index.html file for the development server.\n *\n * @example\n * ```ts\n * const viteDevServer = new ViteDevServer(app, endpoints);\n * await viteDevServer.setup();\n * ```\n */\nexport class ViteDevServer extends BaseServer {\n private vite: ViteDevServerType | null;\n\n constructor(app: express.Application, endpoints: PluginEndpoints = {}) {\n super(app, endpoints);\n this.vite = null;\n }\n\n /**\n * Setup the Vite dev server.\n *\n * This method sets up the Vite dev server and the index.html file for the development server.\n *\n * @returns\n */\n async setup() {\n const {\n createServer: createViteServer,\n loadConfigFromFile,\n mergeConfig,\n } = await import(\"vite\");\n const react = await import(\"@vitejs/plugin-react\");\n\n const clientRoot = this.findClientRoot();\n\n const loadedConfig = await loadConfigFromFile(\n {\n mode: \"development\",\n command: \"serve\",\n },\n undefined,\n clientRoot,\n );\n\n const userConfig = loadedConfig?.config ?? {};\n const coreConfig = {\n configFile: false,\n root: clientRoot,\n server: {\n middlewareMode: true,\n watch: {\n useFsEvents: true,\n ignored: [\"**/node_modules/**\", \"!**/node_modules/@databricks/**\"],\n },\n },\n plugins: [react.default(), appKitTypesPlugin()],\n appType: \"custom\",\n };\n\n const mergedConfigs = mergeConfigDedup(userConfig, coreConfig, mergeConfig);\n this.vite = await createViteServer(mergedConfigs);\n\n this.app.use(this.vite.middlewares);\n\n this.app.use(\"*\", async (req, res, next) => {\n if (\n req.originalUrl.startsWith(\"/api\") ||\n req.originalUrl.startsWith(\"/query\")\n ) {\n return next();\n }\n const vite = this.vite;\n this.validateVite(vite);\n\n try {\n const indexPath = path.resolve(clientRoot, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n html = await vite.transformIndexHtml(req.originalUrl, html);\n res.status(200).set({ \"Content-Type\": \"text/html\" }).end(html);\n } catch (e) {\n vite.ssrFixStacktrace(e as Error);\n next(e);\n }\n });\n }\n\n /** Close the Vite dev server. */\n async close() {\n await this.vite?.close();\n }\n\n /** Find the client root. */\n private findClientRoot(): string {\n const cwd = process.cwd();\n const candidates = [\"client\", \"src\", \"app\", \"frontend\", \".\"];\n\n for (const dir of candidates) {\n const fullPath = path.resolve(cwd, dir);\n const hasViteConfig =\n fs.existsSync(path.join(fullPath, \"vite.config.ts\")) ||\n fs.existsSync(path.join(fullPath, \"vite.config.js\"));\n const hasIndexHtml = fs.existsSync(path.join(fullPath, \"index.html\"));\n\n if (hasViteConfig && hasIndexHtml) {\n logger.debug(\"Vite dev server: using client root %s\", fullPath);\n return fullPath;\n }\n }\n\n throw ServerError.clientDirectoryNotFound(candidates);\n }\n\n // type assertion to ensure vite is not null\n private validateVite(\n vite: ViteDevServerType | null,\n ): asserts vite is ViteDevServerType {\n if (!vite) {\n throw ServerError.viteNotInitialized();\n }\n }\n}\n"],"mappings":";;;;;;;;;;aAKwC;AAMxC,MAAM,SAAS,aAAa,cAAc;;;;;;;;;;;;;AAc1C,IAAa,gBAAb,cAAmC,WAAW;CAG5C,YAAY,KAA0B,YAA6B,EAAE,EAAE;AACrE,QAAM,KAAK,UAAU;AACrB,OAAK,OAAO;;;;;;;;;CAUd,MAAM,QAAQ;EACZ,MAAM,EACJ,cAAc,kBACd,oBACA,gBACE,MAAM,OAAO;EACjB,MAAM,QAAQ,MAAM,OAAO;EAE3B,MAAM,aAAa,KAAK,gBAAgB;AA2BxC,OAAK,OAAO,MAAM,iBADI,kBAxBD,MAAM,mBACzB;GACE,MAAM;GACN,SAAS;GACV,EACD,QACA,WACD,GAEgC,UAAU,EAAE,EAC1B;GACjB,YAAY;GACZ,MAAM;GACN,QAAQ;IACN,gBAAgB;IAChB,OAAO;KACL,aAAa;KACb,SAAS,CAAC,sBAAsB,kCAAkC;KACnE;IACF;GACD,SAAS,CAAC,MAAM,SAAS,EAAE,mBAAmB,CAAC;GAC/C,SAAS;GACV,EAE8D,YAAY,CAC1B;AAEjD,OAAK,IAAI,IAAI,KAAK,KAAK,YAAY;AAEnC,OAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK,SAAS;AAC1C,OACE,IAAI,YAAY,WAAW,OAAO,IAClC,IAAI,YAAY,WAAW,SAAS,CAEpC,QAAO,MAAM;GAEf,MAAM,OAAO,KAAK;AAClB,QAAK,aAAa,KAAK;AAEvB,OAAI;IACF,MAAM,YAAY,KAAK,QAAQ,YAAY,aAAa;IACxD,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,WAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,WAAO,MAAM,KAAK,mBAAmB,IAAI,aAAa,KAAK;AAC3D,QAAI,OAAO,IAAI,CAAC,IAAI,EAAE,gBAAgB,aAAa,CAAC,CAAC,IAAI,KAAK;YACvD,GAAG;AACV,SAAK,iBAAiB,EAAW;AACjC,SAAK,EAAE;;IAET;;;CAIJ,MAAM,QAAQ;AACZ,QAAM,KAAK,MAAM,OAAO;;;CAI1B,AAAQ,iBAAyB;EAC/B,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,aAAa;GAAC;GAAU;GAAO;GAAO;GAAY;GAAI;AAE5D,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,WAAW,KAAK,QAAQ,KAAK,IAAI;GACvC,MAAM,gBACJ,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC,IACpD,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC;GACtD,MAAM,eAAe,GAAG,WAAW,KAAK,KAAK,UAAU,aAAa,CAAC;AAErE,OAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM,yCAAyC,SAAS;AAC/D,WAAO;;;AAIX,QAAM,YAAY,wBAAwB,WAAW;;CAIvD,AAAQ,aACN,MACmC;AACnC,MAAI,CAAC,KACH,OAAM,YAAY,oBAAoB"}
1
+ {"version":3,"file":"vite-dev-server.js","names":[],"sources":["../../src/server/vite-dev-server.ts"],"sourcesContent":["import fs from \"node:fs\";\nimport path from \"node:path\";\nimport type express from \"express\";\nimport type { ViteDevServer as ViteDevServerType } from \"vite\";\nimport { mergeConfigDedup } from \"@/utils\";\nimport { ServerError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\nimport { appKitTypesPlugin } from \"../type-generator/vite-plugin\";\nimport { BaseServer } from \"./base-server\";\nimport type { PluginEndpoints } from \"./utils\";\n\nconst logger = createLogger(\"server:vite\");\n\n/**\n * Vite dev server for the AppKit.\n *\n * This class is responsible for serving the Vite dev server for the development server.\n * It also handles the index.html file for the development server.\n *\n * @example\n * ```ts\n * const viteDevServer = new ViteDevServer(app, endpoints);\n * await viteDevServer.setup();\n * ```\n */\nexport class ViteDevServer extends BaseServer {\n private vite: ViteDevServerType | null;\n\n constructor(app: express.Application, endpoints: PluginEndpoints = {}) {\n super(app, endpoints);\n this.vite = null;\n }\n\n /**\n * Setup the Vite dev server.\n *\n * This method sets up the Vite dev server and the index.html file for the development server.\n *\n * @returns\n */\n async setup() {\n const {\n createServer: createViteServer,\n loadConfigFromFile,\n mergeConfig,\n } = await import(\"vite\");\n const react = await import(\"@vitejs/plugin-react\");\n\n const clientRoot = this.findClientRoot();\n\n const loadedConfig = await loadConfigFromFile(\n {\n mode: \"development\",\n command: \"serve\",\n },\n undefined,\n clientRoot,\n );\n\n const userConfig = loadedConfig?.config ?? {};\n const coreConfig = {\n configFile: false,\n root: clientRoot,\n server: {\n middlewareMode: true,\n watch: {\n useFsEvents: true,\n ignored: [\"**/node_modules/**\", \"!**/node_modules/@databricks/**\"],\n },\n },\n plugins: [react.default(), appKitTypesPlugin()],\n appType: \"custom\",\n };\n\n const mergedConfigs = mergeConfigDedup(userConfig, coreConfig, mergeConfig);\n this.vite = await createViteServer(mergedConfigs);\n\n this.app.use(this.vite.middlewares);\n\n this.app.use(\"*\", async (req, res, next) => {\n if (\n req.originalUrl.startsWith(\"/api\") ||\n req.originalUrl.startsWith(\"/query\")\n ) {\n return next();\n }\n const vite = this.vite;\n this.validateVite(vite);\n\n try {\n const indexPath = path.resolve(clientRoot, \"index.html\");\n let html = fs.readFileSync(indexPath, \"utf-8\");\n html = html.replace(\"<body>\", `<body>${this.getConfigScript()}`);\n html = await vite.transformIndexHtml(req.originalUrl, html);\n res.status(200).set({ \"Content-Type\": \"text/html\" }).end(html);\n } catch (e) {\n vite.ssrFixStacktrace(e as Error);\n next(e);\n }\n });\n }\n\n /** Close the Vite dev server. */\n async close() {\n await this.vite?.close();\n }\n\n /** Find the client root. */\n private findClientRoot(): string {\n const cwd = process.cwd();\n const candidates = [\"client\", \"src\", \"app\", \"frontend\", \".\"];\n\n for (const dir of candidates) {\n const fullPath = path.resolve(cwd, dir);\n const hasViteConfig =\n fs.existsSync(path.join(fullPath, \"vite.config.ts\")) ||\n fs.existsSync(path.join(fullPath, \"vite.config.js\"));\n const hasIndexHtml = fs.existsSync(path.join(fullPath, \"index.html\"));\n\n if (hasViteConfig && hasIndexHtml) {\n logger.debug(\"Vite dev server: using client root %s\", fullPath);\n return fullPath;\n }\n }\n\n throw ServerError.clientDirectoryNotFound(candidates);\n }\n\n // type assertion to ensure vite is not null\n private validateVite(\n vite: ViteDevServerType | null,\n ): asserts vite is ViteDevServerType {\n if (!vite) {\n throw ServerError.viteNotInitialized();\n }\n }\n}\n"],"mappings":";;;;;;;;;;aAKwC;AAMxC,MAAM,SAAS,aAAa,cAAc;;;;;;;;;;;;;AAc1C,IAAa,gBAAb,cAAmC,WAAW;CAC5C,AAAQ;CAER,YAAY,KAA0B,YAA6B,EAAE,EAAE;AACrE,QAAM,KAAK,UAAU;AACrB,OAAK,OAAO;;;;;;;;;CAUd,MAAM,QAAQ;EACZ,MAAM,EACJ,cAAc,kBACd,oBACA,gBACE,MAAM,OAAO;EACjB,MAAM,QAAQ,MAAM,OAAO;EAE3B,MAAM,aAAa,KAAK,gBAAgB;AA2BxC,OAAK,OAAO,MAAM,iBADI,kBAxBD,MAAM,mBACzB;GACE,MAAM;GACN,SAAS;GACV,EACD,QACA,WACD,GAEgC,UAAU,EAAE,EAC1B;GACjB,YAAY;GACZ,MAAM;GACN,QAAQ;IACN,gBAAgB;IAChB,OAAO;KACL,aAAa;KACb,SAAS,CAAC,sBAAsB,kCAAkC;KACnE;IACF;GACD,SAAS,CAAC,MAAM,SAAS,EAAE,mBAAmB,CAAC;GAC/C,SAAS;GACV,EAE8D,YAAY,CAC1B;AAEjD,OAAK,IAAI,IAAI,KAAK,KAAK,YAAY;AAEnC,OAAK,IAAI,IAAI,KAAK,OAAO,KAAK,KAAK,SAAS;AAC1C,OACE,IAAI,YAAY,WAAW,OAAO,IAClC,IAAI,YAAY,WAAW,SAAS,CAEpC,QAAO,MAAM;GAEf,MAAM,OAAO,KAAK;AAClB,QAAK,aAAa,KAAK;AAEvB,OAAI;IACF,MAAM,YAAY,KAAK,QAAQ,YAAY,aAAa;IACxD,IAAI,OAAO,GAAG,aAAa,WAAW,QAAQ;AAC9C,WAAO,KAAK,QAAQ,UAAU,SAAS,KAAK,iBAAiB,GAAG;AAChE,WAAO,MAAM,KAAK,mBAAmB,IAAI,aAAa,KAAK;AAC3D,QAAI,OAAO,IAAI,CAAC,IAAI,EAAE,gBAAgB,aAAa,CAAC,CAAC,IAAI,KAAK;YACvD,GAAG;AACV,SAAK,iBAAiB,EAAW;AACjC,SAAK,EAAE;;IAET;;;CAIJ,MAAM,QAAQ;AACZ,QAAM,KAAK,MAAM,OAAO;;;CAI1B,AAAQ,iBAAyB;EAC/B,MAAM,MAAM,QAAQ,KAAK;EACzB,MAAM,aAAa;GAAC;GAAU;GAAO;GAAO;GAAY;GAAI;AAE5D,OAAK,MAAM,OAAO,YAAY;GAC5B,MAAM,WAAW,KAAK,QAAQ,KAAK,IAAI;GACvC,MAAM,gBACJ,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC,IACpD,GAAG,WAAW,KAAK,KAAK,UAAU,iBAAiB,CAAC;GACtD,MAAM,eAAe,GAAG,WAAW,KAAK,KAAK,UAAU,aAAa,CAAC;AAErE,OAAI,iBAAiB,cAAc;AACjC,WAAO,MAAM,yCAAyC,SAAS;AAC/D,WAAO;;;AAIX,QAAM,YAAY,wBAAwB,WAAW;;CAIvD,AAAQ,aACN,MACmC;AACnC,MAAI,CAAC,KACH,OAAM,YAAY,oBAAoB"}
@@ -9,6 +9,7 @@ interface BasePlugin {
9
9
  setup(): Promise<void>;
10
10
  injectRoutes(router: express.Router): void;
11
11
  getEndpoints(): PluginEndpointMap;
12
+ exports?(): unknown;
12
13
  }
13
14
  /** Base configuration interface for AppKit plugins */
14
15
  interface BasePluginConfig {
@@ -27,7 +28,30 @@ type PluginConstructor<C = BasePluginConfig, I extends BasePlugin = BasePlugin>
27
28
  DEFAULT_CONFIG?: Record<string, unknown>;
28
29
  phase?: PluginPhase;
29
30
  };
30
- type PluginMap<U extends readonly PluginData<PluginConstructor, unknown, string>[]> = { [P in U[number] as P["name"]]: InstanceType<P["plugin"]> };
31
+ /**
32
+ * Extracts the exports type from a plugin.
33
+ * This is the return type of the plugin's exports() method.
34
+ * If the plugin doesn't implement exports(), returns an empty object type.
35
+ */
36
+ type PluginExports<T extends BasePlugin> = T["exports"] extends (() => infer R) ? R : Record<string, never>;
37
+ /**
38
+ * Wraps an SDK with the `asUser` method that AppKit automatically adds.
39
+ * When `asUser(req)` is called, it returns the same SDK but scoped to the user's credentials.
40
+ */
41
+ type WithAsUser<SDK> = SDK & {
42
+ /**
43
+ * Execute operations using the user's identity from the request.
44
+ * Returns a user-scoped SDK where all methods execute with the
45
+ * user's Databricks credentials instead of the service principal.
46
+ */
47
+ asUser: (req: IAppRequest) => SDK;
48
+ };
49
+ /**
50
+ * Maps plugin names to their exported types (with asUser automatically added).
51
+ * Each plugin exposes its public API via the exports() method,
52
+ * and AppKit wraps it with asUser() for user-scoped execution.
53
+ */
54
+ type PluginMap<U extends readonly PluginData<PluginConstructor, unknown, string>[]> = { [P in U[number] as P["name"]]: WithAsUser<PluginExports<InstanceType<P["plugin"]>>> };
31
55
  type PluginData<T, U, N> = {
32
56
  plugin: T;
33
57
  config: U;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,UAAA;EAAA,IAAA,EAAA,MAAA;EAAU,qBAAA,GAAA,EAAA,IAAA;aAOhB,EAAA,EAAA,IAAA;OAEY,EAAA,EAFZ,OAEoB,CAAA,IAAA,CAAA;cAEb,CAAA,MAAA,EAFK,OAAA,CAAQ,MAEb,CAAA,EAAA,IAAA;EAAiB,YAAA,EAAA,EAAjB,iBAAiB;AAInC;AAaA;AAaY,UA1BK,gBAAA,CA0BM;EAEX,IAAA,CAAA,EAAA,MAAA;EAAiB,IAAA,CAAA,EAAA,MAAA;MACvB,EAAA,MAAA,CAAA,EAAA,OAAA;WACM,CAAA,EApBE,gBAoBF;;AAEF,KAnBE,gBAAA,GAmBF,OAAA,GAAA;QACL,CAAA,EAAA,OAAA;SACc,CAAA,EAAA,OAAA;MACT,CAAA,EAAA,OAAA;CAAW;AAkCb,KA3CI,WAAA,GA2CJ,MAAA,GAAA,QAAA,GAAA,UAAA;AAAa,KAzCT,iBAyCS,CAAA,IAxCf,gBAwCe,EAAA,UAvCT,UAuCS,GAvCI,UAuCJ,CAAA,GAAA,CAAA,KAAA,MAAA,EArCX,CAqCW,EAAA,GApChB,CAoCgB,CAAA,GAAA;gBAAyB,CAAA,EAnC3B,MAmC2B,CAAA,MAAA,EAAA,OAAA,CAAA;OAAb,CAAA,EAlCvB,WAkCuB;CAAY;AAajC,KAhBA,SAgBU,CAAA,UAAA,SAfD,UAeC,CAfU,iBAeV,EAAA,OAAA,EAAA,MAAA,CAAA,EAAA,CAAA,GAAA,QAbd,CAeI,CAAA,MAAW,CAAA,IAfF,CAeE,CAAA,MAAA,CAAA,GAfU,YAeV,CAfuB,CAevB,CAAA,QAAA,CAAA,CAAA,EAAA;AAGb,KAfE,UAeF,CAAA,CAAA,EAAA,CAAA,EAAA,CAAA,CAAA,GAAA;QAEO,EAjB2B,CAiB3B;QAAkB,EAjBoB,CAiBpB;MAAiB,EAjBY,CAiBZ;CAAO;AAI/C,KApBA,QAoBA,CAAA,CAAA,EAAiB,CAAA,EAAA,UAAG,MAAM,CAAA,GAAA,CAAA,MAAA,CAAA,EAnB3B,CAmB2B,EAAA,GAlBjC,UAkBiC,CAlBtB,CAkBsB,EAlBnB,CAkBmB,EAlBhB,CAkBgB,CAAA;;KAf1B,UAAA,GAAa,OAAA,CAAQ;KACrB,YAAA,GAAe,OAAA,CAAQ;KACvB,WAAA,GAAc,OAAA,CAAQ;KAEtB,UAAA;KAEA,WAAA;;;UAGF;;iBAEO,kBAAkB,iBAAiB;;;KAIxC,iBAAA,GAAoB"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../../../shared/src/plugin.ts"],"sourcesContent":[],"mappings":";;;;UAGiB,UAAA;EAAA,IAAA,EAAA,MAAA;EAAU,qBAAA,GAAA,EAAA,IAAA;aAOhB,EAAA,EAAA,IAAA;OAEY,EAAA,EAFZ,OAEoB,CAAA,IAAA,CAAA;cAEb,CAAA,MAAA,EAFK,OAAA,CAAQ,MAEb,CAAA,EAAA,IAAA;EAAiB,YAAA,EAAA,EAAjB,iBAAiB;EAMlB,OAAA,GAAA,EAAA,OAAgB;AAajC;AAaA;AAEY,UA5BK,gBAAA,CA4BY;EAAA,IAAA,CAAA,EAAA,MAAA;MACvB,CAAA,EAAA,MAAA;MACM,EAAA,MAAA,CAAA,EAAA,OAAA;WAAa,CAAA,EApBX,gBAoBW;;AAGpB,KApBO,gBAAA,GAoBP,OAAA,GAAA;QACc,CAAA,EAAA,OAAA;SACT,CAAA,EAAA,OAAA;EAAW,IAAA,CAAA,EAAA,OAAA;AAoCrB,CAAA;AAC2C,KA9C/B,WAAA,GA8C+B,MAAA,GAAA,QAAA,GAAA,UAAA;AAAM,KA5CrC,iBA4CqC,CAAA,IA3C3C,gBA2C2C,EAAA,UA1CrC,UA0CqC,GA1CxB,UA0CwB,CAAA,GAAA,CAAA,KAAA,MAAA,EAxCvC,CAwCuC,EAAA,GAvC5C,CAuC4C,CAAA,GAAA;EAMrC,cAAU,CAAA,EA5CH,MA4CG,CAAA,MAAA,EAAA,OAAA,CAAA;EAAA,KAAA,CAAA,EA3CZ,WA2CY;;;;;AAuBtB;;AACW,KA/BC,aA+BD,CAAA,UA/ByB,UA+BzB,CAAA,GA9BT,CA8BS,CAAA,SAAA,CAAA,UAAA,GAAA,GAAA,KAAA,EAAA,IA9B4B,CA8B5B,GA9BgC,MA8BhC,CAAA,MAAA,EAAA,KAAA,CAAA;;;;;AACI,KAzBH,UAyBG,CAAA,GAAA,CAAA,GAzBe,GAyBf,GAAA;EAGH;AACZ;AACA;AAEA;AAEA;EAAuB,MAAA,EAAA,CAAA,GAAA,EA5BP,WA4BO,EAAA,GA5BS,GA4BT;;;;;;AASvB;KA7BY,6BACS,WAAW,iDAExB,aAAa,YAAY,WAC7B,cAAc,aAAa;KAInB;UAAgC;UAAW;QAAS;;KACpD,6CACD,MACN,WAAW,GAAG,GAAG;;KAGV,UAAA,GAAa,OAAA,CAAQ;KACrB,YAAA,GAAe,OAAA,CAAQ;KACvB,WAAA,GAAc,OAAA,CAAQ;KAEtB,UAAA;KAEA,WAAA;;;UAGF;;iBAEO,kBAAkB,iBAAiB;;;KAIxC,iBAAA,GAAoB"}
@@ -8,15 +8,9 @@ init_errors();
8
8
  const logger = createLogger("stream:arrow");
9
9
  const BACKOFF_MULTIPLIER = 1e3;
10
10
  var ArrowStreamProcessor = class ArrowStreamProcessor {
11
- static {
12
- this.DEFAULT_MAX_CONCURRENT_DOWNLOADS = 5;
13
- }
14
- static {
15
- this.DEFAULT_TIMEOUT = 3e4;
16
- }
17
- static {
18
- this.DEFAULT_RETRIES = 3;
19
- }
11
+ static DEFAULT_MAX_CONCURRENT_DOWNLOADS = 5;
12
+ static DEFAULT_TIMEOUT = 3e4;
13
+ static DEFAULT_RETRIES = 3;
20
14
  constructor(options = {
21
15
  maxConcurrentDownloads: ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,
22
16
  timeout: ArrowStreamProcessor.DEFAULT_TIMEOUT,
@@ -135,8 +129,9 @@ var ArrowStreamProcessor = class ArrowStreamProcessor {
135
129
  }
136
130
  };
137
131
  var Semaphore = class {
132
+ permits;
133
+ waiting = [];
138
134
  constructor(permits) {
139
- this.waiting = [];
140
135
  this.permits = permits;
141
136
  }
142
137
  async acquire() {
@@ -1 +1 @@
1
- {"version":3,"file":"arrow-stream-processor.js","names":[],"sources":["../../src/stream/arrow-stream-processor.ts"],"sourcesContent":["import type { sql } from \"@databricks/sdk-experimental\";\nimport { ExecutionError, ValidationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"stream:arrow\");\n\ntype ResultManifest = sql.ResultManifest;\ntype ExternalLink = sql.ExternalLink;\n\nexport interface ArrowStreamOptions {\n maxConcurrentDownloads: number;\n timeout: number;\n retries: number;\n}\n\n/**\n * Result from zero-copy Arrow chunk processing.\n * Contains raw IPC bytes without server-side parsing.\n */\nexport interface ArrowRawResult {\n /** Concatenated raw Arrow IPC bytes */\n data: Uint8Array;\n /** Schema from Databricks manifest (not parsed from Arrow) */\n schema: ResultManifest[\"schema\"];\n}\n\nconst BACKOFF_MULTIPLIER = 1000;\n\nexport class ArrowStreamProcessor {\n static readonly DEFAULT_MAX_CONCURRENT_DOWNLOADS = 5;\n static readonly DEFAULT_TIMEOUT = 30000;\n static readonly DEFAULT_RETRIES = 3;\n\n constructor(\n private options: ArrowStreamOptions = {\n maxConcurrentDownloads:\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n timeout: ArrowStreamProcessor.DEFAULT_TIMEOUT,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n },\n ) {\n this.options = {\n maxConcurrentDownloads:\n options.maxConcurrentDownloads ??\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n timeout: options.timeout ?? ArrowStreamProcessor.DEFAULT_TIMEOUT,\n retries: options.retries ?? ArrowStreamProcessor.DEFAULT_RETRIES,\n };\n }\n\n /**\n * Process Arrow chunks using zero-copy proxy pattern.\n *\n * Downloads raw IPC bytes from external links and concatenates them\n * without parsing into Arrow Tables on the server. This reduces:\n * - Memory usage by ~50% (no parsed Table representation)\n * - CPU usage (no tableFromIPC/tableToIPC calls)\n *\n * The client is responsible for parsing the IPC bytes.\n *\n * @param chunks - External links to Arrow IPC data\n * @param schema - Schema from Databricks manifest\n * @param signal - Optional abort signal\n * @returns Raw concatenated IPC bytes with schema\n */\n async processChunks(\n chunks: ExternalLink[],\n schema: ResultManifest[\"schema\"],\n signal?: AbortSignal,\n ): Promise<ArrowRawResult> {\n if (chunks.length === 0) {\n throw ValidationError.missingField(\"chunks\");\n }\n\n const buffers = await this.downloadChunksRaw(chunks, signal);\n const data = this.concatenateBuffers(buffers);\n\n return { data, schema };\n }\n\n /**\n * Download all chunks as raw bytes with concurrency control.\n */\n private async downloadChunksRaw(\n chunks: ExternalLink[],\n signal?: AbortSignal,\n ): Promise<Uint8Array[]> {\n const semaphore = new Semaphore(this.options.maxConcurrentDownloads);\n\n const downloadPromises = chunks.map(async (chunk) => {\n await semaphore.acquire();\n try {\n return await this.downloadChunkRaw(chunk, signal);\n } finally {\n semaphore.release();\n }\n });\n\n return Promise.all(downloadPromises);\n }\n\n /**\n * Download a single chunk as raw bytes with retry logic.\n */\n private async downloadChunkRaw(\n chunk: ExternalLink,\n signal?: AbortSignal,\n ): Promise<Uint8Array> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < this.options.retries; attempt++) {\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => {\n timeoutController.abort();\n }, this.options.timeout);\n\n const combinedSignal = signal\n ? this.combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const externalLink = chunk.external_link;\n if (!externalLink) {\n logger.error(\"External link is required for chunk: %O\", chunk);\n continue;\n }\n\n const response = await fetch(externalLink, {\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw ExecutionError.statementFailed(\n `Failed to download chunk ${chunk.chunk_index}: ${response.status} ${response.statusText}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return new Uint8Array(arrayBuffer);\n } catch (error) {\n lastError = error as Error;\n\n if (timeoutController.signal.aborted) {\n lastError = new Error(\n `Chunk ${chunk.chunk_index} download timed out after ${this.options.timeout}ms`,\n );\n }\n\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n if (attempt < this.options.retries - 1) {\n await this.delay(2 ** attempt * BACKOFF_MULTIPLIER);\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n throw ExecutionError.statementFailed(\n `Failed to download chunk ${chunk.chunk_index} after ${this.options.retries} attempts: ${lastError?.message}`,\n );\n }\n\n /**\n * Concatenate multiple Uint8Array buffers into a single buffer.\n * Pre-allocates the result array for efficiency.\n */\n private concatenateBuffers(buffers: Uint8Array[]): Uint8Array {\n if (buffers.length === 0) {\n throw ValidationError.missingField(\"buffers\");\n }\n\n if (buffers.length === 1) {\n return buffers[0];\n }\n\n const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);\n const result = new Uint8Array(totalLength);\n\n let offset = 0;\n for (const buffer of buffers) {\n result.set(buffer, offset);\n offset += buffer.length;\n }\n\n return result;\n }\n\n /**\n * Combines multiple AbortSignals into one.\n * The combined signal aborts when any of the input signals abort.\n */\n private combineAbortSignals(...signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort();\n return controller.signal;\n }\n signal.addEventListener(\"abort\", () => controller.abort(), {\n once: true,\n });\n }\n\n return controller.signal;\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nclass Semaphore {\n private permits: number;\n private waiting: (() => void)[] = [];\n\n constructor(permits: number) {\n this.permits = permits;\n }\n\n async acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--;\n return;\n }\n\n return new Promise<void>((resolve) => {\n this.waiting.push(resolve);\n });\n }\n\n release(): void {\n if (this.waiting.length > 0) {\n const next = this.waiting.shift();\n\n if (next) {\n next();\n }\n } else {\n this.permits++;\n }\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"arrow-stream-processor.js","names":[],"sources":["../../src/stream/arrow-stream-processor.ts"],"sourcesContent":["import type { sql } from \"@databricks/sdk-experimental\";\nimport { ExecutionError, ValidationError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"stream:arrow\");\n\ntype ResultManifest = sql.ResultManifest;\ntype ExternalLink = sql.ExternalLink;\n\nexport interface ArrowStreamOptions {\n maxConcurrentDownloads: number;\n timeout: number;\n retries: number;\n}\n\n/**\n * Result from zero-copy Arrow chunk processing.\n * Contains raw IPC bytes without server-side parsing.\n */\nexport interface ArrowRawResult {\n /** Concatenated raw Arrow IPC bytes */\n data: Uint8Array;\n /** Schema from Databricks manifest (not parsed from Arrow) */\n schema: ResultManifest[\"schema\"];\n}\n\nconst BACKOFF_MULTIPLIER = 1000;\n\nexport class ArrowStreamProcessor {\n static readonly DEFAULT_MAX_CONCURRENT_DOWNLOADS = 5;\n static readonly DEFAULT_TIMEOUT = 30000;\n static readonly DEFAULT_RETRIES = 3;\n\n constructor(\n private options: ArrowStreamOptions = {\n maxConcurrentDownloads:\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n timeout: ArrowStreamProcessor.DEFAULT_TIMEOUT,\n retries: ArrowStreamProcessor.DEFAULT_RETRIES,\n },\n ) {\n this.options = {\n maxConcurrentDownloads:\n options.maxConcurrentDownloads ??\n ArrowStreamProcessor.DEFAULT_MAX_CONCURRENT_DOWNLOADS,\n timeout: options.timeout ?? ArrowStreamProcessor.DEFAULT_TIMEOUT,\n retries: options.retries ?? ArrowStreamProcessor.DEFAULT_RETRIES,\n };\n }\n\n /**\n * Process Arrow chunks using zero-copy proxy pattern.\n *\n * Downloads raw IPC bytes from external links and concatenates them\n * without parsing into Arrow Tables on the server. This reduces:\n * - Memory usage by ~50% (no parsed Table representation)\n * - CPU usage (no tableFromIPC/tableToIPC calls)\n *\n * The client is responsible for parsing the IPC bytes.\n *\n * @param chunks - External links to Arrow IPC data\n * @param schema - Schema from Databricks manifest\n * @param signal - Optional abort signal\n * @returns Raw concatenated IPC bytes with schema\n */\n async processChunks(\n chunks: ExternalLink[],\n schema: ResultManifest[\"schema\"],\n signal?: AbortSignal,\n ): Promise<ArrowRawResult> {\n if (chunks.length === 0) {\n throw ValidationError.missingField(\"chunks\");\n }\n\n const buffers = await this.downloadChunksRaw(chunks, signal);\n const data = this.concatenateBuffers(buffers);\n\n return { data, schema };\n }\n\n /**\n * Download all chunks as raw bytes with concurrency control.\n */\n private async downloadChunksRaw(\n chunks: ExternalLink[],\n signal?: AbortSignal,\n ): Promise<Uint8Array[]> {\n const semaphore = new Semaphore(this.options.maxConcurrentDownloads);\n\n const downloadPromises = chunks.map(async (chunk) => {\n await semaphore.acquire();\n try {\n return await this.downloadChunkRaw(chunk, signal);\n } finally {\n semaphore.release();\n }\n });\n\n return Promise.all(downloadPromises);\n }\n\n /**\n * Download a single chunk as raw bytes with retry logic.\n */\n private async downloadChunkRaw(\n chunk: ExternalLink,\n signal?: AbortSignal,\n ): Promise<Uint8Array> {\n let lastError: Error | null = null;\n\n for (let attempt = 0; attempt < this.options.retries; attempt++) {\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => {\n timeoutController.abort();\n }, this.options.timeout);\n\n const combinedSignal = signal\n ? this.combineAbortSignals(signal, timeoutController.signal)\n : timeoutController.signal;\n\n try {\n const externalLink = chunk.external_link;\n if (!externalLink) {\n logger.error(\"External link is required for chunk: %O\", chunk);\n continue;\n }\n\n const response = await fetch(externalLink, {\n signal: combinedSignal,\n });\n\n if (!response.ok) {\n throw ExecutionError.statementFailed(\n `Failed to download chunk ${chunk.chunk_index}: ${response.status} ${response.statusText}`,\n );\n }\n\n const arrayBuffer = await response.arrayBuffer();\n return new Uint8Array(arrayBuffer);\n } catch (error) {\n lastError = error as Error;\n\n if (timeoutController.signal.aborted) {\n lastError = new Error(\n `Chunk ${chunk.chunk_index} download timed out after ${this.options.timeout}ms`,\n );\n }\n\n if (signal?.aborted) {\n throw ExecutionError.canceled();\n }\n\n if (attempt < this.options.retries - 1) {\n await this.delay(2 ** attempt * BACKOFF_MULTIPLIER);\n }\n } finally {\n clearTimeout(timeoutId);\n }\n }\n\n throw ExecutionError.statementFailed(\n `Failed to download chunk ${chunk.chunk_index} after ${this.options.retries} attempts: ${lastError?.message}`,\n );\n }\n\n /**\n * Concatenate multiple Uint8Array buffers into a single buffer.\n * Pre-allocates the result array for efficiency.\n */\n private concatenateBuffers(buffers: Uint8Array[]): Uint8Array {\n if (buffers.length === 0) {\n throw ValidationError.missingField(\"buffers\");\n }\n\n if (buffers.length === 1) {\n return buffers[0];\n }\n\n const totalLength = buffers.reduce((sum, buf) => sum + buf.length, 0);\n const result = new Uint8Array(totalLength);\n\n let offset = 0;\n for (const buffer of buffers) {\n result.set(buffer, offset);\n offset += buffer.length;\n }\n\n return result;\n }\n\n /**\n * Combines multiple AbortSignals into one.\n * The combined signal aborts when any of the input signals abort.\n */\n private combineAbortSignals(...signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort();\n return controller.signal;\n }\n signal.addEventListener(\"abort\", () => controller.abort(), {\n once: true,\n });\n }\n\n return controller.signal;\n }\n\n private delay(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n\nclass Semaphore {\n private permits: number;\n private waiting: (() => void)[] = [];\n\n constructor(permits: number) {\n this.permits = permits;\n }\n\n async acquire(): Promise<void> {\n if (this.permits > 0) {\n this.permits--;\n return;\n }\n\n return new Promise<void>((resolve) => {\n this.waiting.push(resolve);\n });\n }\n\n release(): void {\n if (this.waiting.length > 0) {\n const next = this.waiting.shift();\n\n if (next) {\n next();\n }\n } else {\n this.permits++;\n }\n }\n}\n"],"mappings":"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"}
@@ -4,6 +4,12 @@ import { init_errors } from "../errors/index.js";
4
4
  //#region src/stream/buffers.ts
5
5
  init_errors();
6
6
  var RingBuffer = class {
7
+ buffer;
8
+ capacity;
9
+ writeIndex;
10
+ size;
11
+ keyExtractor;
12
+ keyIndex;
7
13
  constructor(capacity, keyExtractor) {
8
14
  if (capacity <= 0) throw ValidationError.invalidValue("capacity", capacity, "greater than 0");
9
15
  this.capacity = capacity;
@@ -65,6 +71,7 @@ var RingBuffer = class {
65
71
  }
66
72
  };
67
73
  var EventRingBuffer = class {
74
+ buffer;
68
75
  constructor(capacity = 100) {
69
76
  this.buffer = new RingBuffer(capacity, (event) => event.id);
70
77
  }
@@ -1 +1 @@
1
- {"version":3,"file":"buffers.js","names":[],"sources":["../../src/stream/buffers.ts"],"sourcesContent":["import { ValidationError } from \"../errors\";\nimport type { BufferedEvent } from \"./types\";\n\n// generic ring buffer implementation\nexport class RingBuffer<T> {\n public buffer: (T | null)[];\n public capacity: number;\n private writeIndex: number;\n private size: number;\n private keyExtractor: (item: T) => string;\n private keyIndex: Map<string, number>;\n\n constructor(capacity: number, keyExtractor: (item: T) => string) {\n if (capacity <= 0) {\n throw ValidationError.invalidValue(\n \"capacity\",\n capacity,\n \"greater than 0\",\n );\n }\n\n this.capacity = capacity;\n this.buffer = new Array(capacity).fill(null);\n this.writeIndex = 0;\n this.size = 0;\n this.keyExtractor = keyExtractor;\n this.keyIndex = new Map();\n }\n\n // add an item to the buffer\n add(item: T): void {\n const key = this.keyExtractor(item);\n\n // check if item already exists\n const existingIndex = this.keyIndex.get(key);\n if (existingIndex !== undefined) {\n // update existing item\n this.buffer[existingIndex] = item;\n return;\n }\n\n // evict least recently used item if at capacity\n const evicted = this.buffer[this.writeIndex];\n if (evicted !== null) {\n const evictedKey = this.keyExtractor(evicted);\n this.keyIndex.delete(evictedKey);\n }\n\n // add new item\n this.buffer[this.writeIndex] = item;\n this.keyIndex.set(key, this.writeIndex);\n\n // update write index and size\n this.writeIndex = (this.writeIndex + 1) % this.capacity;\n this.size = Math.min(this.size + 1, this.capacity);\n }\n\n // get an item from the buffer\n get(key: string): T | null {\n const index = this.keyIndex.get(key);\n if (index === undefined) return null;\n\n return this.buffer[index];\n }\n\n // check if an item exists in the buffer\n has(key: string): boolean {\n return this.keyIndex.has(key);\n }\n\n // remove an item from the buffer\n remove(key: string): void {\n const index = this.keyIndex.get(key);\n if (index === undefined) return;\n\n // remove item from buffer\n this.buffer[index] = null;\n this.keyIndex.delete(key);\n\n // update size\n this.size = Math.max(this.size - 1, 0);\n }\n\n // get all items from the buffer\n getAll(): T[] {\n const result: T[] = [];\n\n // iterate over buffer in order of insertion\n for (let i = 0; i < this.size; i++) {\n // calculate index of item in buffer\n const index =\n (this.writeIndex - this.size + i + this.capacity) % this.capacity;\n // add item to result if not null\n const item = this.buffer[index];\n if (item !== null) {\n result.push(item);\n }\n }\n return result;\n }\n\n // get the size of the buffer\n getSize(): number {\n return this.size;\n }\n\n // clear the buffer\n clear(): void {\n this.buffer = new Array(this.capacity).fill(null);\n this.keyIndex.clear();\n this.writeIndex = 0;\n this.size = 0;\n }\n}\n\n// event ring buffer implementation\nexport class EventRingBuffer {\n private buffer: RingBuffer<BufferedEvent>;\n\n constructor(capacity: number = 100) {\n this.buffer = new RingBuffer<BufferedEvent>(capacity, (event) => event.id);\n }\n\n // add an event to the buffer\n add(event: BufferedEvent): void {\n this.buffer.add(event);\n }\n\n // check if an event exists in the buffer\n has(eventId: string): boolean {\n return this.buffer.has(eventId);\n }\n\n // get all events since a given event id\n getEventsSince(lastEventId: string): BufferedEvent[] {\n const allEvents = this.buffer.getAll();\n const result: BufferedEvent[] = [];\n // flag to track if we've found the last event\n let foundLastEvent = false;\n\n // iterate over all events\n for (const event of allEvents) {\n // if found, add to result\n if (foundLastEvent) {\n result.push(event);\n // if not found, check if it's the last event\n } else if (event.id === lastEventId) {\n foundLastEvent = true;\n }\n }\n return result;\n }\n\n clear(): void {\n this.buffer.clear();\n }\n}\n"],"mappings":";;;;aAA4C;AAI5C,IAAa,aAAb,MAA2B;CAQzB,YAAY,UAAkB,cAAmC;AAC/D,MAAI,YAAY,EACd,OAAM,gBAAgB,aACpB,YACA,UACA,iBACD;AAGH,OAAK,WAAW;AAChB,OAAK,SAAS,IAAI,MAAM,SAAS,CAAC,KAAK,KAAK;AAC5C,OAAK,aAAa;AAClB,OAAK,OAAO;AACZ,OAAK,eAAe;AACpB,OAAK,2BAAW,IAAI,KAAK;;CAI3B,IAAI,MAAe;EACjB,MAAM,MAAM,KAAK,aAAa,KAAK;EAGnC,MAAM,gBAAgB,KAAK,SAAS,IAAI,IAAI;AAC5C,MAAI,kBAAkB,QAAW;AAE/B,QAAK,OAAO,iBAAiB;AAC7B;;EAIF,MAAM,UAAU,KAAK,OAAO,KAAK;AACjC,MAAI,YAAY,MAAM;GACpB,MAAM,aAAa,KAAK,aAAa,QAAQ;AAC7C,QAAK,SAAS,OAAO,WAAW;;AAIlC,OAAK,OAAO,KAAK,cAAc;AAC/B,OAAK,SAAS,IAAI,KAAK,KAAK,WAAW;AAGvC,OAAK,cAAc,KAAK,aAAa,KAAK,KAAK;AAC/C,OAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,SAAS;;CAIpD,IAAI,KAAuB;EACzB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,MAAI,UAAU,OAAW,QAAO;AAEhC,SAAO,KAAK,OAAO;;CAIrB,IAAI,KAAsB;AACxB,SAAO,KAAK,SAAS,IAAI,IAAI;;CAI/B,OAAO,KAAmB;EACxB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,MAAI,UAAU,OAAW;AAGzB,OAAK,OAAO,SAAS;AACrB,OAAK,SAAS,OAAO,IAAI;AAGzB,OAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,EAAE;;CAIxC,SAAc;EACZ,MAAM,SAAc,EAAE;AAGtB,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,MAAM,KAAK;GAElC,MAAM,SACH,KAAK,aAAa,KAAK,OAAO,IAAI,KAAK,YAAY,KAAK;GAE3D,MAAM,OAAO,KAAK,OAAO;AACzB,OAAI,SAAS,KACX,QAAO,KAAK,KAAK;;AAGrB,SAAO;;CAIT,UAAkB;AAChB,SAAO,KAAK;;CAId,QAAc;AACZ,OAAK,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC,KAAK,KAAK;AACjD,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa;AAClB,OAAK,OAAO;;;AAKhB,IAAa,kBAAb,MAA6B;CAG3B,YAAY,WAAmB,KAAK;AAClC,OAAK,SAAS,IAAI,WAA0B,WAAW,UAAU,MAAM,GAAG;;CAI5E,IAAI,OAA4B;AAC9B,OAAK,OAAO,IAAI,MAAM;;CAIxB,IAAI,SAA0B;AAC5B,SAAO,KAAK,OAAO,IAAI,QAAQ;;CAIjC,eAAe,aAAsC;EACnD,MAAM,YAAY,KAAK,OAAO,QAAQ;EACtC,MAAM,SAA0B,EAAE;EAElC,IAAI,iBAAiB;AAGrB,OAAK,MAAM,SAAS,UAElB,KAAI,eACF,QAAO,KAAK,MAAM;WAET,MAAM,OAAO,YACtB,kBAAiB;AAGrB,SAAO;;CAGT,QAAc;AACZ,OAAK,OAAO,OAAO"}
1
+ {"version":3,"file":"buffers.js","names":[],"sources":["../../src/stream/buffers.ts"],"sourcesContent":["import { ValidationError } from \"../errors\";\nimport type { BufferedEvent } from \"./types\";\n\n// generic ring buffer implementation\nexport class RingBuffer<T> {\n public buffer: (T | null)[];\n public capacity: number;\n private writeIndex: number;\n private size: number;\n private keyExtractor: (item: T) => string;\n private keyIndex: Map<string, number>;\n\n constructor(capacity: number, keyExtractor: (item: T) => string) {\n if (capacity <= 0) {\n throw ValidationError.invalidValue(\n \"capacity\",\n capacity,\n \"greater than 0\",\n );\n }\n\n this.capacity = capacity;\n this.buffer = new Array(capacity).fill(null);\n this.writeIndex = 0;\n this.size = 0;\n this.keyExtractor = keyExtractor;\n this.keyIndex = new Map();\n }\n\n // add an item to the buffer\n add(item: T): void {\n const key = this.keyExtractor(item);\n\n // check if item already exists\n const existingIndex = this.keyIndex.get(key);\n if (existingIndex !== undefined) {\n // update existing item\n this.buffer[existingIndex] = item;\n return;\n }\n\n // evict least recently used item if at capacity\n const evicted = this.buffer[this.writeIndex];\n if (evicted !== null) {\n const evictedKey = this.keyExtractor(evicted);\n this.keyIndex.delete(evictedKey);\n }\n\n // add new item\n this.buffer[this.writeIndex] = item;\n this.keyIndex.set(key, this.writeIndex);\n\n // update write index and size\n this.writeIndex = (this.writeIndex + 1) % this.capacity;\n this.size = Math.min(this.size + 1, this.capacity);\n }\n\n // get an item from the buffer\n get(key: string): T | null {\n const index = this.keyIndex.get(key);\n if (index === undefined) return null;\n\n return this.buffer[index];\n }\n\n // check if an item exists in the buffer\n has(key: string): boolean {\n return this.keyIndex.has(key);\n }\n\n // remove an item from the buffer\n remove(key: string): void {\n const index = this.keyIndex.get(key);\n if (index === undefined) return;\n\n // remove item from buffer\n this.buffer[index] = null;\n this.keyIndex.delete(key);\n\n // update size\n this.size = Math.max(this.size - 1, 0);\n }\n\n // get all items from the buffer\n getAll(): T[] {\n const result: T[] = [];\n\n // iterate over buffer in order of insertion\n for (let i = 0; i < this.size; i++) {\n // calculate index of item in buffer\n const index =\n (this.writeIndex - this.size + i + this.capacity) % this.capacity;\n // add item to result if not null\n const item = this.buffer[index];\n if (item !== null) {\n result.push(item);\n }\n }\n return result;\n }\n\n // get the size of the buffer\n getSize(): number {\n return this.size;\n }\n\n // clear the buffer\n clear(): void {\n this.buffer = new Array(this.capacity).fill(null);\n this.keyIndex.clear();\n this.writeIndex = 0;\n this.size = 0;\n }\n}\n\n// event ring buffer implementation\nexport class EventRingBuffer {\n private buffer: RingBuffer<BufferedEvent>;\n\n constructor(capacity: number = 100) {\n this.buffer = new RingBuffer<BufferedEvent>(capacity, (event) => event.id);\n }\n\n // add an event to the buffer\n add(event: BufferedEvent): void {\n this.buffer.add(event);\n }\n\n // check if an event exists in the buffer\n has(eventId: string): boolean {\n return this.buffer.has(eventId);\n }\n\n // get all events since a given event id\n getEventsSince(lastEventId: string): BufferedEvent[] {\n const allEvents = this.buffer.getAll();\n const result: BufferedEvent[] = [];\n // flag to track if we've found the last event\n let foundLastEvent = false;\n\n // iterate over all events\n for (const event of allEvents) {\n // if found, add to result\n if (foundLastEvent) {\n result.push(event);\n // if not found, check if it's the last event\n } else if (event.id === lastEventId) {\n foundLastEvent = true;\n }\n }\n return result;\n }\n\n clear(): void {\n this.buffer.clear();\n }\n}\n"],"mappings":";;;;aAA4C;AAI5C,IAAa,aAAb,MAA2B;CACzB,AAAO;CACP,AAAO;CACP,AAAQ;CACR,AAAQ;CACR,AAAQ;CACR,AAAQ;CAER,YAAY,UAAkB,cAAmC;AAC/D,MAAI,YAAY,EACd,OAAM,gBAAgB,aACpB,YACA,UACA,iBACD;AAGH,OAAK,WAAW;AAChB,OAAK,SAAS,IAAI,MAAM,SAAS,CAAC,KAAK,KAAK;AAC5C,OAAK,aAAa;AAClB,OAAK,OAAO;AACZ,OAAK,eAAe;AACpB,OAAK,2BAAW,IAAI,KAAK;;CAI3B,IAAI,MAAe;EACjB,MAAM,MAAM,KAAK,aAAa,KAAK;EAGnC,MAAM,gBAAgB,KAAK,SAAS,IAAI,IAAI;AAC5C,MAAI,kBAAkB,QAAW;AAE/B,QAAK,OAAO,iBAAiB;AAC7B;;EAIF,MAAM,UAAU,KAAK,OAAO,KAAK;AACjC,MAAI,YAAY,MAAM;GACpB,MAAM,aAAa,KAAK,aAAa,QAAQ;AAC7C,QAAK,SAAS,OAAO,WAAW;;AAIlC,OAAK,OAAO,KAAK,cAAc;AAC/B,OAAK,SAAS,IAAI,KAAK,KAAK,WAAW;AAGvC,OAAK,cAAc,KAAK,aAAa,KAAK,KAAK;AAC/C,OAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,KAAK,SAAS;;CAIpD,IAAI,KAAuB;EACzB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,MAAI,UAAU,OAAW,QAAO;AAEhC,SAAO,KAAK,OAAO;;CAIrB,IAAI,KAAsB;AACxB,SAAO,KAAK,SAAS,IAAI,IAAI;;CAI/B,OAAO,KAAmB;EACxB,MAAM,QAAQ,KAAK,SAAS,IAAI,IAAI;AACpC,MAAI,UAAU,OAAW;AAGzB,OAAK,OAAO,SAAS;AACrB,OAAK,SAAS,OAAO,IAAI;AAGzB,OAAK,OAAO,KAAK,IAAI,KAAK,OAAO,GAAG,EAAE;;CAIxC,SAAc;EACZ,MAAM,SAAc,EAAE;AAGtB,OAAK,IAAI,IAAI,GAAG,IAAI,KAAK,MAAM,KAAK;GAElC,MAAM,SACH,KAAK,aAAa,KAAK,OAAO,IAAI,KAAK,YAAY,KAAK;GAE3D,MAAM,OAAO,KAAK,OAAO;AACzB,OAAI,SAAS,KACX,QAAO,KAAK,KAAK;;AAGrB,SAAO;;CAIT,UAAkB;AAChB,SAAO,KAAK;;CAId,QAAc;AACZ,OAAK,SAAS,IAAI,MAAM,KAAK,SAAS,CAAC,KAAK,KAAK;AACjD,OAAK,SAAS,OAAO;AACrB,OAAK,aAAa;AAClB,OAAK,OAAO;;;AAKhB,IAAa,kBAAb,MAA6B;CAC3B,AAAQ;CAER,YAAY,WAAmB,KAAK;AAClC,OAAK,SAAS,IAAI,WAA0B,WAAW,UAAU,MAAM,GAAG;;CAI5E,IAAI,OAA4B;AAC9B,OAAK,OAAO,IAAI,MAAM;;CAIxB,IAAI,SAA0B;AAC5B,SAAO,KAAK,OAAO,IAAI,QAAQ;;CAIjC,eAAe,aAAsC;EACnD,MAAM,YAAY,KAAK,OAAO,QAAQ;EACtC,MAAM,SAA0B,EAAE;EAElC,IAAI,iBAAiB;AAGrB,OAAK,MAAM,SAAS,UAElB,KAAI,eACF,QAAO,KAAK,MAAM;WAET,MAAM,OAAO,YACtB,kBAAiB;AAGrB,SAAO;;CAGT,QAAc;AACZ,OAAK,OAAO,OAAO"}
@@ -9,6 +9,11 @@ import { context } from "@opentelemetry/api";
9
9
 
10
10
  //#region src/stream/stream-manager.ts
11
11
  var StreamManager = class {
12
+ activeOperations;
13
+ streamRegistry;
14
+ sseWriter;
15
+ maxEventSize;
16
+ bufferTTL;
12
17
  constructor(options) {
13
18
  this.streamRegistry = new StreamRegistry(options?.maxActiveStreams ?? streamDefaults.maxActiveStreams);
14
19
  this.sseWriter = new SSEWriter();
@@ -1 +1 @@
1
- {"version":3,"file":"stream-manager.js","names":[],"sources":["../../src/stream/stream-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { context } from \"@opentelemetry/api\";\nimport type { IAppResponse, StreamConfig } from \"shared\";\nimport { EventRingBuffer } from \"./buffers\";\nimport { streamDefaults } from \"./defaults\";\nimport { SSEWriter } from \"./sse-writer\";\nimport { StreamRegistry } from \"./stream-registry\";\nimport { SSEErrorCode, type StreamEntry, type StreamOperation } from \"./types\";\nimport { StreamValidator } from \"./validator\";\n\n// main entry point for Server-Sent events streaming\nexport class StreamManager {\n private activeOperations: Set<StreamOperation>;\n private streamRegistry: StreamRegistry;\n private sseWriter: SSEWriter;\n private maxEventSize: number;\n private bufferTTL: number;\n\n constructor(options?: StreamConfig) {\n this.streamRegistry = new StreamRegistry(\n options?.maxActiveStreams ?? streamDefaults.maxActiveStreams,\n );\n this.sseWriter = new SSEWriter();\n this.maxEventSize = options?.maxEventSize ?? streamDefaults.maxEventSize;\n this.bufferTTL = options?.bufferTTL ?? streamDefaults.bufferTTL;\n this.activeOperations = new Set();\n }\n\n // main streaming method - handles new connection and reconnection\n async stream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const { streamId } = options || {};\n\n // check if response is already closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n // setup SSE headers\n this.sseWriter.setupHeaders(res);\n\n // handle reconnection\n if (streamId && StreamValidator.validateStreamId(streamId)) {\n const existingStream = this.streamRegistry.get(streamId);\n // if stream exists, attach to it\n if (existingStream) {\n return this._attachToExistingStream(res, existingStream, options);\n }\n }\n\n // if stream does not exist, create a new one\n return this._createNewStream(res, handler, options);\n }\n\n // abort all active operations\n abortAll(): void {\n this.activeOperations.forEach((operation) => {\n if (operation.heartbeat) clearInterval(operation.heartbeat);\n operation.controller.abort(\"Server shutdown\");\n });\n this.activeOperations.clear();\n this.streamRegistry.clear();\n }\n\n // get the number of active operations\n getActiveCount(): number {\n return this.activeOperations.size;\n }\n\n // attach to existing stream\n private async _attachToExistingStream(\n res: IAppResponse,\n streamEntry: StreamEntry,\n options?: StreamConfig,\n ): Promise<void> {\n // handle reconnection - replay missed events\n const lastEventId = res.req?.headers[\"last-event-id\"];\n\n if (StreamValidator.validateEventId(lastEventId)) {\n // cast to string after validation\n const validEventId = lastEventId as string;\n if (streamEntry.eventBuffer.has(validEventId)) {\n const missedEvents =\n streamEntry.eventBuffer.getEventsSince(validEventId);\n // broadcast missed events to client\n for (const event of missedEvents) {\n if (options?.userSignal?.aborted) break;\n this.sseWriter.writeBufferedEvent(res, event);\n }\n } else {\n // buffer overflow - send warning\n this.sseWriter.writeBufferOverflowWarning(res, validEventId);\n }\n }\n\n // add client to stream entry\n streamEntry.clients.add(res);\n streamEntry.lastAccess = Date.now();\n\n // start heartbeat\n const combinedSignal = this._combineSignals(\n streamEntry.abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: streamEntry.abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n // handle client disconnect\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n streamEntry.clients.delete(res);\n this.activeOperations.delete(streamOperation);\n\n // cleanup if stream is completed and no clients are connected\n if (streamEntry.isCompleted && streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n });\n\n // if stream is completed, close connection\n if (streamEntry.isCompleted) {\n res.end();\n // cleanup operation\n this.activeOperations.delete(streamOperation);\n clearInterval(heartbeat);\n }\n }\n private async _createNewStream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const streamId = options?.streamId ?? randomUUID();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n const abortController = new AbortController();\n\n // create event buffer\n const eventBuffer = new EventRingBuffer(\n options?.bufferSize ?? streamDefaults.bufferSize,\n );\n\n // setup signals and heartbeat\n const combinedSignal = this._combineSignals(\n abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // capture the current trace context at stream creation time\n const traceContext = context.active();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n clearInterval(heartbeat);\n return;\n }\n\n // create stream entry\n const streamEntry: StreamEntry = {\n streamId,\n generator: handler(combinedSignal),\n eventBuffer,\n clients: new Set([res]),\n isCompleted: false,\n lastAccess: Date.now(),\n abortController,\n traceContext,\n };\n this.streamRegistry.add(streamEntry);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n streamEntry.clients.delete(res);\n });\n\n await this._processGeneratorInBackground(streamEntry);\n\n // cleanup\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n }\n\n private async _processGeneratorInBackground(\n streamEntry: StreamEntry,\n ): Promise<void> {\n // run the entire generator processing within the captured trace context\n return context.with(streamEntry.traceContext, async () => {\n try {\n // retrieve all events from generator\n for await (const event of streamEntry.generator) {\n if (streamEntry.abortController.signal.aborted) break;\n const eventId = randomUUID();\n const eventData = JSON.stringify(event);\n\n // validate event size\n if (eventData.length > this.maxEventSize) {\n const errorMsg = `Event exceeds max size of ${this.maxEventSize} bytes`;\n const errorCode = SSEErrorCode.INVALID_REQUEST;\n // broadcast error to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n eventId,\n errorMsg,\n errorCode,\n );\n continue;\n }\n\n // buffer event for reconnection\n streamEntry.eventBuffer.add({\n id: eventId,\n type: event.type,\n data: eventData,\n timestamp: Date.now(),\n });\n\n // broadcast to all connected clients\n this._broadcastEventsToClients(streamEntry, eventId, event);\n streamEntry.lastAccess = Date.now();\n }\n\n streamEntry.isCompleted = true;\n\n // close all clients\n this._closeAllClients(streamEntry);\n\n // cleanup if no clients are connected\n this._cleanupStream(streamEntry);\n } catch (error) {\n const errorMsg =\n error instanceof Error ? error.message : \"Internal server error\";\n const errorEventId = randomUUID();\n const errorCode = this._categorizeError(error);\n\n // buffer error event\n streamEntry.eventBuffer.add({\n id: errorEventId,\n type: \"error\",\n data: JSON.stringify({ error: errorMsg, code: errorCode }),\n timestamp: Date.now(),\n });\n\n // send error event to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n errorEventId,\n errorMsg,\n errorCode,\n true,\n );\n streamEntry.isCompleted = true;\n }\n });\n }\n\n private _combineSignals(\n internalSignal?: AbortSignal,\n userSignal?: AbortSignal,\n ): AbortSignal {\n if (!userSignal) return internalSignal || new AbortController().signal;\n\n const signals = [internalSignal, userSignal].filter(\n Boolean,\n ) as AbortSignal[];\n const controller = new AbortController();\n\n signals.forEach((signal) => {\n if (signal?.aborted) {\n controller.abort(signal.reason);\n return;\n }\n\n signal?.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n });\n return controller.signal;\n }\n\n // broadcast events to all connected clients\n private _broadcastEventsToClients(\n streamEntry: StreamEntry,\n eventId: string,\n event: any,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeEvent(client, eventId, event);\n }\n }\n }\n\n // broadcast error to all connected clients\n private _broadcastErrorToClients(\n streamEntry: StreamEntry,\n eventId: string,\n errorMessage: string,\n errorCode: SSEErrorCode,\n closeClients: boolean = false,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeError(client, eventId, errorMessage, errorCode);\n if (closeClients) {\n client.end();\n }\n }\n }\n }\n\n // close all connected clients\n private _closeAllClients(streamEntry: StreamEntry): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n client.end();\n }\n }\n }\n\n // cleanup stream if no clients are connected\n private _cleanupStream(streamEntry: StreamEntry): void {\n if (streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n }\n\n private _categorizeError(error: unknown): SSEErrorCode {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes(\"timeout\") || message.includes(\"timed out\")) {\n return SSEErrorCode.TIMEOUT;\n }\n\n if (message.includes(\"unavailable\") || message.includes(\"econnrefused\")) {\n return SSEErrorCode.TEMPORARY_UNAVAILABLE;\n }\n\n if (error.name === \"AbortError\") {\n return SSEErrorCode.STREAM_ABORTED;\n }\n }\n\n return SSEErrorCode.INTERNAL_ERROR;\n }\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"stream-manager.js","names":[],"sources":["../../src/stream/stream-manager.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport { context } from \"@opentelemetry/api\";\nimport type { IAppResponse, StreamConfig } from \"shared\";\nimport { EventRingBuffer } from \"./buffers\";\nimport { streamDefaults } from \"./defaults\";\nimport { SSEWriter } from \"./sse-writer\";\nimport { StreamRegistry } from \"./stream-registry\";\nimport { SSEErrorCode, type StreamEntry, type StreamOperation } from \"./types\";\nimport { StreamValidator } from \"./validator\";\n\n// main entry point for Server-Sent events streaming\nexport class StreamManager {\n private activeOperations: Set<StreamOperation>;\n private streamRegistry: StreamRegistry;\n private sseWriter: SSEWriter;\n private maxEventSize: number;\n private bufferTTL: number;\n\n constructor(options?: StreamConfig) {\n this.streamRegistry = new StreamRegistry(\n options?.maxActiveStreams ?? streamDefaults.maxActiveStreams,\n );\n this.sseWriter = new SSEWriter();\n this.maxEventSize = options?.maxEventSize ?? streamDefaults.maxEventSize;\n this.bufferTTL = options?.bufferTTL ?? streamDefaults.bufferTTL;\n this.activeOperations = new Set();\n }\n\n // main streaming method - handles new connection and reconnection\n async stream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const { streamId } = options || {};\n\n // check if response is already closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n // setup SSE headers\n this.sseWriter.setupHeaders(res);\n\n // handle reconnection\n if (streamId && StreamValidator.validateStreamId(streamId)) {\n const existingStream = this.streamRegistry.get(streamId);\n // if stream exists, attach to it\n if (existingStream) {\n return this._attachToExistingStream(res, existingStream, options);\n }\n }\n\n // if stream does not exist, create a new one\n return this._createNewStream(res, handler, options);\n }\n\n // abort all active operations\n abortAll(): void {\n this.activeOperations.forEach((operation) => {\n if (operation.heartbeat) clearInterval(operation.heartbeat);\n operation.controller.abort(\"Server shutdown\");\n });\n this.activeOperations.clear();\n this.streamRegistry.clear();\n }\n\n // get the number of active operations\n getActiveCount(): number {\n return this.activeOperations.size;\n }\n\n // attach to existing stream\n private async _attachToExistingStream(\n res: IAppResponse,\n streamEntry: StreamEntry,\n options?: StreamConfig,\n ): Promise<void> {\n // handle reconnection - replay missed events\n const lastEventId = res.req?.headers[\"last-event-id\"];\n\n if (StreamValidator.validateEventId(lastEventId)) {\n // cast to string after validation\n const validEventId = lastEventId as string;\n if (streamEntry.eventBuffer.has(validEventId)) {\n const missedEvents =\n streamEntry.eventBuffer.getEventsSince(validEventId);\n // broadcast missed events to client\n for (const event of missedEvents) {\n if (options?.userSignal?.aborted) break;\n this.sseWriter.writeBufferedEvent(res, event);\n }\n } else {\n // buffer overflow - send warning\n this.sseWriter.writeBufferOverflowWarning(res, validEventId);\n }\n }\n\n // add client to stream entry\n streamEntry.clients.add(res);\n streamEntry.lastAccess = Date.now();\n\n // start heartbeat\n const combinedSignal = this._combineSignals(\n streamEntry.abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: streamEntry.abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n // handle client disconnect\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n streamEntry.clients.delete(res);\n this.activeOperations.delete(streamOperation);\n\n // cleanup if stream is completed and no clients are connected\n if (streamEntry.isCompleted && streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n });\n\n // if stream is completed, close connection\n if (streamEntry.isCompleted) {\n res.end();\n // cleanup operation\n this.activeOperations.delete(streamOperation);\n clearInterval(heartbeat);\n }\n }\n private async _createNewStream(\n res: IAppResponse,\n handler: (signal: AbortSignal) => AsyncGenerator<any, void, unknown>,\n options?: StreamConfig,\n ): Promise<void> {\n const streamId = options?.streamId ?? randomUUID();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n return;\n }\n\n const abortController = new AbortController();\n\n // create event buffer\n const eventBuffer = new EventRingBuffer(\n options?.bufferSize ?? streamDefaults.bufferSize,\n );\n\n // setup signals and heartbeat\n const combinedSignal = this._combineSignals(\n abortController.signal,\n options?.userSignal,\n );\n const heartbeat = this.sseWriter.startHeartbeat(res, combinedSignal);\n\n // capture the current trace context at stream creation time\n const traceContext = context.active();\n\n // abort stream if response is closed\n if (res.writableEnded || res.destroyed) {\n clearInterval(heartbeat);\n return;\n }\n\n // create stream entry\n const streamEntry: StreamEntry = {\n streamId,\n generator: handler(combinedSignal),\n eventBuffer,\n clients: new Set([res]),\n isCompleted: false,\n lastAccess: Date.now(),\n abortController,\n traceContext,\n };\n this.streamRegistry.add(streamEntry);\n\n // track operation\n const streamOperation: StreamOperation = {\n controller: abortController,\n type: \"stream\",\n heartbeat,\n };\n this.activeOperations.add(streamOperation);\n\n res.on(\"close\", () => {\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n streamEntry.clients.delete(res);\n });\n\n await this._processGeneratorInBackground(streamEntry);\n\n // cleanup\n clearInterval(heartbeat);\n this.activeOperations.delete(streamOperation);\n }\n\n private async _processGeneratorInBackground(\n streamEntry: StreamEntry,\n ): Promise<void> {\n // run the entire generator processing within the captured trace context\n return context.with(streamEntry.traceContext, async () => {\n try {\n // retrieve all events from generator\n for await (const event of streamEntry.generator) {\n if (streamEntry.abortController.signal.aborted) break;\n const eventId = randomUUID();\n const eventData = JSON.stringify(event);\n\n // validate event size\n if (eventData.length > this.maxEventSize) {\n const errorMsg = `Event exceeds max size of ${this.maxEventSize} bytes`;\n const errorCode = SSEErrorCode.INVALID_REQUEST;\n // broadcast error to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n eventId,\n errorMsg,\n errorCode,\n );\n continue;\n }\n\n // buffer event for reconnection\n streamEntry.eventBuffer.add({\n id: eventId,\n type: event.type,\n data: eventData,\n timestamp: Date.now(),\n });\n\n // broadcast to all connected clients\n this._broadcastEventsToClients(streamEntry, eventId, event);\n streamEntry.lastAccess = Date.now();\n }\n\n streamEntry.isCompleted = true;\n\n // close all clients\n this._closeAllClients(streamEntry);\n\n // cleanup if no clients are connected\n this._cleanupStream(streamEntry);\n } catch (error) {\n const errorMsg =\n error instanceof Error ? error.message : \"Internal server error\";\n const errorEventId = randomUUID();\n const errorCode = this._categorizeError(error);\n\n // buffer error event\n streamEntry.eventBuffer.add({\n id: errorEventId,\n type: \"error\",\n data: JSON.stringify({ error: errorMsg, code: errorCode }),\n timestamp: Date.now(),\n });\n\n // send error event to all connected clients\n this._broadcastErrorToClients(\n streamEntry,\n errorEventId,\n errorMsg,\n errorCode,\n true,\n );\n streamEntry.isCompleted = true;\n }\n });\n }\n\n private _combineSignals(\n internalSignal?: AbortSignal,\n userSignal?: AbortSignal,\n ): AbortSignal {\n if (!userSignal) return internalSignal || new AbortController().signal;\n\n const signals = [internalSignal, userSignal].filter(\n Boolean,\n ) as AbortSignal[];\n const controller = new AbortController();\n\n signals.forEach((signal) => {\n if (signal?.aborted) {\n controller.abort(signal.reason);\n return;\n }\n\n signal?.addEventListener(\n \"abort\",\n () => {\n controller.abort(signal.reason);\n },\n { once: true },\n );\n });\n return controller.signal;\n }\n\n // broadcast events to all connected clients\n private _broadcastEventsToClients(\n streamEntry: StreamEntry,\n eventId: string,\n event: any,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeEvent(client, eventId, event);\n }\n }\n }\n\n // broadcast error to all connected clients\n private _broadcastErrorToClients(\n streamEntry: StreamEntry,\n eventId: string,\n errorMessage: string,\n errorCode: SSEErrorCode,\n closeClients: boolean = false,\n ): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n this.sseWriter.writeError(client, eventId, errorMessage, errorCode);\n if (closeClients) {\n client.end();\n }\n }\n }\n }\n\n // close all connected clients\n private _closeAllClients(streamEntry: StreamEntry): void {\n for (const client of streamEntry.clients) {\n if (!client.writableEnded) {\n client.end();\n }\n }\n }\n\n // cleanup stream if no clients are connected\n private _cleanupStream(streamEntry: StreamEntry): void {\n if (streamEntry.clients.size === 0) {\n setTimeout(() => {\n if (streamEntry.clients.size === 0) {\n this.streamRegistry.remove(streamEntry.streamId);\n }\n }, this.bufferTTL);\n }\n }\n\n private _categorizeError(error: unknown): SSEErrorCode {\n if (error instanceof Error) {\n const message = error.message.toLowerCase();\n if (message.includes(\"timeout\") || message.includes(\"timed out\")) {\n return SSEErrorCode.TIMEOUT;\n }\n\n if (message.includes(\"unavailable\") || message.includes(\"econnrefused\")) {\n return SSEErrorCode.TEMPORARY_UNAVAILABLE;\n }\n\n if (error.name === \"AbortError\") {\n return SSEErrorCode.STREAM_ABORTED;\n }\n }\n\n return SSEErrorCode.INTERNAL_ERROR;\n }\n}\n"],"mappings":"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"}
@@ -3,6 +3,7 @@ import { SSEErrorCode } from "./types.js";
3
3
 
4
4
  //#region src/stream/stream-registry.ts
5
5
  var StreamRegistry = class {
6
+ streams;
6
7
  constructor(maxActiveStreams) {
7
8
  this.streams = new RingBuffer(maxActiveStreams, (entry) => entry.streamId);
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"stream-registry.js","names":[],"sources":["../../src/stream/stream-registry.ts"],"sourcesContent":["import { RingBuffer } from \"./buffers\";\nimport { SSEErrorCode, type StreamEntry } from \"./types\";\n\nexport class StreamRegistry {\n private streams: RingBuffer<StreamEntry>;\n\n constructor(maxActiveStreams: number) {\n this.streams = new RingBuffer<StreamEntry>(\n maxActiveStreams,\n (entry) => entry.streamId,\n );\n }\n\n // add a stream to the registry\n add(entry: StreamEntry): void {\n // enforce hard cap\n if (this.streams.getSize() >= this.streams.capacity) {\n this._evictOldestStream(entry.streamId);\n }\n\n this.streams.add(entry);\n }\n\n // get a stream from the registry\n get(streamId: string): StreamEntry | null {\n return this.streams.get(streamId);\n }\n\n // check if a stream exists in the registry\n has(streamId: string): boolean {\n return this.streams.has(streamId);\n }\n\n // remove a stream from the registry\n remove(streamId: string): void {\n this.streams.remove(streamId);\n }\n\n // get the number of streams in the registry\n size(): number {\n return this.streams.getSize();\n }\n\n clear(): void {\n const allStreams = this.streams.getAll();\n\n for (const stream of allStreams) {\n stream.abortController.abort(\"Server shutdown\");\n }\n\n this.streams.clear();\n }\n\n // evict the oldest stream from the registry\n private _evictOldestStream(excludeStreamId: string): void {\n const allStreams = this.streams.getAll();\n let oldestStream: StreamEntry | null = null;\n let oldestAccess = Infinity;\n\n // find the least recently accessed stream\n for (const stream of allStreams) {\n if (\n stream.streamId !== excludeStreamId &&\n stream.lastAccess < oldestAccess\n ) {\n oldestStream = stream;\n oldestAccess = stream.lastAccess;\n }\n }\n\n // abort the oldest stream\n if (oldestStream) {\n // broadcast stream eviction error to all clients\n for (const client of oldestStream.clients) {\n if (!client.writableEnded) {\n try {\n client.write(`event: error\\n`);\n client.write(\n `data: ${JSON.stringify({ error: \"Stream evicted\", code: SSEErrorCode.STREAM_EVICTED })}\\n\\n`,\n );\n } catch (_error) {\n // ignore\n }\n }\n }\n oldestStream.abortController.abort(\"Stream evicted\");\n this.streams.remove(oldestStream.streamId);\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,iBAAb,MAA4B;CAG1B,YAAY,kBAA0B;AACpC,OAAK,UAAU,IAAI,WACjB,mBACC,UAAU,MAAM,SAClB;;CAIH,IAAI,OAA0B;AAE5B,MAAI,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SACzC,MAAK,mBAAmB,MAAM,SAAS;AAGzC,OAAK,QAAQ,IAAI,MAAM;;CAIzB,IAAI,UAAsC;AACxC,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,IAAI,UAA2B;AAC7B,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,OAAO,UAAwB;AAC7B,OAAK,QAAQ,OAAO,SAAS;;CAI/B,OAAe;AACb,SAAO,KAAK,QAAQ,SAAS;;CAG/B,QAAc;EACZ,MAAM,aAAa,KAAK,QAAQ,QAAQ;AAExC,OAAK,MAAM,UAAU,WACnB,QAAO,gBAAgB,MAAM,kBAAkB;AAGjD,OAAK,QAAQ,OAAO;;CAItB,AAAQ,mBAAmB,iBAA+B;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACxC,IAAI,eAAmC;EACvC,IAAI,eAAe;AAGnB,OAAK,MAAM,UAAU,WACnB,KACE,OAAO,aAAa,mBACpB,OAAO,aAAa,cACpB;AACA,kBAAe;AACf,kBAAe,OAAO;;AAK1B,MAAI,cAAc;AAEhB,QAAK,MAAM,UAAU,aAAa,QAChC,KAAI,CAAC,OAAO,cACV,KAAI;AACF,WAAO,MAAM,iBAAiB;AAC9B,WAAO,MACL,SAAS,KAAK,UAAU;KAAE,OAAO;KAAkB,MAAM,aAAa;KAAgB,CAAC,CAAC,MACzF;YACM,QAAQ;AAKrB,gBAAa,gBAAgB,MAAM,iBAAiB;AACpD,QAAK,QAAQ,OAAO,aAAa,SAAS"}
1
+ {"version":3,"file":"stream-registry.js","names":[],"sources":["../../src/stream/stream-registry.ts"],"sourcesContent":["import { RingBuffer } from \"./buffers\";\nimport { SSEErrorCode, type StreamEntry } from \"./types\";\n\nexport class StreamRegistry {\n private streams: RingBuffer<StreamEntry>;\n\n constructor(maxActiveStreams: number) {\n this.streams = new RingBuffer<StreamEntry>(\n maxActiveStreams,\n (entry) => entry.streamId,\n );\n }\n\n // add a stream to the registry\n add(entry: StreamEntry): void {\n // enforce hard cap\n if (this.streams.getSize() >= this.streams.capacity) {\n this._evictOldestStream(entry.streamId);\n }\n\n this.streams.add(entry);\n }\n\n // get a stream from the registry\n get(streamId: string): StreamEntry | null {\n return this.streams.get(streamId);\n }\n\n // check if a stream exists in the registry\n has(streamId: string): boolean {\n return this.streams.has(streamId);\n }\n\n // remove a stream from the registry\n remove(streamId: string): void {\n this.streams.remove(streamId);\n }\n\n // get the number of streams in the registry\n size(): number {\n return this.streams.getSize();\n }\n\n clear(): void {\n const allStreams = this.streams.getAll();\n\n for (const stream of allStreams) {\n stream.abortController.abort(\"Server shutdown\");\n }\n\n this.streams.clear();\n }\n\n // evict the oldest stream from the registry\n private _evictOldestStream(excludeStreamId: string): void {\n const allStreams = this.streams.getAll();\n let oldestStream: StreamEntry | null = null;\n let oldestAccess = Infinity;\n\n // find the least recently accessed stream\n for (const stream of allStreams) {\n if (\n stream.streamId !== excludeStreamId &&\n stream.lastAccess < oldestAccess\n ) {\n oldestStream = stream;\n oldestAccess = stream.lastAccess;\n }\n }\n\n // abort the oldest stream\n if (oldestStream) {\n // broadcast stream eviction error to all clients\n for (const client of oldestStream.clients) {\n if (!client.writableEnded) {\n try {\n client.write(`event: error\\n`);\n client.write(\n `data: ${JSON.stringify({ error: \"Stream evicted\", code: SSEErrorCode.STREAM_EVICTED })}\\n\\n`,\n );\n } catch (_error) {\n // ignore\n }\n }\n }\n oldestStream.abortController.abort(\"Stream evicted\");\n this.streams.remove(oldestStream.streamId);\n }\n }\n}\n"],"mappings":";;;;AAGA,IAAa,iBAAb,MAA4B;CAC1B,AAAQ;CAER,YAAY,kBAA0B;AACpC,OAAK,UAAU,IAAI,WACjB,mBACC,UAAU,MAAM,SAClB;;CAIH,IAAI,OAA0B;AAE5B,MAAI,KAAK,QAAQ,SAAS,IAAI,KAAK,QAAQ,SACzC,MAAK,mBAAmB,MAAM,SAAS;AAGzC,OAAK,QAAQ,IAAI,MAAM;;CAIzB,IAAI,UAAsC;AACxC,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,IAAI,UAA2B;AAC7B,SAAO,KAAK,QAAQ,IAAI,SAAS;;CAInC,OAAO,UAAwB;AAC7B,OAAK,QAAQ,OAAO,SAAS;;CAI/B,OAAe;AACb,SAAO,KAAK,QAAQ,SAAS;;CAG/B,QAAc;EACZ,MAAM,aAAa,KAAK,QAAQ,QAAQ;AAExC,OAAK,MAAM,UAAU,WACnB,QAAO,gBAAgB,MAAM,kBAAkB;AAGjD,OAAK,QAAQ,OAAO;;CAItB,AAAQ,mBAAmB,iBAA+B;EACxD,MAAM,aAAa,KAAK,QAAQ,QAAQ;EACxC,IAAI,eAAmC;EACvC,IAAI,eAAe;AAGnB,OAAK,MAAM,UAAU,WACnB,KACE,OAAO,aAAa,mBACpB,OAAO,aAAa,cACpB;AACA,kBAAe;AACf,kBAAe,OAAO;;AAK1B,MAAI,cAAc;AAEhB,QAAK,MAAM,UAAU,aAAa,QAChC,KAAI,CAAC,OAAO,cACV,KAAI;AACF,WAAO,MAAM,iBAAiB;AAC9B,WAAO,MACL,SAAS,KAAK,UAAU;KAAE,OAAO;KAAkB,MAAM,aAAa;KAAgB,CAAC,CAAC,MACzF;YACM,QAAQ;AAKrB,gBAAa,gBAAgB,MAAM,iBAAiB;AACpD,QAAK,QAAQ,OAAO,aAAa,SAAS"}
@@ -1,11 +1,7 @@
1
1
  //#region src/stream/validator.ts
2
2
  var StreamValidator = class StreamValidator {
3
- static {
4
- this.UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
5
- }
6
- static {
7
- this.STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
8
- }
3
+ static UUID_REGEX = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
4
+ static STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;
9
5
  static validateEventId(eventId) {
10
6
  if (!eventId || typeof eventId !== "string" || eventId.length !== 36) return false;
11
7
  return StreamValidator.UUID_REGEX.test(eventId);
@@ -1 +1 @@
1
- {"version":3,"file":"validator.js","names":[],"sources":["../../src/stream/validator.ts"],"sourcesContent":["export class StreamValidator {\n private static readonly UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n private static readonly STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;\n\n // validates eventId format and throws on invalid input\n static validateEventId(eventId?: string | string[]): boolean {\n if (!eventId || typeof eventId !== \"string\" || eventId.length !== 36) {\n return false;\n }\n\n return StreamValidator.UUID_REGEX.test(eventId);\n }\n\n // validates streamId format and throws on invalid input\n static validateStreamId(streamId?: string): boolean {\n if (!streamId || streamId.length === 0 || streamId.length > 256) {\n return false;\n }\n\n if (!StreamValidator.STREAM_ID_REGEX.test(streamId)) {\n return false;\n }\n return true;\n }\n\n // sanitizes event type for SSE format\n static sanitizeEventType(type: string): string {\n return (type || \"message\").replace(/[\\r\\n]/g, \"\").slice(0, 100);\n }\n}\n"],"mappings":";AAAA,IAAa,kBAAb,MAAa,gBAAgB;;oBAEzB;;;yBAEwC;;CAG1C,OAAO,gBAAgB,SAAsC;AAC3D,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,QAAQ,WAAW,GAChE,QAAO;AAGT,SAAO,gBAAgB,WAAW,KAAK,QAAQ;;CAIjD,OAAO,iBAAiB,UAA4B;AAClD,MAAI,CAAC,YAAY,SAAS,WAAW,KAAK,SAAS,SAAS,IAC1D,QAAO;AAGT,MAAI,CAAC,gBAAgB,gBAAgB,KAAK,SAAS,CACjD,QAAO;AAET,SAAO;;CAIT,OAAO,kBAAkB,MAAsB;AAC7C,UAAQ,QAAQ,WAAW,QAAQ,WAAW,GAAG,CAAC,MAAM,GAAG,IAAI"}
1
+ {"version":3,"file":"validator.js","names":[],"sources":["../../src/stream/validator.ts"],"sourcesContent":["export class StreamValidator {\n private static readonly UUID_REGEX =\n /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n\n private static readonly STREAM_ID_REGEX = /^[a-zA-Z0-9_-]+$/;\n\n // validates eventId format and throws on invalid input\n static validateEventId(eventId?: string | string[]): boolean {\n if (!eventId || typeof eventId !== \"string\" || eventId.length !== 36) {\n return false;\n }\n\n return StreamValidator.UUID_REGEX.test(eventId);\n }\n\n // validates streamId format and throws on invalid input\n static validateStreamId(streamId?: string): boolean {\n if (!streamId || streamId.length === 0 || streamId.length > 256) {\n return false;\n }\n\n if (!StreamValidator.STREAM_ID_REGEX.test(streamId)) {\n return false;\n }\n return true;\n }\n\n // sanitizes event type for SSE format\n static sanitizeEventType(type: string): string {\n return (type || \"message\").replace(/[\\r\\n]/g, \"\").slice(0, 100);\n }\n}\n"],"mappings":";AAAA,IAAa,kBAAb,MAAa,gBAAgB;CAC3B,OAAwB,aACtB;CAEF,OAAwB,kBAAkB;CAG1C,OAAO,gBAAgB,SAAsC;AAC3D,MAAI,CAAC,WAAW,OAAO,YAAY,YAAY,QAAQ,WAAW,GAChE,QAAO;AAGT,SAAO,gBAAgB,WAAW,KAAK,QAAQ;;CAIjD,OAAO,iBAAiB,UAA4B;AAClD,MAAI,CAAC,YAAY,SAAS,WAAW,KAAK,SAAS,SAAS,IAC1D,QAAO;AAGT,MAAI,CAAC,gBAAgB,gBAAgB,KAAK,SAAS,CACjD,QAAO;AAET,SAAO;;CAIT,OAAO,kBAAkB,MAAsB;AAC7C,UAAQ,QAAQ,WAAW,QAAQ,WAAW,GAAG,CAAC,MAAM,GAAG,IAAI"}
@@ -3,6 +3,7 @@ import { NOOP_LOGGER } from "@opentelemetry/api-logs";
3
3
 
4
4
  //#region src/telemetry/noop.ts
5
5
  var NonRecordingSpan = class {
6
+ _spanContext;
6
7
  constructor(spanContext = INVALID_SPAN_CONTEXT) {
7
8
  this._spanContext = spanContext;
8
9
  }
@@ -1 +1 @@
1
- {"version":3,"file":"noop.js","names":[],"sources":["../../src/telemetry/noop.ts"],"sourcesContent":["// Our own noop tracer implementation.\n// Why?\n// Unfortunately, noop tracer is not exported from the api package, unlike noop meter and noop logger.\n// Read more: https://github.com/open-telemetry/opentelemetry-js/issues/3455\n// and https://github.com/open-telemetry/opentelemetry-js/issues/4518\n//\n// The original implementation is here: https://github.com/open-telemetry/opentelemetry-js/blob/a7acd9355cd0c1da63d285dfb960efeacc3cbc15/api/src/trace/NoopTracer.ts#L32\n// licensed under the Apache License 2.0.\n// Our own implementation is much simpler but will do the job for our needs.\n\nimport type {\n Context,\n Span,\n SpanContext,\n SpanOptions,\n Tracer,\n} from \"@opentelemetry/api\";\nimport {\n createNoopMeter,\n INVALID_SPAN_CONTEXT,\n type SpanStatusCode,\n} from \"@opentelemetry/api\";\n\nclass NonRecordingSpan implements Span {\n private readonly _spanContext: SpanContext;\n\n constructor(spanContext: SpanContext = INVALID_SPAN_CONTEXT) {\n this._spanContext = spanContext;\n }\n\n spanContext(): SpanContext {\n return this._spanContext;\n }\n\n setAttribute(_key: string, _value: any): this {\n return this;\n }\n\n setAttributes(_attributes: any): this {\n return this;\n }\n\n addEvent(\n _name: string,\n _attributesOrStartTime?: any,\n _startTime?: any,\n ): this {\n return this;\n }\n\n addLink(_link: any): this {\n return this;\n }\n\n addLinks(_links: any[]): this {\n return this;\n }\n\n setStatus(_status: { code: SpanStatusCode; message?: string }): this {\n return this;\n }\n\n updateName(_name: string): this {\n return this;\n }\n\n end(_endTime?: number): void {}\n\n isRecording(): boolean {\n return false;\n }\n\n recordException(_exception: any, _time?: number): void {}\n}\n\nexport class NoopTracer implements Tracer {\n startSpan(_name: string, _options?: SpanOptions, _context?: Context): Span {\n return new NonRecordingSpan(INVALID_SPAN_CONTEXT);\n }\n\n startActiveSpan<F extends (span: Span) => any>(\n _name: string,\n ...args: [F] | [SpanOptions, F] | [SpanOptions, Context, F]\n ): ReturnType<F> | undefined {\n const fn = args[args.length - 1] as F;\n\n if (typeof fn !== \"function\") {\n return undefined as ReturnType<F>;\n }\n\n return fn(new NonRecordingSpan(INVALID_SPAN_CONTEXT));\n }\n}\n\nexport const NOOP_TRACER = new NoopTracer();\nexport const NOOP_METER = createNoopMeter();\nexport { NOOP_LOGGER } from \"@opentelemetry/api-logs\";\n"],"mappings":";;;;AAuBA,IAAM,mBAAN,MAAuC;CAGrC,YAAY,cAA2B,sBAAsB;AAC3D,OAAK,eAAe;;CAGtB,cAA2B;AACzB,SAAO,KAAK;;CAGd,aAAa,MAAc,QAAmB;AAC5C,SAAO;;CAGT,cAAc,aAAwB;AACpC,SAAO;;CAGT,SACE,OACA,wBACA,YACM;AACN,SAAO;;CAGT,QAAQ,OAAkB;AACxB,SAAO;;CAGT,SAAS,QAAqB;AAC5B,SAAO;;CAGT,UAAU,SAA2D;AACnE,SAAO;;CAGT,WAAW,OAAqB;AAC9B,SAAO;;CAGT,IAAI,UAAyB;CAE7B,cAAuB;AACrB,SAAO;;CAGT,gBAAgB,YAAiB,OAAsB;;AAGzD,IAAa,aAAb,MAA0C;CACxC,UAAU,OAAe,UAAwB,UAA0B;AACzE,SAAO,IAAI,iBAAiB,qBAAqB;;CAGnD,gBACE,OACA,GAAG,MACwB;EAC3B,MAAM,KAAK,KAAK,KAAK,SAAS;AAE9B,MAAI,OAAO,OAAO,WAChB;AAGF,SAAO,GAAG,IAAI,iBAAiB,qBAAqB,CAAC;;;AAIzD,MAAa,cAAc,IAAI,YAAY;AAC3C,MAAa,aAAa,iBAAiB"}
1
+ {"version":3,"file":"noop.js","names":[],"sources":["../../src/telemetry/noop.ts"],"sourcesContent":["// Our own noop tracer implementation.\n// Why?\n// Unfortunately, noop tracer is not exported from the api package, unlike noop meter and noop logger.\n// Read more: https://github.com/open-telemetry/opentelemetry-js/issues/3455\n// and https://github.com/open-telemetry/opentelemetry-js/issues/4518\n//\n// The original implementation is here: https://github.com/open-telemetry/opentelemetry-js/blob/a7acd9355cd0c1da63d285dfb960efeacc3cbc15/api/src/trace/NoopTracer.ts#L32\n// licensed under the Apache License 2.0.\n// Our own implementation is much simpler but will do the job for our needs.\n\nimport type {\n Context,\n Span,\n SpanContext,\n SpanOptions,\n Tracer,\n} from \"@opentelemetry/api\";\nimport {\n createNoopMeter,\n INVALID_SPAN_CONTEXT,\n type SpanStatusCode,\n} from \"@opentelemetry/api\";\n\nclass NonRecordingSpan implements Span {\n private readonly _spanContext: SpanContext;\n\n constructor(spanContext: SpanContext = INVALID_SPAN_CONTEXT) {\n this._spanContext = spanContext;\n }\n\n spanContext(): SpanContext {\n return this._spanContext;\n }\n\n setAttribute(_key: string, _value: any): this {\n return this;\n }\n\n setAttributes(_attributes: any): this {\n return this;\n }\n\n addEvent(\n _name: string,\n _attributesOrStartTime?: any,\n _startTime?: any,\n ): this {\n return this;\n }\n\n addLink(_link: any): this {\n return this;\n }\n\n addLinks(_links: any[]): this {\n return this;\n }\n\n setStatus(_status: { code: SpanStatusCode; message?: string }): this {\n return this;\n }\n\n updateName(_name: string): this {\n return this;\n }\n\n end(_endTime?: number): void {}\n\n isRecording(): boolean {\n return false;\n }\n\n recordException(_exception: any, _time?: number): void {}\n}\n\nexport class NoopTracer implements Tracer {\n startSpan(_name: string, _options?: SpanOptions, _context?: Context): Span {\n return new NonRecordingSpan(INVALID_SPAN_CONTEXT);\n }\n\n startActiveSpan<F extends (span: Span) => any>(\n _name: string,\n ...args: [F] | [SpanOptions, F] | [SpanOptions, Context, F]\n ): ReturnType<F> | undefined {\n const fn = args[args.length - 1] as F;\n\n if (typeof fn !== \"function\") {\n return undefined as ReturnType<F>;\n }\n\n return fn(new NonRecordingSpan(INVALID_SPAN_CONTEXT));\n }\n}\n\nexport const NOOP_TRACER = new NoopTracer();\nexport const NOOP_METER = createNoopMeter();\nexport { NOOP_LOGGER } from \"@opentelemetry/api-logs\";\n"],"mappings":";;;;AAuBA,IAAM,mBAAN,MAAuC;CACrC,AAAiB;CAEjB,YAAY,cAA2B,sBAAsB;AAC3D,OAAK,eAAe;;CAGtB,cAA2B;AACzB,SAAO,KAAK;;CAGd,aAAa,MAAc,QAAmB;AAC5C,SAAO;;CAGT,cAAc,aAAwB;AACpC,SAAO;;CAGT,SACE,OACA,wBACA,YACM;AACN,SAAO;;CAGT,QAAQ,OAAkB;AACxB,SAAO;;CAGT,SAAS,QAAqB;AAC5B,SAAO;;CAGT,UAAU,SAA2D;AACnE,SAAO;;CAGT,WAAW,OAAqB;AAC9B,SAAO;;CAGT,IAAI,UAAyB;CAE7B,cAAuB;AACrB,SAAO;;CAGT,gBAAgB,YAAiB,OAAsB;;AAGzD,IAAa,aAAb,MAA0C;CACxC,UAAU,OAAe,UAAwB,UAA0B;AACzE,SAAO,IAAI,iBAAiB,qBAAqB;;CAGnD,gBACE,OACA,GAAG,MACwB;EAC3B,MAAM,KAAK,KAAK,KAAK,SAAS;AAE9B,MAAI,OAAO,OAAO,WAChB;AAGF,SAAO,GAAG,IAAI,iBAAiB,qBAAqB,CAAC;;;AAIzD,MAAa,cAAc,IAAI,YAAY;AAC3C,MAAa,aAAa,iBAAiB"}
@@ -15,12 +15,10 @@ import { ATTR_SERVICE_NAME, ATTR_SERVICE_VERSION } from "@opentelemetry/semantic
15
15
  //#region src/telemetry/telemetry-manager.ts
16
16
  const logger = createLogger("telemetry");
17
17
  var TelemetryManager = class TelemetryManager {
18
- static {
19
- this.DEFAULT_EXPORT_INTERVAL_MS = 1e4;
20
- }
21
- static {
22
- this.DEFAULT_FALLBACK_APP_NAME = "databricks-app";
23
- }
18
+ static DEFAULT_EXPORT_INTERVAL_MS = 1e4;
19
+ static DEFAULT_FALLBACK_APP_NAME = "databricks-app";
20
+ static instance;
21
+ sdk;
24
22
  /**
25
23
  * Create a scoped telemetry provider for a specific plugin.
26
24
  * The plugin's name will be used as the default tracer/meter name.
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-manager.js","names":[],"sources":["../../src/telemetry/telemetry-manager.ts"],"sourcesContent":["import { getNodeAutoInstrumentations } from \"@opentelemetry/auto-instrumentations-node\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-proto\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-proto\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-proto\";\nimport {\n type Instrumentation,\n registerInstrumentations as otelRegisterInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport {\n detectResources,\n envDetector,\n hostDetector,\n processDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { TelemetryProvider } from \"./telemetry-provider\";\nimport { AppKitSampler } from \"./trace-sampler\";\nimport type { TelemetryConfig } from \"./types\";\n\nconst logger = createLogger(\"telemetry\");\n\nexport class TelemetryManager {\n private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;\n private static readonly DEFAULT_FALLBACK_APP_NAME = \"databricks-app\";\n\n private static instance?: TelemetryManager;\n private sdk?: NodeSDK;\n\n /**\n * Create a scoped telemetry provider for a specific plugin.\n * The plugin's name will be used as the default tracer/meter name.\n * @param pluginName - The name of the plugin to create scoped telemetry for\n * @param telemetryConfig - The telemetry configuration for the plugin\n * @returns A scoped telemetry instance for the plugin\n */\n static getProvider(\n pluginName: string,\n telemetryConfig?: TelemetryOptions,\n ): TelemetryProvider {\n const globalManager = TelemetryManager.getInstance();\n return new TelemetryProvider(pluginName, globalManager, telemetryConfig);\n }\n\n private constructor() {}\n\n static getInstance(): TelemetryManager {\n if (!TelemetryManager.instance) {\n TelemetryManager.instance = new TelemetryManager();\n }\n return TelemetryManager.instance;\n }\n\n static initialize(config: Partial<TelemetryConfig> = {}): void {\n const instance = TelemetryManager.getInstance();\n instance._initialize(config);\n }\n\n private _initialize(config: Partial<TelemetryConfig>): void {\n if (this.sdk) return;\n\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {\n return;\n }\n\n try {\n this.sdk = new NodeSDK({\n resource: this.createResource(config),\n autoDetectResources: false,\n sampler: new AppKitSampler(),\n traceExporter: new OTLPTraceExporter({ headers: config.headers }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ headers: config.headers }),\n exportIntervalMillis:\n config.exportIntervalMs ||\n TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ headers: config.headers }),\n ),\n ],\n instrumentations: this.getDefaultInstrumentations(),\n });\n\n this.sdk.start();\n this.registerShutdown();\n logger.debug(\"Initialized successfully\");\n } catch (error) {\n logger.error(\"Failed to initialize: %O\", error);\n }\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n otelRegisterInstrumentations({\n // global providers set by NodeSDK.start()\n instrumentations,\n });\n }\n\n private createResource(config: Partial<TelemetryConfig>): Resource {\n const serviceName =\n config.serviceName ||\n process.env.OTEL_SERVICE_NAME ||\n process.env.DATABRICKS_APP_NAME ||\n TelemetryManager.DEFAULT_FALLBACK_APP_NAME;\n const initialResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,\n });\n const detectedResource = detectResources({\n detectors: [envDetector, hostDetector, processDetector],\n });\n return initialResource.merge(detectedResource);\n }\n\n private getDefaultInstrumentations(): Instrumentation[] {\n return [\n ...getNodeAutoInstrumentations({\n //\n // enabled as a part of the server plugin\n //\n \"@opentelemetry/instrumentation-http\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-express\": {\n enabled: false,\n },\n //\n // reduce noise\n //\n \"@opentelemetry/instrumentation-fs\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-dns\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-net\": {\n enabled: false,\n },\n }),\n ];\n }\n\n private registerShutdown() {\n const shutdownFn = async () => {\n await TelemetryManager.getInstance().shutdown();\n };\n process.once(\"SIGTERM\", shutdownFn);\n process.once(\"SIGINT\", shutdownFn);\n }\n\n private async shutdown(): Promise<void> {\n if (!this.sdk) {\n return;\n }\n\n try {\n await this.sdk.shutdown();\n this.sdk = undefined;\n } catch (error) {\n logger.error(\"Error shutting down: %O\", error);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,MAAM,SAAS,aAAa,YAAY;AAExC,IAAa,mBAAb,MAAa,iBAAiB;;oCACyB;;;mCACD;;;;;;;;;CAYpD,OAAO,YACL,YACA,iBACmB;AAEnB,SAAO,IAAI,kBAAkB,YADP,iBAAiB,aAAa,EACI,gBAAgB;;CAG1E,AAAQ,cAAc;CAEtB,OAAO,cAAgC;AACrC,MAAI,CAAC,iBAAiB,SACpB,kBAAiB,WAAW,IAAI,kBAAkB;AAEpD,SAAO,iBAAiB;;CAG1B,OAAO,WAAW,SAAmC,EAAE,EAAQ;AAE7D,EADiB,iBAAiB,aAAa,CACtC,YAAY,OAAO;;CAG9B,AAAQ,YAAY,QAAwC;AAC1D,MAAI,KAAK,IAAK;AAEd,MAAI,CAAC,QAAQ,IAAI,4BACf;AAGF,MAAI;AACF,QAAK,MAAM,IAAI,QAAQ;IACrB,UAAU,KAAK,eAAe,OAAO;IACrC,qBAAqB;IACrB,SAAS,IAAI,eAAe;IAC5B,eAAe,IAAI,kBAAkB,EAAE,SAAS,OAAO,SAAS,CAAC;IACjE,eAAe,CACb,IAAI,8BAA8B;KAChC,UAAU,IAAI,mBAAmB,EAAE,SAAS,OAAO,SAAS,CAAC;KAC7D,sBACE,OAAO,oBACP,iBAAiB;KACpB,CAAC,CACH;IACD,qBAAqB,CACnB,IAAI,wBACF,IAAI,gBAAgB,EAAE,SAAS,OAAO,SAAS,CAAC,CACjD,CACF;IACD,kBAAkB,KAAK,4BAA4B;IACpD,CAAC;AAEF,QAAK,IAAI,OAAO;AAChB,QAAK,kBAAkB;AACvB,UAAO,MAAM,2BAA2B;WACjC,OAAO;AACd,UAAO,MAAM,4BAA4B,MAAM;;;;;;;;CASnD,yBAAyB,kBAA2C;AAClE,2BAA6B,EAE3B,kBACD,CAAC;;CAGJ,AAAQ,eAAe,QAA4C;EACjE,MAAM,cACJ,OAAO,eACP,QAAQ,IAAI,qBACZ,QAAQ,IAAI,uBACZ,iBAAiB;EACnB,MAAM,kBAAkB,uBAAuB;IAC5C,oBAAoB;IACpB,uBAAuB,OAAO,kBAAkB;GAClD,CAAC;EACF,MAAM,mBAAmB,gBAAgB,EACvC,WAAW;GAAC;GAAa;GAAc;GAAgB,EACxD,CAAC;AACF,SAAO,gBAAgB,MAAM,iBAAiB;;CAGhD,AAAQ,6BAAgD;AACtD,SAAO,CACL,GAAG,4BAA4B;GAI7B,uCAAuC,EACrC,SAAS,OACV;GACD,0CAA0C,EACxC,SAAS,OACV;GAID,qCAAqC,EACnC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACF,CAAC,CACH;;CAGH,AAAQ,mBAAmB;EACzB,MAAM,aAAa,YAAY;AAC7B,SAAM,iBAAiB,aAAa,CAAC,UAAU;;AAEjD,UAAQ,KAAK,WAAW,WAAW;AACnC,UAAQ,KAAK,UAAU,WAAW;;CAGpC,MAAc,WAA0B;AACtC,MAAI,CAAC,KAAK,IACR;AAGF,MAAI;AACF,SAAM,KAAK,IAAI,UAAU;AACzB,QAAK,MAAM;WACJ,OAAO;AACd,UAAO,MAAM,2BAA2B,MAAM"}
1
+ {"version":3,"file":"telemetry-manager.js","names":[],"sources":["../../src/telemetry/telemetry-manager.ts"],"sourcesContent":["import { getNodeAutoInstrumentations } from \"@opentelemetry/auto-instrumentations-node\";\nimport { OTLPLogExporter } from \"@opentelemetry/exporter-logs-otlp-proto\";\nimport { OTLPMetricExporter } from \"@opentelemetry/exporter-metrics-otlp-proto\";\nimport { OTLPTraceExporter } from \"@opentelemetry/exporter-trace-otlp-proto\";\nimport {\n type Instrumentation,\n registerInstrumentations as otelRegisterInstrumentations,\n} from \"@opentelemetry/instrumentation\";\nimport {\n detectResources,\n envDetector,\n hostDetector,\n processDetector,\n type Resource,\n resourceFromAttributes,\n} from \"@opentelemetry/resources\";\nimport { BatchLogRecordProcessor } from \"@opentelemetry/sdk-logs\";\nimport { PeriodicExportingMetricReader } from \"@opentelemetry/sdk-metrics\";\nimport { NodeSDK } from \"@opentelemetry/sdk-node\";\nimport {\n ATTR_SERVICE_NAME,\n ATTR_SERVICE_VERSION,\n} from \"@opentelemetry/semantic-conventions\";\nimport type { TelemetryOptions } from \"shared\";\nimport { createLogger } from \"../logging/logger\";\nimport { TelemetryProvider } from \"./telemetry-provider\";\nimport { AppKitSampler } from \"./trace-sampler\";\nimport type { TelemetryConfig } from \"./types\";\n\nconst logger = createLogger(\"telemetry\");\n\nexport class TelemetryManager {\n private static readonly DEFAULT_EXPORT_INTERVAL_MS = 10000;\n private static readonly DEFAULT_FALLBACK_APP_NAME = \"databricks-app\";\n\n private static instance?: TelemetryManager;\n private sdk?: NodeSDK;\n\n /**\n * Create a scoped telemetry provider for a specific plugin.\n * The plugin's name will be used as the default tracer/meter name.\n * @param pluginName - The name of the plugin to create scoped telemetry for\n * @param telemetryConfig - The telemetry configuration for the plugin\n * @returns A scoped telemetry instance for the plugin\n */\n static getProvider(\n pluginName: string,\n telemetryConfig?: TelemetryOptions,\n ): TelemetryProvider {\n const globalManager = TelemetryManager.getInstance();\n return new TelemetryProvider(pluginName, globalManager, telemetryConfig);\n }\n\n private constructor() {}\n\n static getInstance(): TelemetryManager {\n if (!TelemetryManager.instance) {\n TelemetryManager.instance = new TelemetryManager();\n }\n return TelemetryManager.instance;\n }\n\n static initialize(config: Partial<TelemetryConfig> = {}): void {\n const instance = TelemetryManager.getInstance();\n instance._initialize(config);\n }\n\n private _initialize(config: Partial<TelemetryConfig>): void {\n if (this.sdk) return;\n\n if (!process.env.OTEL_EXPORTER_OTLP_ENDPOINT) {\n return;\n }\n\n try {\n this.sdk = new NodeSDK({\n resource: this.createResource(config),\n autoDetectResources: false,\n sampler: new AppKitSampler(),\n traceExporter: new OTLPTraceExporter({ headers: config.headers }),\n metricReaders: [\n new PeriodicExportingMetricReader({\n exporter: new OTLPMetricExporter({ headers: config.headers }),\n exportIntervalMillis:\n config.exportIntervalMs ||\n TelemetryManager.DEFAULT_EXPORT_INTERVAL_MS,\n }),\n ],\n logRecordProcessors: [\n new BatchLogRecordProcessor(\n new OTLPLogExporter({ headers: config.headers }),\n ),\n ],\n instrumentations: this.getDefaultInstrumentations(),\n });\n\n this.sdk.start();\n this.registerShutdown();\n logger.debug(\"Initialized successfully\");\n } catch (error) {\n logger.error(\"Failed to initialize: %O\", error);\n }\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n otelRegisterInstrumentations({\n // global providers set by NodeSDK.start()\n instrumentations,\n });\n }\n\n private createResource(config: Partial<TelemetryConfig>): Resource {\n const serviceName =\n config.serviceName ||\n process.env.OTEL_SERVICE_NAME ||\n process.env.DATABRICKS_APP_NAME ||\n TelemetryManager.DEFAULT_FALLBACK_APP_NAME;\n const initialResource = resourceFromAttributes({\n [ATTR_SERVICE_NAME]: serviceName,\n [ATTR_SERVICE_VERSION]: config.serviceVersion ?? undefined,\n });\n const detectedResource = detectResources({\n detectors: [envDetector, hostDetector, processDetector],\n });\n return initialResource.merge(detectedResource);\n }\n\n private getDefaultInstrumentations(): Instrumentation[] {\n return [\n ...getNodeAutoInstrumentations({\n //\n // enabled as a part of the server plugin\n //\n \"@opentelemetry/instrumentation-http\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-express\": {\n enabled: false,\n },\n //\n // reduce noise\n //\n \"@opentelemetry/instrumentation-fs\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-dns\": {\n enabled: false,\n },\n \"@opentelemetry/instrumentation-net\": {\n enabled: false,\n },\n }),\n ];\n }\n\n private registerShutdown() {\n const shutdownFn = async () => {\n await TelemetryManager.getInstance().shutdown();\n };\n process.once(\"SIGTERM\", shutdownFn);\n process.once(\"SIGINT\", shutdownFn);\n }\n\n private async shutdown(): Promise<void> {\n if (!this.sdk) {\n return;\n }\n\n try {\n await this.sdk.shutdown();\n this.sdk = undefined;\n } catch (error) {\n logger.error(\"Error shutting down: %O\", error);\n }\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;AA6BA,MAAM,SAAS,aAAa,YAAY;AAExC,IAAa,mBAAb,MAAa,iBAAiB;CAC5B,OAAwB,6BAA6B;CACrD,OAAwB,4BAA4B;CAEpD,OAAe;CACf,AAAQ;;;;;;;;CASR,OAAO,YACL,YACA,iBACmB;AAEnB,SAAO,IAAI,kBAAkB,YADP,iBAAiB,aAAa,EACI,gBAAgB;;CAG1E,AAAQ,cAAc;CAEtB,OAAO,cAAgC;AACrC,MAAI,CAAC,iBAAiB,SACpB,kBAAiB,WAAW,IAAI,kBAAkB;AAEpD,SAAO,iBAAiB;;CAG1B,OAAO,WAAW,SAAmC,EAAE,EAAQ;AAE7D,EADiB,iBAAiB,aAAa,CACtC,YAAY,OAAO;;CAG9B,AAAQ,YAAY,QAAwC;AAC1D,MAAI,KAAK,IAAK;AAEd,MAAI,CAAC,QAAQ,IAAI,4BACf;AAGF,MAAI;AACF,QAAK,MAAM,IAAI,QAAQ;IACrB,UAAU,KAAK,eAAe,OAAO;IACrC,qBAAqB;IACrB,SAAS,IAAI,eAAe;IAC5B,eAAe,IAAI,kBAAkB,EAAE,SAAS,OAAO,SAAS,CAAC;IACjE,eAAe,CACb,IAAI,8BAA8B;KAChC,UAAU,IAAI,mBAAmB,EAAE,SAAS,OAAO,SAAS,CAAC;KAC7D,sBACE,OAAO,oBACP,iBAAiB;KACpB,CAAC,CACH;IACD,qBAAqB,CACnB,IAAI,wBACF,IAAI,gBAAgB,EAAE,SAAS,OAAO,SAAS,CAAC,CACjD,CACF;IACD,kBAAkB,KAAK,4BAA4B;IACpD,CAAC;AAEF,QAAK,IAAI,OAAO;AAChB,QAAK,kBAAkB;AACvB,UAAO,MAAM,2BAA2B;WACjC,OAAO;AACd,UAAO,MAAM,4BAA4B,MAAM;;;;;;;;CASnD,yBAAyB,kBAA2C;AAClE,2BAA6B,EAE3B,kBACD,CAAC;;CAGJ,AAAQ,eAAe,QAA4C;EACjE,MAAM,cACJ,OAAO,eACP,QAAQ,IAAI,qBACZ,QAAQ,IAAI,uBACZ,iBAAiB;EACnB,MAAM,kBAAkB,uBAAuB;IAC5C,oBAAoB;IACpB,uBAAuB,OAAO,kBAAkB;GAClD,CAAC;EACF,MAAM,mBAAmB,gBAAgB,EACvC,WAAW;GAAC;GAAa;GAAc;GAAgB,EACxD,CAAC;AACF,SAAO,gBAAgB,MAAM,iBAAiB;;CAGhD,AAAQ,6BAAgD;AACtD,SAAO,CACL,GAAG,4BAA4B;GAI7B,uCAAuC,EACrC,SAAS,OACV;GACD,0CAA0C,EACxC,SAAS,OACV;GAID,qCAAqC,EACnC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACD,sCAAsC,EACpC,SAAS,OACV;GACF,CAAC,CACH;;CAGH,AAAQ,mBAAmB;EACzB,MAAM,aAAa,YAAY;AAC7B,SAAM,iBAAiB,aAAa,CAAC,UAAU;;AAEjD,UAAQ,KAAK,WAAW,WAAW;AACnC,UAAQ,KAAK,UAAU,WAAW;;CAGpC,MAAc,WAA0B;AACtC,MAAI,CAAC,KAAK,IACR;AAGF,MAAI;AACF,SAAM,KAAK,IAAI,UAAU;AACzB,QAAK,MAAM;WACJ,OAAO;AACd,UAAO,MAAM,2BAA2B,MAAM"}
@@ -9,6 +9,9 @@ import { logs } from "@opentelemetry/api-logs";
9
9
  * Automatically uses the plugin name as the default tracer/meter name.
10
10
  */
11
11
  var TelemetryProvider = class {
12
+ pluginName;
13
+ globalManager;
14
+ config;
12
15
  constructor(pluginName, globalManager, telemetryConfig) {
13
16
  this.pluginName = pluginName;
14
17
  this.globalManager = globalManager;
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry-provider.js","names":[],"sources":["../../src/telemetry/telemetry-provider.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\nimport type { Meter, Span, SpanOptions, Tracer } from \"@opentelemetry/api\";\nimport { metrics, trace } from \"@opentelemetry/api\";\nimport { type Logger, type LogRecord, logs } from \"@opentelemetry/api-logs\";\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport {\n normalizeTelemetryOptions,\n type TelemetryProviderConfig,\n} from \"./config\";\nimport { NOOP_LOGGER, NOOP_METER, NOOP_TRACER } from \"./noop\";\nimport type { TelemetryManager } from \"./telemetry-manager\";\nimport type { InstrumentConfig, ITelemetry } from \"./types\";\n\n/**\n * Scoped telemetry instance for specific plugins and other classes.\n * Automatically uses the plugin name as the default tracer/meter name.\n */\nexport class TelemetryProvider implements ITelemetry {\n private readonly pluginName: string;\n private readonly globalManager: TelemetryManager;\n private readonly config: TelemetryProviderConfig;\n\n constructor(\n pluginName: string,\n globalManager: TelemetryManager,\n telemetryConfig?: TelemetryOptions,\n ) {\n this.pluginName = pluginName;\n this.globalManager = globalManager;\n this.config = normalizeTelemetryOptions(telemetryConfig);\n }\n\n /**\n * Gets a tracer for creating spans.\n * @param options - Optional tracer configuration.\n */\n getTracer(options?: InstrumentConfig): Tracer {\n if (!this.config.traces) {\n return NOOP_TRACER;\n }\n\n const tracerName = this.getInstrumentName(options);\n return trace.getTracer(tracerName);\n }\n\n /**\n * Gets a meter for recording metrics.\n * @param config - Optional meter configuration.\n */\n getMeter(config?: InstrumentConfig): Meter {\n if (!this.config.metrics) {\n return NOOP_METER;\n }\n\n const meterName = this.getInstrumentName(config);\n return metrics.getMeter(meterName);\n }\n\n /**\n * Gets a logger for emitting log records.\n * @param config - Optional logger configuration.\n */\n getLogger(config?: InstrumentConfig): Logger {\n if (!this.config.logs) {\n return NOOP_LOGGER;\n }\n\n const loggerName = this.getInstrumentName(config);\n return logs.getLogger(loggerName);\n }\n\n /**\n * Emits a log record using the default logger.\n * Convenience method for logging without explicitly getting a logger.\n * @param logRecord - The log record to emit\n */\n emit(logRecord: LogRecord): void {\n const logger = this.getLogger();\n logger.emit(logRecord);\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n if (!this.config.traces) {\n return;\n }\n\n this.globalManager.registerInstrumentations(instrumentations);\n }\n\n /**\n * Starts an active span and executes a callback function within its context.\n * Uses the plugin's default tracer unless custom tracer options are provided.\n * @param name - The name of the span\n * @param options - Span options including attributes, kind, etc.\n * @param fn - Callback function to execute within the span context\n * @param tracerOptions - Optional tracer configuration\n * @returns Promise resolving to the callback's return value\n */\n startActiveSpan<T>(\n name: string,\n options: SpanOptions,\n fn: (span: Span) => Promise<T>,\n tracerOptions?: InstrumentConfig,\n ): Promise<T> {\n const tracer = this.getTracer(tracerOptions);\n return tracer.startActiveSpan(name, options, fn);\n }\n\n private getInstrumentName(options?: InstrumentConfig): string {\n const prefix = this.pluginName;\n if (!options || !options.name) {\n return this.pluginName;\n }\n\n return options.includePrefix ? `${prefix}-${options.name}` : options.name;\n }\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,oBAAb,MAAqD;CAKnD,YACE,YACA,eACA,iBACA;AACA,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,SAAS,0BAA0B,gBAAgB;;;;;;CAO1D,UAAU,SAAoC;AAC5C,MAAI,CAAC,KAAK,OAAO,OACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,QAAQ;AAClD,SAAO,MAAM,UAAU,WAAW;;;;;;CAOpC,SAAS,QAAkC;AACzC,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;EAGT,MAAM,YAAY,KAAK,kBAAkB,OAAO;AAChD,SAAO,QAAQ,SAAS,UAAU;;;;;;CAOpC,UAAU,QAAmC;AAC3C,MAAI,CAAC,KAAK,OAAO,KACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,OAAO;AACjD,SAAO,KAAK,UAAU,WAAW;;;;;;;CAQnC,KAAK,WAA4B;AAE/B,EADe,KAAK,WAAW,CACxB,KAAK,UAAU;;;;;;;CAQxB,yBAAyB,kBAA2C;AAClE,MAAI,CAAC,KAAK,OAAO,OACf;AAGF,OAAK,cAAc,yBAAyB,iBAAiB;;;;;;;;;;;CAY/D,gBACE,MACA,SACA,IACA,eACY;AAEZ,SADe,KAAK,UAAU,cAAc,CAC9B,gBAAgB,MAAM,SAAS,GAAG;;CAGlD,AAAQ,kBAAkB,SAAoC;EAC5D,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,WAAW,CAAC,QAAQ,KACvB,QAAO,KAAK;AAGd,SAAO,QAAQ,gBAAgB,GAAG,OAAO,GAAG,QAAQ,SAAS,QAAQ"}
1
+ {"version":3,"file":"telemetry-provider.js","names":[],"sources":["../../src/telemetry/telemetry-provider.ts"],"sourcesContent":["import type { TelemetryOptions } from \"shared\";\nimport type { Meter, Span, SpanOptions, Tracer } from \"@opentelemetry/api\";\nimport { metrics, trace } from \"@opentelemetry/api\";\nimport { type Logger, type LogRecord, logs } from \"@opentelemetry/api-logs\";\nimport type { Instrumentation } from \"@opentelemetry/instrumentation\";\nimport {\n normalizeTelemetryOptions,\n type TelemetryProviderConfig,\n} from \"./config\";\nimport { NOOP_LOGGER, NOOP_METER, NOOP_TRACER } from \"./noop\";\nimport type { TelemetryManager } from \"./telemetry-manager\";\nimport type { InstrumentConfig, ITelemetry } from \"./types\";\n\n/**\n * Scoped telemetry instance for specific plugins and other classes.\n * Automatically uses the plugin name as the default tracer/meter name.\n */\nexport class TelemetryProvider implements ITelemetry {\n private readonly pluginName: string;\n private readonly globalManager: TelemetryManager;\n private readonly config: TelemetryProviderConfig;\n\n constructor(\n pluginName: string,\n globalManager: TelemetryManager,\n telemetryConfig?: TelemetryOptions,\n ) {\n this.pluginName = pluginName;\n this.globalManager = globalManager;\n this.config = normalizeTelemetryOptions(telemetryConfig);\n }\n\n /**\n * Gets a tracer for creating spans.\n * @param options - Optional tracer configuration.\n */\n getTracer(options?: InstrumentConfig): Tracer {\n if (!this.config.traces) {\n return NOOP_TRACER;\n }\n\n const tracerName = this.getInstrumentName(options);\n return trace.getTracer(tracerName);\n }\n\n /**\n * Gets a meter for recording metrics.\n * @param config - Optional meter configuration.\n */\n getMeter(config?: InstrumentConfig): Meter {\n if (!this.config.metrics) {\n return NOOP_METER;\n }\n\n const meterName = this.getInstrumentName(config);\n return metrics.getMeter(meterName);\n }\n\n /**\n * Gets a logger for emitting log records.\n * @param config - Optional logger configuration.\n */\n getLogger(config?: InstrumentConfig): Logger {\n if (!this.config.logs) {\n return NOOP_LOGGER;\n }\n\n const loggerName = this.getInstrumentName(config);\n return logs.getLogger(loggerName);\n }\n\n /**\n * Emits a log record using the default logger.\n * Convenience method for logging without explicitly getting a logger.\n * @param logRecord - The log record to emit\n */\n emit(logRecord: LogRecord): void {\n const logger = this.getLogger();\n logger.emit(logRecord);\n }\n\n /**\n * Register OpenTelemetry instrumentations.\n * Can be called at any time, but recommended to call in plugin constructor.\n * @param instrumentations - Array of OpenTelemetry instrumentations to register\n */\n registerInstrumentations(instrumentations: Instrumentation[]): void {\n if (!this.config.traces) {\n return;\n }\n\n this.globalManager.registerInstrumentations(instrumentations);\n }\n\n /**\n * Starts an active span and executes a callback function within its context.\n * Uses the plugin's default tracer unless custom tracer options are provided.\n * @param name - The name of the span\n * @param options - Span options including attributes, kind, etc.\n * @param fn - Callback function to execute within the span context\n * @param tracerOptions - Optional tracer configuration\n * @returns Promise resolving to the callback's return value\n */\n startActiveSpan<T>(\n name: string,\n options: SpanOptions,\n fn: (span: Span) => Promise<T>,\n tracerOptions?: InstrumentConfig,\n ): Promise<T> {\n const tracer = this.getTracer(tracerOptions);\n return tracer.startActiveSpan(name, options, fn);\n }\n\n private getInstrumentName(options?: InstrumentConfig): string {\n const prefix = this.pluginName;\n if (!options || !options.name) {\n return this.pluginName;\n }\n\n return options.includePrefix ? `${prefix}-${options.name}` : options.name;\n }\n}\n"],"mappings":";;;;;;;;;;AAiBA,IAAa,oBAAb,MAAqD;CACnD,AAAiB;CACjB,AAAiB;CACjB,AAAiB;CAEjB,YACE,YACA,eACA,iBACA;AACA,OAAK,aAAa;AAClB,OAAK,gBAAgB;AACrB,OAAK,SAAS,0BAA0B,gBAAgB;;;;;;CAO1D,UAAU,SAAoC;AAC5C,MAAI,CAAC,KAAK,OAAO,OACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,QAAQ;AAClD,SAAO,MAAM,UAAU,WAAW;;;;;;CAOpC,SAAS,QAAkC;AACzC,MAAI,CAAC,KAAK,OAAO,QACf,QAAO;EAGT,MAAM,YAAY,KAAK,kBAAkB,OAAO;AAChD,SAAO,QAAQ,SAAS,UAAU;;;;;;CAOpC,UAAU,QAAmC;AAC3C,MAAI,CAAC,KAAK,OAAO,KACf,QAAO;EAGT,MAAM,aAAa,KAAK,kBAAkB,OAAO;AACjD,SAAO,KAAK,UAAU,WAAW;;;;;;;CAQnC,KAAK,WAA4B;AAE/B,EADe,KAAK,WAAW,CACxB,KAAK,UAAU;;;;;;;CAQxB,yBAAyB,kBAA2C;AAClE,MAAI,CAAC,KAAK,OAAO,OACf;AAGF,OAAK,cAAc,yBAAyB,iBAAiB;;;;;;;;;;;CAY/D,gBACE,MACA,SACA,IACA,eACY;AAEZ,SADe,KAAK,UAAU,cAAc,CAC9B,gBAAgB,MAAM,SAAS,GAAG;;CAGlD,AAAQ,kBAAkB,SAAoC;EAC5D,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,WAAW,CAAC,QAAQ,KACvB,QAAO,KAAK;AAGd,SAAO,QAAQ,gBAAgB,GAAG,OAAO,GAAG,QAAQ,SAAS,QAAQ"}
@@ -3,17 +3,15 @@
3
3
  * Simple loading spinner for CLI
4
4
  */
5
5
  var Spinner = class {
6
- constructor() {
7
- this.frames = [
8
- " ",
9
- ". ",
10
- ".. ",
11
- "..."
12
- ];
13
- this.current = 0;
14
- this.interval = null;
15
- this.text = "";
16
- }
6
+ frames = [
7
+ " ",
8
+ ". ",
9
+ ".. ",
10
+ "..."
11
+ ];
12
+ current = 0;
13
+ interval = null;
14
+ text = "";
17
15
  start(text) {
18
16
  this.text = text;
19
17
  this.current = 0;
@@ -1 +1 @@
1
- {"version":3,"file":"spinner.js","names":[],"sources":["../../src/type-generator/spinner.ts"],"sourcesContent":["/**\n * Simple loading spinner for CLI\n */\nexport class Spinner {\n private frames = [\" \", \". \", \".. \", \"...\"];\n private current = 0;\n private interval: NodeJS.Timeout | null = null;\n private text = \"\";\n\n start(text: string) {\n this.text = text;\n this.current = 0;\n process.stdout.write(` ${this.text}${this.frames[0]}`);\n this.interval = setInterval(() => {\n this.current = (this.current + 1) % this.frames.length;\n process.stdout.write(`\\r ${this.text}${this.frames[this.current]}`);\n }, 300);\n }\n\n stop(finalText?: string) {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n // clear the line and write the final text\n process.stdout.write(`\\x1b[2K\\r ${finalText || this.text}\\n`);\n }\n}\n"],"mappings":";;;;AAGA,IAAa,UAAb,MAAqB;;gBACF;GAAC;GAAO;GAAO;GAAO;GAAM;iBAC3B;kBACwB;cAC3B;;CAEf,MAAM,MAAc;AAClB,OAAK,OAAO;AACZ,OAAK,UAAU;AACf,UAAQ,OAAO,MAAM,KAAK,KAAK,OAAO,KAAK,OAAO,KAAK;AACvD,OAAK,WAAW,kBAAkB;AAChC,QAAK,WAAW,KAAK,UAAU,KAAK,KAAK,OAAO;AAChD,WAAQ,OAAO,MAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,WAAW;KACnE,IAAI;;CAGT,KAAK,WAAoB;AACvB,MAAI,KAAK,UAAU;AACjB,iBAAc,KAAK,SAAS;AAC5B,QAAK,WAAW;;AAGlB,UAAQ,OAAO,MAAM,cAAc,aAAa,KAAK,KAAK,IAAI"}
1
+ {"version":3,"file":"spinner.js","names":[],"sources":["../../src/type-generator/spinner.ts"],"sourcesContent":["/**\n * Simple loading spinner for CLI\n */\nexport class Spinner {\n private frames = [\" \", \". \", \".. \", \"...\"];\n private current = 0;\n private interval: NodeJS.Timeout | null = null;\n private text = \"\";\n\n start(text: string) {\n this.text = text;\n this.current = 0;\n process.stdout.write(` ${this.text}${this.frames[0]}`);\n this.interval = setInterval(() => {\n this.current = (this.current + 1) % this.frames.length;\n process.stdout.write(`\\r ${this.text}${this.frames[this.current]}`);\n }, 300);\n }\n\n stop(finalText?: string) {\n if (this.interval) {\n clearInterval(this.interval);\n this.interval = null;\n }\n // clear the line and write the final text\n process.stdout.write(`\\x1b[2K\\r ${finalText || this.text}\\n`);\n }\n}\n"],"mappings":";;;;AAGA,IAAa,UAAb,MAAqB;CACnB,AAAQ,SAAS;EAAC;EAAO;EAAO;EAAO;EAAM;CAC7C,AAAQ,UAAU;CAClB,AAAQ,WAAkC;CAC1C,AAAQ,OAAO;CAEf,MAAM,MAAc;AAClB,OAAK,OAAO;AACZ,OAAK,UAAU;AACf,UAAQ,OAAO,MAAM,KAAK,KAAK,OAAO,KAAK,OAAO,KAAK;AACvD,OAAK,WAAW,kBAAkB;AAChC,QAAK,WAAW,KAAK,UAAU,KAAK,KAAK,OAAO;AAChD,WAAQ,OAAO,MAAM,OAAO,KAAK,OAAO,KAAK,OAAO,KAAK,WAAW;KACnE,IAAI;;CAGT,KAAK,WAAoB;AACvB,MAAI,KAAK,UAAU;AACjB,iBAAc,KAAK,SAAS;AAC5B,QAAK,WAAW;;AAGlB,UAAQ,OAAO,MAAM,cAAc,aAAa,KAAK,KAAK,IAAI"}