@databricks/appkit 0.1.4 → 0.2.0

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 (163) hide show
  1. package/AGENTS.md +89 -12
  2. package/CLAUDE.md +89 -12
  3. package/NOTICE.md +4 -0
  4. package/README.md +21 -15
  5. package/bin/appkit-lint.js +129 -0
  6. package/dist/analytics/analytics.d.ts +33 -8
  7. package/dist/analytics/analytics.d.ts.map +1 -1
  8. package/dist/analytics/analytics.js +67 -27
  9. package/dist/analytics/analytics.js.map +1 -1
  10. package/dist/analytics/defaults.js.map +1 -1
  11. package/dist/analytics/query.js +12 -6
  12. package/dist/analytics/query.js.map +1 -1
  13. package/dist/app/index.d.ts.map +1 -1
  14. package/dist/app/index.js +7 -5
  15. package/dist/app/index.js.map +1 -1
  16. package/dist/appkit/package.js +1 -1
  17. package/dist/cache/defaults.js.map +1 -1
  18. package/dist/cache/index.d.ts +1 -0
  19. package/dist/cache/index.d.ts.map +1 -1
  20. package/dist/cache/index.js +25 -5
  21. package/dist/cache/index.js.map +1 -1
  22. package/dist/cache/storage/memory.js.map +1 -1
  23. package/dist/cache/storage/persistent.js +12 -6
  24. package/dist/cache/storage/persistent.js.map +1 -1
  25. package/dist/connectors/lakebase/client.js +31 -21
  26. package/dist/connectors/lakebase/client.js.map +1 -1
  27. package/dist/connectors/lakebase/defaults.js.map +1 -1
  28. package/dist/connectors/sql-warehouse/client.js +68 -28
  29. package/dist/connectors/sql-warehouse/client.js.map +1 -1
  30. package/dist/connectors/sql-warehouse/defaults.js.map +1 -1
  31. package/dist/context/execution-context.js +75 -0
  32. package/dist/context/execution-context.js.map +1 -0
  33. package/dist/context/index.js +27 -0
  34. package/dist/context/index.js.map +1 -0
  35. package/dist/context/service-context.js +154 -0
  36. package/dist/context/service-context.js.map +1 -0
  37. package/dist/context/user-context.js +15 -0
  38. package/dist/context/user-context.js.map +1 -0
  39. package/dist/core/appkit.d.ts +3 -0
  40. package/dist/core/appkit.d.ts.map +1 -1
  41. package/dist/core/appkit.js +7 -0
  42. package/dist/core/appkit.js.map +1 -1
  43. package/dist/errors/authentication.d.ts +38 -0
  44. package/dist/errors/authentication.d.ts.map +1 -0
  45. package/dist/errors/authentication.js +48 -0
  46. package/dist/errors/authentication.js.map +1 -0
  47. package/dist/errors/base.d.ts +58 -0
  48. package/dist/errors/base.d.ts.map +1 -0
  49. package/dist/errors/base.js +70 -0
  50. package/dist/errors/base.js.map +1 -0
  51. package/dist/errors/configuration.d.ts +38 -0
  52. package/dist/errors/configuration.d.ts.map +1 -0
  53. package/dist/errors/configuration.js +45 -0
  54. package/dist/errors/configuration.js.map +1 -0
  55. package/dist/errors/connection.d.ts +42 -0
  56. package/dist/errors/connection.d.ts.map +1 -0
  57. package/dist/errors/connection.js +54 -0
  58. package/dist/errors/connection.js.map +1 -0
  59. package/dist/errors/execution.d.ts +42 -0
  60. package/dist/errors/execution.d.ts.map +1 -0
  61. package/dist/errors/execution.js +51 -0
  62. package/dist/errors/execution.js.map +1 -0
  63. package/dist/errors/index.js +28 -0
  64. package/dist/errors/index.js.map +1 -0
  65. package/dist/errors/initialization.d.ts +34 -0
  66. package/dist/errors/initialization.d.ts.map +1 -0
  67. package/dist/errors/initialization.js +42 -0
  68. package/dist/errors/initialization.js.map +1 -0
  69. package/dist/errors/server.d.ts +38 -0
  70. package/dist/errors/server.d.ts.map +1 -0
  71. package/dist/errors/server.js +45 -0
  72. package/dist/errors/server.js.map +1 -0
  73. package/dist/errors/tunnel.d.ts +38 -0
  74. package/dist/errors/tunnel.d.ts.map +1 -0
  75. package/dist/errors/tunnel.js +51 -0
  76. package/dist/errors/tunnel.js.map +1 -0
  77. package/dist/errors/validation.d.ts +36 -0
  78. package/dist/errors/validation.d.ts.map +1 -0
  79. package/dist/errors/validation.js +45 -0
  80. package/dist/errors/validation.js.map +1 -0
  81. package/dist/index.d.ts +12 -4
  82. package/dist/index.js +12 -4
  83. package/dist/index.js.map +1 -1
  84. package/dist/logging/logger.js +179 -0
  85. package/dist/logging/logger.js.map +1 -0
  86. package/dist/logging/sampling.js +56 -0
  87. package/dist/logging/sampling.js.map +1 -0
  88. package/dist/logging/wide-event-emitter.js +108 -0
  89. package/dist/logging/wide-event-emitter.js.map +1 -0
  90. package/dist/logging/wide-event.js +167 -0
  91. package/dist/logging/wide-event.js.map +1 -0
  92. package/dist/plugin/dev-reader.d.ts.map +1 -1
  93. package/dist/plugin/dev-reader.js +8 -3
  94. package/dist/plugin/dev-reader.js.map +1 -1
  95. package/dist/plugin/interceptors/cache.js.map +1 -1
  96. package/dist/plugin/interceptors/retry.js +10 -2
  97. package/dist/plugin/interceptors/retry.js.map +1 -1
  98. package/dist/plugin/interceptors/telemetry.js +24 -9
  99. package/dist/plugin/interceptors/telemetry.js.map +1 -1
  100. package/dist/plugin/interceptors/timeout.js +4 -0
  101. package/dist/plugin/interceptors/timeout.js.map +1 -1
  102. package/dist/plugin/plugin.d.ts +38 -4
  103. package/dist/plugin/plugin.d.ts.map +1 -1
  104. package/dist/plugin/plugin.js +86 -5
  105. package/dist/plugin/plugin.js.map +1 -1
  106. package/dist/plugin/to-plugin.d.ts +4 -0
  107. package/dist/plugin/to-plugin.d.ts.map +1 -1
  108. package/dist/plugin/to-plugin.js +3 -0
  109. package/dist/plugin/to-plugin.js.map +1 -1
  110. package/dist/server/index.d.ts +3 -0
  111. package/dist/server/index.d.ts.map +1 -1
  112. package/dist/server/index.js +25 -21
  113. package/dist/server/index.js.map +1 -1
  114. package/dist/server/remote-tunnel/remote-tunnel-controller.js +4 -2
  115. package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
  116. package/dist/server/remote-tunnel/remote-tunnel-manager.js +10 -8
  117. package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
  118. package/dist/server/utils.js.map +1 -1
  119. package/dist/server/vite-dev-server.js +8 -5
  120. package/dist/server/vite-dev-server.js.map +1 -1
  121. package/dist/shared/src/sql/helpers.js.map +1 -1
  122. package/dist/stream/arrow-stream-processor.js +13 -6
  123. package/dist/stream/arrow-stream-processor.js.map +1 -1
  124. package/dist/stream/buffers.js +5 -1
  125. package/dist/stream/buffers.js.map +1 -1
  126. package/dist/stream/sse-writer.js.map +1 -1
  127. package/dist/stream/stream-manager.d.ts.map +1 -1
  128. package/dist/stream/stream-manager.js +47 -36
  129. package/dist/stream/stream-manager.js.map +1 -1
  130. package/dist/stream/stream-registry.js.map +1 -1
  131. package/dist/stream/types.js.map +1 -1
  132. package/dist/telemetry/index.d.ts +2 -2
  133. package/dist/telemetry/index.js +2 -2
  134. package/dist/telemetry/instrumentations.js +14 -10
  135. package/dist/telemetry/instrumentations.js.map +1 -1
  136. package/dist/telemetry/telemetry-manager.js +8 -6
  137. package/dist/telemetry/telemetry-manager.js.map +1 -1
  138. package/dist/telemetry/trace-sampler.js +33 -0
  139. package/dist/telemetry/trace-sampler.js.map +1 -0
  140. package/dist/type-generator/index.js +4 -2
  141. package/dist/type-generator/index.js.map +1 -1
  142. package/dist/type-generator/query-registry.js +4 -2
  143. package/dist/type-generator/query-registry.js.map +1 -1
  144. package/dist/type-generator/types.js.map +1 -1
  145. package/dist/type-generator/vite-plugin.d.ts.map +1 -1
  146. package/dist/type-generator/vite-plugin.js +5 -3
  147. package/dist/type-generator/vite-plugin.js.map +1 -1
  148. package/dist/utils/env-validator.js +5 -5
  149. package/dist/utils/env-validator.js.map +1 -1
  150. package/dist/utils/merge.js +1 -5
  151. package/dist/utils/merge.js.map +1 -1
  152. package/dist/utils/path-exclusions.js +66 -0
  153. package/dist/utils/path-exclusions.js.map +1 -0
  154. package/dist/utils/vite-config-merge.js +1 -5
  155. package/dist/utils/vite-config-merge.js.map +1 -1
  156. package/llms.txt +89 -12
  157. package/package.json +6 -1
  158. package/dist/utils/databricks-client-middleware.d.ts +0 -17
  159. package/dist/utils/databricks-client-middleware.d.ts.map +0 -1
  160. package/dist/utils/databricks-client-middleware.js +0 -117
  161. package/dist/utils/databricks-client-middleware.js.map +0 -1
  162. package/dist/utils/index.js +0 -26
  163. package/dist/utils/index.js.map +0 -1
@@ -0,0 +1,108 @@
1
+ import { SeverityNumber, logs } from "@opentelemetry/api-logs";
2
+
3
+ //#region src/logging/wide-event-emitter.ts
4
+ /**
5
+ * Emits WideEvents to OpenTelemetry as structured logs
6
+ */
7
+ var WideEventEmitter = class {
8
+ constructor() {
9
+ this.logger = logs.getLogger("appkit", "1.0.0");
10
+ }
11
+ /**
12
+ * Emit a WideEvent to OpenTelemetry.
13
+ * Fails silently to avoid crashing the application due to observability issues.
14
+ */
15
+ emit(event) {
16
+ try {
17
+ const logRecord = {
18
+ timestamp: Date.parse(event.timestamp),
19
+ severityNumber: this.getSeverityNumber(event),
20
+ severityText: this.getSeverityText(event),
21
+ body: this.createLogBody(event),
22
+ attributes: this.createAttributes(event)
23
+ };
24
+ this.logger.emit(logRecord);
25
+ } catch {}
26
+ }
27
+ /**
28
+ * Get OpenTelemetry severity number based on event data
29
+ */
30
+ getSeverityNumber(event) {
31
+ if (event.error) return SeverityNumber.ERROR;
32
+ if (event.status_code) {
33
+ if (event.status_code >= 500) return SeverityNumber.ERROR;
34
+ if (event.status_code >= 400) return SeverityNumber.WARN;
35
+ }
36
+ if (event.logs) {
37
+ if (event.logs.some((log) => log.level === "error")) return SeverityNumber.ERROR;
38
+ if (event.logs.some((log) => log.level === "warn")) return SeverityNumber.WARN;
39
+ }
40
+ return SeverityNumber.INFO;
41
+ }
42
+ /**
43
+ * Get severity text based on severity number
44
+ */
45
+ getSeverityText(event) {
46
+ const severityNumber = this.getSeverityNumber(event);
47
+ if (severityNumber >= SeverityNumber.ERROR) return "ERROR";
48
+ if (severityNumber >= SeverityNumber.WARN) return "WARN";
49
+ if (severityNumber >= SeverityNumber.INFO) return "INFO";
50
+ return "DEBUG";
51
+ }
52
+ /**
53
+ * Create log body from event data
54
+ */
55
+ createLogBody(event) {
56
+ const parts = [];
57
+ if (event.method && event.path) parts.push(`${event.method} ${event.path}`);
58
+ if (event.status_code) parts.push(`→ ${event.status_code}`);
59
+ if (event.duration_ms) parts.push(`(${event.duration_ms}ms)`);
60
+ if (event.component) {
61
+ const componentStr = event.component.operation ? `${event.component.name}.${event.component.operation}` : event.component.name;
62
+ parts.push(`[${componentStr}]`);
63
+ }
64
+ if (event.error) parts.push(`ERROR: ${event.error.message}`);
65
+ return parts.join(" ");
66
+ }
67
+ /**
68
+ * Create OpenTelemetry attributes from event data
69
+ */
70
+ createAttributes(event) {
71
+ const attributes = {
72
+ request_id: event.request_id,
73
+ trace_id: event.trace_id,
74
+ "http.method": event.method,
75
+ "http.route": event.path,
76
+ "http.status_code": event.status_code,
77
+ "http.request.duration_ms": event.duration_ms,
78
+ "service.name": event.service?.name,
79
+ "service.version": event.service?.version,
80
+ "service.region": event.service?.region,
81
+ "service.deployment_id": event.service?.deployment_id,
82
+ "service.node_env": event.service?.node_env,
83
+ "component.name": event.component?.name,
84
+ "component.operation": event.component?.operation,
85
+ "user.id": event.user?.id,
86
+ "error.type": event.error?.type,
87
+ "error.code": event.error?.code,
88
+ "error.message": event.error?.message,
89
+ "error.retriable": event.error?.retriable,
90
+ "execution.timeout_ms": event.execution?.timeout_ms,
91
+ "execution.retry_attempts": event.execution?.retry_attempts,
92
+ "execution.cache_hit": event.execution?.cache_hit,
93
+ "execution.cache_key": event.execution?.cache_key,
94
+ "execution.cache_deduplication": event.execution?.cache_deduplication,
95
+ "stream.id": event.stream?.stream_id,
96
+ "stream.events_sent": event.stream?.events_sent,
97
+ log_count: event.logs?.length
98
+ };
99
+ if (event.context) {
100
+ for (const [scope, scopeData] of Object.entries(event.context)) for (const [key, value] of Object.entries(scopeData)) if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") attributes[`${scope}.${key}`] = value;
101
+ }
102
+ return Object.fromEntries(Object.entries(attributes).filter(([_, value]) => value !== void 0));
103
+ }
104
+ };
105
+
106
+ //#endregion
107
+ export { WideEventEmitter };
108
+ //# sourceMappingURL=wide-event-emitter.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wide-event-emitter.js","names":[],"sources":["../../src/logging/wide-event-emitter.ts"],"sourcesContent":["import { logs, SeverityNumber } from \"@opentelemetry/api-logs\";\nimport type { WideEventData } from \"./wide-event\";\n\n/**\n * Emits WideEvents to OpenTelemetry as structured logs\n */\nexport class WideEventEmitter {\n private logger = logs.getLogger(\"appkit\", \"1.0.0\");\n\n /**\n * Emit a WideEvent to OpenTelemetry.\n * Fails silently to avoid crashing the application due to observability issues.\n */\n emit(event: WideEventData): void {\n try {\n const logRecord = {\n timestamp: Date.parse(event.timestamp),\n severityNumber: this.getSeverityNumber(event),\n severityText: this.getSeverityText(event),\n body: this.createLogBody(event),\n attributes: this.createAttributes(event),\n };\n\n this.logger.emit(logRecord);\n } catch {\n // Silent fail - observability should never crash the application\n }\n }\n\n /**\n * Get OpenTelemetry severity number based on event data\n */\n private getSeverityNumber(event: WideEventData): SeverityNumber {\n // Error level\n if (event.error) {\n return SeverityNumber.ERROR;\n }\n\n // Status code based\n if (event.status_code) {\n if (event.status_code >= 500) {\n return SeverityNumber.ERROR;\n }\n if (event.status_code >= 400) {\n return SeverityNumber.WARN;\n }\n }\n\n // Check logs for errors/warnings\n if (event.logs) {\n const hasError = event.logs.some((log) => log.level === \"error\");\n if (hasError) {\n return SeverityNumber.ERROR;\n }\n\n const hasWarn = event.logs.some((log) => log.level === \"warn\");\n if (hasWarn) {\n return SeverityNumber.WARN;\n }\n }\n\n return SeverityNumber.INFO;\n }\n\n /**\n * Get severity text based on severity number\n */\n private getSeverityText(event: WideEventData): string {\n const severityNumber = this.getSeverityNumber(event);\n\n if (severityNumber >= SeverityNumber.ERROR) {\n return \"ERROR\";\n }\n if (severityNumber >= SeverityNumber.WARN) {\n return \"WARN\";\n }\n if (severityNumber >= SeverityNumber.INFO) {\n return \"INFO\";\n }\n return \"DEBUG\";\n }\n\n /**\n * Create log body from event data\n */\n private createLogBody(event: WideEventData): string {\n const parts: string[] = [];\n\n // HTTP request info\n if (event.method && event.path) {\n parts.push(`${event.method} ${event.path}`);\n }\n\n // Status code\n if (event.status_code) {\n parts.push(`→ ${event.status_code}`);\n }\n\n // Duration\n if (event.duration_ms) {\n parts.push(`(${event.duration_ms}ms)`);\n }\n\n // Component info\n if (event.component) {\n const componentStr = event.component.operation\n ? `${event.component.name}.${event.component.operation}`\n : event.component.name;\n parts.push(`[${componentStr}]`);\n }\n\n // Error message\n if (event.error) {\n parts.push(`ERROR: ${event.error.message}`);\n }\n\n return parts.join(\" \");\n }\n\n /**\n * Create OpenTelemetry attributes from event data\n */\n private createAttributes(\n event: WideEventData,\n ): Record<string, string | number | boolean | undefined> {\n const attributes: Record<string, string | number | boolean | undefined> = {\n // Request metadata\n request_id: event.request_id,\n trace_id: event.trace_id,\n\n // HTTP attributes (OpenTelemetry semantic conventions)\n \"http.method\": event.method,\n \"http.route\": event.path,\n \"http.status_code\": event.status_code,\n \"http.request.duration_ms\": event.duration_ms,\n\n // Service attributes\n \"service.name\": event.service?.name,\n \"service.version\": event.service?.version,\n \"service.region\": event.service?.region,\n \"service.deployment_id\": event.service?.deployment_id,\n \"service.node_env\": event.service?.node_env,\n\n // Component attributes\n \"component.name\": event.component?.name,\n \"component.operation\": event.component?.operation,\n\n // User attributes\n \"user.id\": event.user?.id,\n\n // Error attributes\n \"error.type\": event.error?.type,\n \"error.code\": event.error?.code,\n \"error.message\": event.error?.message,\n \"error.retriable\": event.error?.retriable,\n\n // Execution metadata\n \"execution.timeout_ms\": event.execution?.timeout_ms,\n \"execution.retry_attempts\": event.execution?.retry_attempts,\n \"execution.cache_hit\": event.execution?.cache_hit,\n \"execution.cache_key\": event.execution?.cache_key,\n \"execution.cache_deduplication\": event.execution?.cache_deduplication,\n\n // Stream metadata\n \"stream.id\": event.stream?.stream_id,\n \"stream.events_sent\": event.stream?.events_sent,\n\n // Log count\n log_count: event.logs?.length,\n };\n\n // Add custom context as attributes with scope prefix (no \"appkit\" prefix)\n if (event.context) {\n for (const [scope, scopeData] of Object.entries(event.context)) {\n for (const [key, value] of Object.entries(scopeData)) {\n // Only add primitive values\n if (\n typeof value === \"string\" ||\n typeof value === \"number\" ||\n typeof value === \"boolean\"\n ) {\n attributes[`${scope}.${key}`] = value;\n }\n }\n }\n }\n\n // Remove undefined values\n return Object.fromEntries(\n Object.entries(attributes).filter(([_, value]) => value !== undefined),\n );\n }\n}\n"],"mappings":";;;;;;AAMA,IAAa,mBAAb,MAA8B;;gBACX,KAAK,UAAU,UAAU,QAAQ;;;;;;CAMlD,KAAK,OAA4B;AAC/B,MAAI;GACF,MAAM,YAAY;IAChB,WAAW,KAAK,MAAM,MAAM,UAAU;IACtC,gBAAgB,KAAK,kBAAkB,MAAM;IAC7C,cAAc,KAAK,gBAAgB,MAAM;IACzC,MAAM,KAAK,cAAc,MAAM;IAC/B,YAAY,KAAK,iBAAiB,MAAM;IACzC;AAED,QAAK,OAAO,KAAK,UAAU;UACrB;;;;;CAQV,AAAQ,kBAAkB,OAAsC;AAE9D,MAAI,MAAM,MACR,QAAO,eAAe;AAIxB,MAAI,MAAM,aAAa;AACrB,OAAI,MAAM,eAAe,IACvB,QAAO,eAAe;AAExB,OAAI,MAAM,eAAe,IACvB,QAAO,eAAe;;AAK1B,MAAI,MAAM,MAAM;AAEd,OADiB,MAAM,KAAK,MAAM,QAAQ,IAAI,UAAU,QAAQ,CAE9D,QAAO,eAAe;AAIxB,OADgB,MAAM,KAAK,MAAM,QAAQ,IAAI,UAAU,OAAO,CAE5D,QAAO,eAAe;;AAI1B,SAAO,eAAe;;;;;CAMxB,AAAQ,gBAAgB,OAA8B;EACpD,MAAM,iBAAiB,KAAK,kBAAkB,MAAM;AAEpD,MAAI,kBAAkB,eAAe,MACnC,QAAO;AAET,MAAI,kBAAkB,eAAe,KACnC,QAAO;AAET,MAAI,kBAAkB,eAAe,KACnC,QAAO;AAET,SAAO;;;;;CAMT,AAAQ,cAAc,OAA8B;EAClD,MAAM,QAAkB,EAAE;AAG1B,MAAI,MAAM,UAAU,MAAM,KACxB,OAAM,KAAK,GAAG,MAAM,OAAO,GAAG,MAAM,OAAO;AAI7C,MAAI,MAAM,YACR,OAAM,KAAK,KAAK,MAAM,cAAc;AAItC,MAAI,MAAM,YACR,OAAM,KAAK,IAAI,MAAM,YAAY,KAAK;AAIxC,MAAI,MAAM,WAAW;GACnB,MAAM,eAAe,MAAM,UAAU,YACjC,GAAG,MAAM,UAAU,KAAK,GAAG,MAAM,UAAU,cAC3C,MAAM,UAAU;AACpB,SAAM,KAAK,IAAI,aAAa,GAAG;;AAIjC,MAAI,MAAM,MACR,OAAM,KAAK,UAAU,MAAM,MAAM,UAAU;AAG7C,SAAO,MAAM,KAAK,IAAI;;;;;CAMxB,AAAQ,iBACN,OACuD;EACvD,MAAM,aAAoE;GAExE,YAAY,MAAM;GAClB,UAAU,MAAM;GAGhB,eAAe,MAAM;GACrB,cAAc,MAAM;GACpB,oBAAoB,MAAM;GAC1B,4BAA4B,MAAM;GAGlC,gBAAgB,MAAM,SAAS;GAC/B,mBAAmB,MAAM,SAAS;GAClC,kBAAkB,MAAM,SAAS;GACjC,yBAAyB,MAAM,SAAS;GACxC,oBAAoB,MAAM,SAAS;GAGnC,kBAAkB,MAAM,WAAW;GACnC,uBAAuB,MAAM,WAAW;GAGxC,WAAW,MAAM,MAAM;GAGvB,cAAc,MAAM,OAAO;GAC3B,cAAc,MAAM,OAAO;GAC3B,iBAAiB,MAAM,OAAO;GAC9B,mBAAmB,MAAM,OAAO;GAGhC,wBAAwB,MAAM,WAAW;GACzC,4BAA4B,MAAM,WAAW;GAC7C,uBAAuB,MAAM,WAAW;GACxC,uBAAuB,MAAM,WAAW;GACxC,iCAAiC,MAAM,WAAW;GAGlD,aAAa,MAAM,QAAQ;GAC3B,sBAAsB,MAAM,QAAQ;GAGpC,WAAW,MAAM,MAAM;GACxB;AAGD,MAAI,MAAM,SACR;QAAK,MAAM,CAAC,OAAO,cAAc,OAAO,QAAQ,MAAM,QAAQ,CAC5D,MAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,UAAU,CAElD,KACE,OAAO,UAAU,YACjB,OAAO,UAAU,YACjB,OAAO,UAAU,UAEjB,YAAW,GAAG,MAAM,GAAG,SAAS;;AAOxC,SAAO,OAAO,YACZ,OAAO,QAAQ,WAAW,CAAC,QAAQ,CAAC,GAAG,WAAW,UAAU,OAAU,CACvE"}
@@ -0,0 +1,167 @@
1
+ //#region src/logging/wide-event.ts
2
+ /**
3
+ * WideEvent
4
+ * - Represents a single event for a request
5
+ * - Fields are camelCase to match OpenTelemetry
6
+ */
7
+ var WideEvent = class {
8
+ constructor(requestId) {
9
+ this.startTime = Date.now();
10
+ this.data = {
11
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
12
+ request_id: requestId,
13
+ service: {
14
+ name: "appkit",
15
+ version: process.env.npm_package_version || "unknown",
16
+ region: process.env.REGION,
17
+ deployment_id: process.env.DEPLOYMENT_ID,
18
+ node_env: process.env.NODE_ENV
19
+ },
20
+ logs: [],
21
+ context: {}
22
+ };
23
+ }
24
+ /**
25
+ * Set a value in the event
26
+ * @param key - The key to set
27
+ * @param value - The value to set
28
+ * @returns The event
29
+ */
30
+ set(key, value) {
31
+ if (typeof value === "object" && value !== null && !Array.isArray(value)) this.data[key] = {
32
+ ...this.data[key],
33
+ ...value
34
+ };
35
+ else this.data[key] = value;
36
+ return this;
37
+ }
38
+ /**
39
+ * Set the component name and operation.
40
+ * Component can be a plugin, connector, or service.
41
+ * @param name - The name of the component (e.g., "analytics", "sql-warehouse", "cache-manager")
42
+ * @param operation - The operation being performed (e.g., "query", "getOrExecute")
43
+ * @returns The event
44
+ */
45
+ setComponent(name, operation) {
46
+ this.data.component = {
47
+ name,
48
+ operation
49
+ };
50
+ return this;
51
+ }
52
+ /**
53
+ * Set the user context
54
+ * @param user - The user context
55
+ * @returns The event
56
+ */
57
+ setUser(user) {
58
+ this.data.user = {
59
+ ...this.data.user,
60
+ ...user
61
+ };
62
+ return this;
63
+ }
64
+ /**
65
+ * Set the execution context
66
+ * @param execution - The execution context
67
+ * @returns The event
68
+ */
69
+ setExecution(execution) {
70
+ this.data.execution = {
71
+ ...this.data.execution,
72
+ ...execution
73
+ };
74
+ return this;
75
+ }
76
+ /**
77
+ * Set the stream context
78
+ * @param stream - The stream context
79
+ * @returns The event
80
+ */
81
+ setStream(stream) {
82
+ this.data.stream = {
83
+ ...this.data.stream,
84
+ ...stream
85
+ };
86
+ return this;
87
+ }
88
+ /**
89
+ * Set the error context
90
+ * @param error - The error context
91
+ * @returns The event
92
+ */
93
+ setError(error) {
94
+ const isAppKitError = "code" in error && "statusCode" in error;
95
+ const errorCause = error.cause;
96
+ this.data.error = {
97
+ type: error.name,
98
+ code: isAppKitError ? error.code : "UNKNOWN_ERROR",
99
+ message: error.message,
100
+ retriable: isAppKitError ? error.isRetryable : false,
101
+ cause: errorCause ? String(errorCause) : void 0
102
+ };
103
+ return this;
104
+ }
105
+ /**
106
+ * Add scoped context to the event
107
+ * @param scope - The scope name (plugin, connector, or service name)
108
+ * @param ctx - Context data to merge
109
+ * @example
110
+ * event.setContext("analytics", { query_key: "apps_list", rows_returned: 100 });
111
+ * event.setContext("sql-warehouse", { warehouse_id: "1234567890" });
112
+ */
113
+ setContext(scope, ctx) {
114
+ if (!this.data.context) this.data.context = {};
115
+ this.data.context[scope] = {
116
+ ...this.data.context[scope],
117
+ ...ctx
118
+ };
119
+ return this;
120
+ }
121
+ /**
122
+ * Add a log to the event
123
+ * @param level - The level of the log
124
+ * @param message - The message of the log
125
+ * @param context - The context of the log
126
+ * @returns The event
127
+ */
128
+ addLog(level, message, context) {
129
+ if (!this.data.logs) this.data.logs = [];
130
+ this.data.logs.push({
131
+ level,
132
+ message,
133
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
134
+ context
135
+ });
136
+ if (this.data.logs.length > 50) this.data.logs = this.data.logs.slice(-50);
137
+ return this;
138
+ }
139
+ /**
140
+ * Finalize the event
141
+ * @param statusCode - The status code of the response
142
+ * @returns The event data
143
+ */
144
+ finalize(statusCode) {
145
+ this.data.status_code = statusCode;
146
+ this.data.duration_ms = this.getDurationMs();
147
+ return this.data;
148
+ }
149
+ /**
150
+ * Get the duration of the event in milliseconds
151
+ * @returns The duration of the event in milliseconds
152
+ */
153
+ getDurationMs() {
154
+ return this.data.duration_ms || Date.now() - this.startTime;
155
+ }
156
+ /**
157
+ * Convert the event to a JSON object
158
+ * @returns The event data as a JSON object
159
+ */
160
+ toJSON() {
161
+ return this.data;
162
+ }
163
+ };
164
+
165
+ //#endregion
166
+ export { WideEvent };
167
+ //# sourceMappingURL=wide-event.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"wide-event.js","names":[],"sources":["../../src/logging/wide-event.ts"],"sourcesContent":["import type { LogLevel } from \"./types\";\n\n/**\n * WideEvent data interface\n * - Represents a single event for a request\n * - Fields are camelCase to match OpenTelemetry\n */\nexport interface WideEventData {\n // request metadata\n timestamp: string;\n request_id: string;\n trace_id?: string;\n method?: string;\n path?: string;\n status_code?: number;\n duration_ms?: number;\n\n // service metadata\n service?: {\n name: string;\n version: string;\n region?: string;\n deployment_id?: string;\n node_env?: string;\n };\n\n // component metadata (plugin, connector, or service)\n component?: {\n name: string;\n operation?: string;\n };\n\n // user metadata\n user?: {\n id?: string;\n [key: string]: unknown;\n };\n\n // execution metadata\n execution?: {\n cache_hit?: boolean;\n cache_key?: string;\n cache_deduplication?: boolean;\n retry_attempts?: number;\n timeout_ms?: number;\n [key: string]: unknown;\n };\n\n // stream metadata\n stream?: {\n stream_id?: string;\n events_sent?: number;\n buffer_size?: number;\n reconnections?: number;\n [key: string]: unknown;\n };\n\n // error metadata\n error?: {\n type: string;\n code: string;\n message: string;\n retriable?: boolean;\n cause?: string;\n };\n\n // log metadata\n logs?: Array<{\n level: LogLevel;\n message: string;\n timestamp: string;\n context?: Record<string, unknown>;\n }>;\n\n /**\n * Scoped context data\n * Each scope (plugin, connector, service) can add its own namespaced data here.\n * Example: { analytics: { query_key: \"...\"}, \"sql-warehouse\": { warehouse_id: \"...\"} }\n */\n context?: Record<string, Record<string, unknown>>;\n\n [key: string]: unknown;\n}\n\n/**\n * WideEvent\n * - Represents a single event for a request\n * - Fields are camelCase to match OpenTelemetry\n */\nexport class WideEvent {\n public data: WideEventData;\n private startTime: number;\n\n constructor(requestId: string) {\n this.startTime = Date.now();\n this.data = {\n timestamp: new Date().toISOString(),\n request_id: requestId,\n service: {\n name: \"appkit\",\n version: process.env.npm_package_version || \"unknown\",\n region: process.env.REGION,\n deployment_id: process.env.DEPLOYMENT_ID,\n node_env: process.env.NODE_ENV,\n },\n logs: [],\n context: {},\n };\n }\n\n /**\n * Set a value in the event\n * @param key - The key to set\n * @param value - The value to set\n * @returns The event\n */\n set<K extends keyof WideEventData>(key: K, value: WideEventData[K]): this {\n if (typeof value === \"object\" && value !== null && !Array.isArray(value)) {\n // merge objects\n this.data[key] = {\n ...(this.data[key] as object),\n ...value,\n } as WideEventData[K];\n } else {\n this.data[key] = value;\n }\n return this;\n }\n\n /**\n * Set the component name and operation.\n * Component can be a plugin, connector, or service.\n * @param name - The name of the component (e.g., \"analytics\", \"sql-warehouse\", \"cache-manager\")\n * @param operation - The operation being performed (e.g., \"query\", \"getOrExecute\")\n * @returns The event\n */\n setComponent(name: string, operation?: string): this {\n this.data.component = { name, operation };\n return this;\n }\n\n /**\n * Set the user context\n * @param user - The user context\n * @returns The event\n */\n setUser(user: WideEventData[\"user\"]): this {\n this.data.user = { ...this.data.user, ...user };\n return this;\n }\n\n /**\n * Set the execution context\n * @param execution - The execution context\n * @returns The event\n */\n setExecution(execution: WideEventData[\"execution\"]): this {\n this.data.execution = { ...this.data.execution, ...execution };\n return this;\n }\n\n /**\n * Set the stream context\n * @param stream - The stream context\n * @returns The event\n */\n setStream(stream: WideEventData[\"stream\"]): this {\n this.data.stream = { ...this.data.stream, ...stream };\n return this;\n }\n\n /**\n * Set the error context\n * @param error - The error context\n * @returns The event\n */\n setError(error: Error): this {\n const isAppKitError = \"code\" in error && \"statusCode\" in error;\n const errorCause = (error as any).cause;\n\n this.data.error = {\n type: error.name,\n code: isAppKitError ? (error as any).code : \"UNKNOWN_ERROR\",\n message: error.message,\n retriable: isAppKitError ? (error as any).isRetryable : false,\n cause: errorCause ? String(errorCause) : undefined,\n };\n\n return this;\n }\n\n /**\n * Add scoped context to the event\n * @param scope - The scope name (plugin, connector, or service name)\n * @param ctx - Context data to merge\n * @example\n * event.setContext(\"analytics\", { query_key: \"apps_list\", rows_returned: 100 });\n * event.setContext(\"sql-warehouse\", { warehouse_id: \"1234567890\" });\n */\n setContext(scope: string, ctx: Record<string, unknown>): this {\n if (!this.data.context) {\n this.data.context = {};\n }\n\n this.data.context[scope] = {\n ...this.data.context[scope],\n ...ctx,\n };\n\n return this;\n }\n\n /**\n * Add a log to the event\n * @param level - The level of the log\n * @param message - The message of the log\n * @param context - The context of the log\n * @returns The event\n */\n addLog(\n level: LogLevel,\n message: string,\n context?: Record<string, unknown>,\n ): this {\n if (!this.data.logs) {\n this.data.logs = [];\n }\n\n this.data.logs.push({\n level,\n message,\n timestamp: new Date().toISOString(),\n context,\n });\n\n // Keep only last 50 logs to prevent unbounded growth\n if (this.data.logs.length > 50) {\n this.data.logs = this.data.logs.slice(-50);\n }\n\n return this;\n }\n\n /**\n * Finalize the event\n * @param statusCode - The status code of the response\n * @returns The event data\n */\n finalize(statusCode: number): WideEventData {\n this.data.status_code = statusCode;\n this.data.duration_ms = this.getDurationMs();\n return this.data;\n }\n\n /**\n * Get the duration of the event in milliseconds\n * @returns The duration of the event in milliseconds\n */\n getDurationMs(): number {\n return this.data.duration_ms || Date.now() - this.startTime;\n }\n\n /**\n * Convert the event to a JSON object\n * @returns The event data as a JSON object\n */\n toJSON(): WideEventData {\n return this.data;\n }\n}\n"],"mappings":";;;;;;AAyFA,IAAa,YAAb,MAAuB;CAIrB,YAAY,WAAmB;AAC7B,OAAK,YAAY,KAAK,KAAK;AAC3B,OAAK,OAAO;GACV,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC,YAAY;GACZ,SAAS;IACP,MAAM;IACN,SAAS,QAAQ,IAAI,uBAAuB;IAC5C,QAAQ,QAAQ,IAAI;IACpB,eAAe,QAAQ,IAAI;IAC3B,UAAU,QAAQ,IAAI;IACvB;GACD,MAAM,EAAE;GACR,SAAS,EAAE;GACZ;;;;;;;;CASH,IAAmC,KAAQ,OAA+B;AACxE,MAAI,OAAO,UAAU,YAAY,UAAU,QAAQ,CAAC,MAAM,QAAQ,MAAM,CAEtE,MAAK,KAAK,OAAO;GACf,GAAI,KAAK,KAAK;GACd,GAAG;GACJ;MAED,MAAK,KAAK,OAAO;AAEnB,SAAO;;;;;;;;;CAUT,aAAa,MAAc,WAA0B;AACnD,OAAK,KAAK,YAAY;GAAE;GAAM;GAAW;AACzC,SAAO;;;;;;;CAQT,QAAQ,MAAmC;AACzC,OAAK,KAAK,OAAO;GAAE,GAAG,KAAK,KAAK;GAAM,GAAG;GAAM;AAC/C,SAAO;;;;;;;CAQT,aAAa,WAA6C;AACxD,OAAK,KAAK,YAAY;GAAE,GAAG,KAAK,KAAK;GAAW,GAAG;GAAW;AAC9D,SAAO;;;;;;;CAQT,UAAU,QAAuC;AAC/C,OAAK,KAAK,SAAS;GAAE,GAAG,KAAK,KAAK;GAAQ,GAAG;GAAQ;AACrD,SAAO;;;;;;;CAQT,SAAS,OAAoB;EAC3B,MAAM,gBAAgB,UAAU,SAAS,gBAAgB;EACzD,MAAM,aAAc,MAAc;AAElC,OAAK,KAAK,QAAQ;GAChB,MAAM,MAAM;GACZ,MAAM,gBAAiB,MAAc,OAAO;GAC5C,SAAS,MAAM;GACf,WAAW,gBAAiB,MAAc,cAAc;GACxD,OAAO,aAAa,OAAO,WAAW,GAAG;GAC1C;AAED,SAAO;;;;;;;;;;CAWT,WAAW,OAAe,KAAoC;AAC5D,MAAI,CAAC,KAAK,KAAK,QACb,MAAK,KAAK,UAAU,EAAE;AAGxB,OAAK,KAAK,QAAQ,SAAS;GACzB,GAAG,KAAK,KAAK,QAAQ;GACrB,GAAG;GACJ;AAED,SAAO;;;;;;;;;CAUT,OACE,OACA,SACA,SACM;AACN,MAAI,CAAC,KAAK,KAAK,KACb,MAAK,KAAK,OAAO,EAAE;AAGrB,OAAK,KAAK,KAAK,KAAK;GAClB;GACA;GACA,4BAAW,IAAI,MAAM,EAAC,aAAa;GACnC;GACD,CAAC;AAGF,MAAI,KAAK,KAAK,KAAK,SAAS,GAC1B,MAAK,KAAK,OAAO,KAAK,KAAK,KAAK,MAAM,IAAI;AAG5C,SAAO;;;;;;;CAQT,SAAS,YAAmC;AAC1C,OAAK,KAAK,cAAc;AACxB,OAAK,KAAK,cAAc,KAAK,eAAe;AAC5C,SAAO,KAAK;;;;;;CAOd,gBAAwB;AACtB,SAAO,KAAK,KAAK,eAAe,KAAK,KAAK,GAAG,KAAK;;;;;;CAOpD,SAAwB;AACtB,SAAO,KAAK"}
@@ -1 +1 @@
1
- {"version":3,"file":"dev-reader.d.ts","names":[],"sources":["../../src/plugin/dev-reader.ts"],"sourcesContent":[],"mappings":";;;;KAIK,sBAAA,SAAsB,QAAA,CACF,YACpB;;;AAL0C;;AAGpB,cAQd,aAAA,CAPY;iBACpB,QAAA;EAAgB,QAAA,mBAAA;EAMR,QAAA,WAAa,CAAA;EAAA,OAAA,WAAA,CAAA,CAAA,EAMF,aANE;sBAMF,CAAA,MAAA,EA+BO,sBA/BP,CAAA,EAAA,IAAA;UA+BO,CAAA,QAAA,EAAA,MAAA,EAAA,GAAA,EAAsB,QAAA,CAM1B,OANI,CAAA,EAO1B,OAP0B,CAAA,MAAA,CAAA"}
1
+ {"version":3,"file":"dev-reader.d.ts","names":[],"sources":["../../src/plugin/dev-reader.ts"],"sourcesContent":[],"mappings":";;;;KAQK,sBAAA,SAAsB,QAAA,CACF,YACpB;;;AAT0C;;AAOpB,cAQd,aAAA,CAPY;iBACpB,QAAA;EAAgB,QAAA,mBAAA;EAMR,QAAA,WAAa,CAAA;EAAA,OAAA,WAAA,CAAA,CAAA,EAMF,aANE;sBAMF,CAAA,MAAA,EA+BO,sBA/BP,CAAA,EAAA,IAAA;UA+BO,CAAA,QAAA,EAAA,MAAA,EAAA,GAAA,EAAsB,QAAA,CAM1B,OANI,CAAA,EAO1B,OAP0B,CAAA,MAAA,CAAA"}
@@ -1,7 +1,12 @@
1
+ import { createLogger } from "../logging/logger.js";
2
+ import { TunnelError } from "../errors/tunnel.js";
3
+ import { init_errors } from "../errors/index.js";
1
4
  import { isRemoteTunnelAllowedByEnv } from "../server/remote-tunnel/gate.js";
2
5
  import { randomUUID } from "node:crypto";
3
6
 
4
7
  //#region src/plugin/dev-reader.ts
8
+ init_errors();
9
+ const logger = createLogger("plugin:dev-reader");
5
10
  /**
6
11
  * This class is used to read files from the local filesystem in dev mode
7
12
  * through the WebSocket tunnel.
@@ -19,7 +24,7 @@ var DevFileReader = class DevFileReader {
19
24
  if (isRemoteTunnelAllowedByEnv()) return Reflect.get(target, prop, receiver);
20
25
  const value = Reflect.get(target, prop, receiver);
21
26
  if (typeof value === "function") return function noop() {
22
- console.info(`Noop: ${String(prop)} (remote server disabled)`);
27
+ logger.debug("Noop: %s (remote server disabled)", String(prop));
23
28
  return Promise.resolve("");
24
29
  };
25
30
  return value;
@@ -34,9 +39,9 @@ var DevFileReader = class DevFileReader {
34
39
  this.getTunnelForRequest = getter;
35
40
  }
36
41
  async readFile(filePath, req) {
37
- if (!this.getTunnelForRequest) throw new Error("Tunnel getter not registered for DevFileReader singleton");
42
+ if (!this.getTunnelForRequest) throw TunnelError.getterNotRegistered();
38
43
  const tunnel = this.getTunnelForRequest(req);
39
- if (!tunnel) throw new Error("No tunnel connection available for file read");
44
+ if (!tunnel) throw TunnelError.noConnection();
40
45
  const { ws, pendingFileReads } = tunnel;
41
46
  const requestId = randomUUID();
42
47
  return new Promise((resolve, reject) => {
@@ -1 +1 @@
1
- {"version":3,"file":"dev-reader.js","names":[],"sources":["../../src/plugin/dev-reader.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { TunnelConnection } from \"shared\";\nimport { isRemoteTunnelAllowedByEnv } from \"@/server/remote-tunnel/gate\";\n\ntype TunnelConnectionGetter = (\n req: import(\"express\").Request,\n) => TunnelConnection | null;\n\n/**\n * This class is used to read files from the local filesystem in dev mode\n * through the WebSocket tunnel.\n */\nexport class DevFileReader {\n private static instance: DevFileReader | null = null;\n private getTunnelForRequest: TunnelConnectionGetter | null = null;\n\n private constructor() {}\n\n static getInstance(): DevFileReader {\n if (!DevFileReader.instance) {\n DevFileReader.instance = new Proxy(new DevFileReader(), {\n /**\n * We proxy the reader to return a noop function if the remote server is disabled.\n */\n get(target, prop, receiver) {\n if (isRemoteTunnelAllowedByEnv()) {\n return Reflect.get(target, prop, receiver);\n }\n\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value === \"function\") {\n return function noop() {\n console.info(`Noop: ${String(prop)} (remote server disabled)`);\n return Promise.resolve(\"\");\n };\n }\n\n return value;\n },\n set(target, prop, value, receiver) {\n return Reflect.set(target, prop, value, receiver);\n },\n });\n }\n\n return DevFileReader.instance;\n }\n\n registerTunnelGetter(getter: TunnelConnectionGetter) {\n this.getTunnelForRequest = getter;\n }\n\n async readFile(\n filePath: string,\n req: import(\"express\").Request,\n ): Promise<string> {\n if (!this.getTunnelForRequest) {\n throw new Error(\n \"Tunnel getter not registered for DevFileReader singleton\",\n );\n }\n const tunnel = this.getTunnelForRequest(req);\n\n if (!tunnel) {\n throw new Error(\"No tunnel connection available for file read\");\n }\n\n const { ws, pendingFileReads } = tunnel;\n const requestId = randomUUID();\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n pendingFileReads.delete(requestId);\n reject(new Error(`File read timeout: ${filePath}`));\n }, 10000);\n\n pendingFileReads.set(requestId, { resolve, reject, timeout });\n\n ws.send(\n JSON.stringify({\n type: \"file:read\",\n requestId,\n path: filePath,\n }),\n );\n });\n }\n}\n"],"mappings":";;;;;;;;AAYA,IAAa,gBAAb,MAAa,cAAc;;kBACuB;;CAGhD,AAAQ,cAAc;6BAFuC;;CAI7D,OAAO,cAA6B;AAClC,MAAI,CAAC,cAAc,SACjB,eAAc,WAAW,IAAI,MAAM,IAAI,eAAe,EAAE;GAItD,IAAI,QAAQ,MAAM,UAAU;AAC1B,QAAI,4BAA4B,CAC9B,QAAO,QAAQ,IAAI,QAAQ,MAAM,SAAS;IAG5C,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS;AAEjD,QAAI,OAAO,UAAU,WACnB,QAAO,SAAS,OAAO;AACrB,aAAQ,KAAK,SAAS,OAAO,KAAK,CAAC,2BAA2B;AAC9D,YAAO,QAAQ,QAAQ,GAAG;;AAI9B,WAAO;;GAET,IAAI,QAAQ,MAAM,OAAO,UAAU;AACjC,WAAO,QAAQ,IAAI,QAAQ,MAAM,OAAO,SAAS;;GAEpD,CAAC;AAGJ,SAAO,cAAc;;CAGvB,qBAAqB,QAAgC;AACnD,OAAK,sBAAsB;;CAG7B,MAAM,SACJ,UACA,KACiB;AACjB,MAAI,CAAC,KAAK,oBACR,OAAM,IAAI,MACR,2DACD;EAEH,MAAM,SAAS,KAAK,oBAAoB,IAAI;AAE5C,MAAI,CAAC,OACH,OAAM,IAAI,MAAM,+CAA+C;EAGjE,MAAM,EAAE,IAAI,qBAAqB;EACjC,MAAM,YAAY,YAAY;AAE9B,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAU,iBAAiB;AAC/B,qBAAiB,OAAO,UAAU;AAClC,2BAAO,IAAI,MAAM,sBAAsB,WAAW,CAAC;MAClD,IAAM;AAET,oBAAiB,IAAI,WAAW;IAAE;IAAS;IAAQ;IAAS,CAAC;AAE7D,MAAG,KACD,KAAK,UAAU;IACb,MAAM;IACN;IACA,MAAM;IACP,CAAC,CACH;IACD"}
1
+ {"version":3,"file":"dev-reader.js","names":[],"sources":["../../src/plugin/dev-reader.ts"],"sourcesContent":["import { randomUUID } from \"node:crypto\";\nimport type { TunnelConnection } from \"shared\";\nimport { isRemoteTunnelAllowedByEnv } from \"@/server/remote-tunnel/gate\";\nimport { TunnelError } from \"../errors\";\nimport { createLogger } from \"../logging/logger\";\n\nconst logger = createLogger(\"plugin:dev-reader\");\n\ntype TunnelConnectionGetter = (\n req: import(\"express\").Request,\n) => TunnelConnection | null;\n\n/**\n * This class is used to read files from the local filesystem in dev mode\n * through the WebSocket tunnel.\n */\nexport class DevFileReader {\n private static instance: DevFileReader | null = null;\n private getTunnelForRequest: TunnelConnectionGetter | null = null;\n\n private constructor() {}\n\n static getInstance(): DevFileReader {\n if (!DevFileReader.instance) {\n DevFileReader.instance = new Proxy(new DevFileReader(), {\n /**\n * We proxy the reader to return a noop function if the remote server is disabled.\n */\n get(target, prop, receiver) {\n if (isRemoteTunnelAllowedByEnv()) {\n return Reflect.get(target, prop, receiver);\n }\n\n const value = Reflect.get(target, prop, receiver);\n\n if (typeof value === \"function\") {\n return function noop() {\n logger.debug(\"Noop: %s (remote server disabled)\", String(prop));\n return Promise.resolve(\"\");\n };\n }\n\n return value;\n },\n set(target, prop, value, receiver) {\n return Reflect.set(target, prop, value, receiver);\n },\n });\n }\n\n return DevFileReader.instance;\n }\n\n registerTunnelGetter(getter: TunnelConnectionGetter) {\n this.getTunnelForRequest = getter;\n }\n\n async readFile(\n filePath: string,\n req: import(\"express\").Request,\n ): Promise<string> {\n if (!this.getTunnelForRequest) {\n throw TunnelError.getterNotRegistered();\n }\n const tunnel = this.getTunnelForRequest(req);\n\n if (!tunnel) {\n throw TunnelError.noConnection();\n }\n\n const { ws, pendingFileReads } = tunnel;\n const requestId = randomUUID();\n\n return new Promise((resolve, reject) => {\n const timeout = setTimeout(() => {\n pendingFileReads.delete(requestId);\n reject(new Error(`File read timeout: ${filePath}`));\n }, 10000);\n\n pendingFileReads.set(requestId, { resolve, reject, timeout });\n\n ws.send(\n JSON.stringify({\n type: \"file:read\",\n requestId,\n path: filePath,\n }),\n );\n });\n }\n}\n"],"mappings":";;;;;;;aAGwC;AAGxC,MAAM,SAAS,aAAa,oBAAoB;;;;;AAUhD,IAAa,gBAAb,MAAa,cAAc;;kBACuB;;CAGhD,AAAQ,cAAc;6BAFuC;;CAI7D,OAAO,cAA6B;AAClC,MAAI,CAAC,cAAc,SACjB,eAAc,WAAW,IAAI,MAAM,IAAI,eAAe,EAAE;GAItD,IAAI,QAAQ,MAAM,UAAU;AAC1B,QAAI,4BAA4B,CAC9B,QAAO,QAAQ,IAAI,QAAQ,MAAM,SAAS;IAG5C,MAAM,QAAQ,QAAQ,IAAI,QAAQ,MAAM,SAAS;AAEjD,QAAI,OAAO,UAAU,WACnB,QAAO,SAAS,OAAO;AACrB,YAAO,MAAM,qCAAqC,OAAO,KAAK,CAAC;AAC/D,YAAO,QAAQ,QAAQ,GAAG;;AAI9B,WAAO;;GAET,IAAI,QAAQ,MAAM,OAAO,UAAU;AACjC,WAAO,QAAQ,IAAI,QAAQ,MAAM,OAAO,SAAS;;GAEpD,CAAC;AAGJ,SAAO,cAAc;;CAGvB,qBAAqB,QAAgC;AACnD,OAAK,sBAAsB;;CAG7B,MAAM,SACJ,UACA,KACiB;AACjB,MAAI,CAAC,KAAK,oBACR,OAAM,YAAY,qBAAqB;EAEzC,MAAM,SAAS,KAAK,oBAAoB,IAAI;AAE5C,MAAI,CAAC,OACH,OAAM,YAAY,cAAc;EAGlC,MAAM,EAAE,IAAI,qBAAqB;EACjC,MAAM,YAAY,YAAY;AAE9B,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,MAAM,UAAU,iBAAiB;AAC/B,qBAAiB,OAAO,UAAU;AAClC,2BAAO,IAAI,MAAM,sBAAsB,WAAW,CAAC;MAClD,IAAM;AAET,oBAAiB,IAAI,WAAW;IAAE;IAAS;IAAQ;IAAS,CAAC;AAE7D,MAAG,KACD,KAAK,UAAU;IACb,MAAM;IACN;IACA,MAAM;IACP,CAAC,CACH;IACD"}
@@ -1 +1 @@
1
- {"version":3,"file":"cache.js","names":["cacheManager: CacheManager","config: CacheConfig"],"sources":["../../../src/plugin/interceptors/cache.ts"],"sourcesContent":["import type { CacheManager } from \"../../cache\";\nimport type { CacheConfig } from \"shared\";\nimport type { ExecutionContext, ExecutionInterceptor } from \"./types\";\n\n// interceptor to handle caching logic\nexport class CacheInterceptor implements ExecutionInterceptor {\n constructor(\n private cacheManager: CacheManager,\n private config: CacheConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: ExecutionContext,\n ): Promise<T> {\n // if cache disabled, ignore\n if (!this.config.enabled || !this.config.cacheKey?.length) {\n return fn();\n }\n\n return this.cacheManager.getOrExecute(\n this.config.cacheKey,\n fn,\n context.userKey,\n { ttl: this.config.ttl },\n );\n }\n}\n"],"mappings":";AAKA,IAAa,mBAAb,MAA8D;CAC5D,YACE,AAAQA,cACR,AAAQC,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,SACY;AAEZ,MAAI,CAAC,KAAK,OAAO,WAAW,CAAC,KAAK,OAAO,UAAU,OACjD,QAAO,IAAI;AAGb,SAAO,KAAK,aAAa,aACvB,KAAK,OAAO,UACZ,IACA,QAAQ,SACR,EAAE,KAAK,KAAK,OAAO,KAAK,CACzB"}
1
+ {"version":3,"file":"cache.js","names":[],"sources":["../../../src/plugin/interceptors/cache.ts"],"sourcesContent":["import type { CacheConfig } from \"shared\";\nimport type { CacheManager } from \"../../cache\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\n// interceptor to handle caching logic\nexport class CacheInterceptor implements ExecutionInterceptor {\n constructor(\n private cacheManager: CacheManager,\n private config: CacheConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n // if cache disabled, ignore\n if (!this.config.enabled || !this.config.cacheKey?.length) {\n return fn();\n }\n\n return this.cacheManager.getOrExecute(\n this.config.cacheKey,\n fn,\n context.userKey,\n { ttl: this.config.ttl },\n );\n }\n}\n"],"mappings":";AAKA,IAAa,mBAAb,MAA8D;CAC5D,YACE,AAAQ,cACR,AAAQ,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,SACY;AAEZ,MAAI,CAAC,KAAK,OAAO,WAAW,CAAC,KAAK,OAAO,UAAU,OACjD,QAAO,IAAI;AAGb,SAAO,KAAK,aAAa,aACvB,KAAK,OAAO,UACZ,IACA,QAAQ,SACR,EAAE,KAAK,KAAK,OAAO,KAAK,CACzB"}
@@ -1,4 +1,7 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+
1
3
  //#region src/plugin/interceptors/retry.ts
4
+ const logger = createLogger("interceptors:retry");
2
5
  var RetryInterceptor = class {
3
6
  constructor(config) {
4
7
  this.attempts = config.attempts ?? 3;
@@ -8,10 +11,15 @@ var RetryInterceptor = class {
8
11
  async intercept(fn, context) {
9
12
  let lastError;
10
13
  for (let attempt = 1; attempt <= this.attempts; attempt++) try {
11
- return await fn();
14
+ const result = await fn();
15
+ if (attempt > 1) logger.event()?.setExecution({ retry_attempts: attempt - 1 });
16
+ return result;
12
17
  } catch (error) {
13
18
  lastError = error;
14
- if (attempt === this.attempts) throw error;
19
+ if (attempt === this.attempts) {
20
+ logger.event()?.setExecution({ retry_attempts: attempt - 1 });
21
+ throw error;
22
+ }
15
23
  if (context.signal?.aborted) throw error;
16
24
  const delay = this.calculateDelay(attempt);
17
25
  await this.sleep(delay);
@@ -1 +1 @@
1
- {"version":3,"file":"retry.js","names":["lastError: Error | unknown"],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport type { ExecutionContext, ExecutionInterceptor } from \"./types\";\n\n// interceptor to handle retry logic\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: ExecutionContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n return await fn();\n } catch (error) {\n lastError = error;\n\n // last attempt, rethrow the error\n if (attempt === this.attempts) {\n throw error;\n }\n\n // don't retry if was already aborted\n if (context.signal?.aborted) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n // type guard\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n // exponential backoff\n const delay = this.initialDelay * 2 ** (attempt - 1);\n\n // max delay cap\n return Math.min(delay, this.maxDelay);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";AAIA,IAAa,mBAAb,MAA8D;CAK5D,YAAY,QAAqB;AAC/B,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,WAAW,OAAO,YAAY;;CAGrC,MAAM,UACJ,IACA,SACY;EACZ,IAAIA;AAEJ,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,UAAU,UAC9C,KAAI;AACF,UAAO,MAAM,IAAI;WACV,OAAO;AACd,eAAY;AAGZ,OAAI,YAAY,KAAK,SACnB,OAAM;AAIR,OAAI,QAAQ,QAAQ,QAClB,OAAM;GAGR,MAAM,QAAQ,KAAK,eAAe,QAAQ;AAC1C,SAAM,KAAK,MAAM,MAAM;;AAK3B,QAAM;;CAGR,AAAQ,eAAe,SAAyB;EAE9C,MAAM,QAAQ,KAAK,eAAe,MAAM,UAAU;AAGlD,SAAO,KAAK,IAAI,OAAO,KAAK,SAAS;;CAGvC,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
1
+ {"version":3,"file":"retry.js","names":[],"sources":["../../../src/plugin/interceptors/retry.ts"],"sourcesContent":["import type { RetryConfig } from \"shared\";\nimport { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:retry\");\n\n// interceptor to handle retry logic\nexport class RetryInterceptor implements ExecutionInterceptor {\n private attempts: number;\n private initialDelay: number;\n private maxDelay: number;\n\n constructor(config: RetryConfig) {\n this.attempts = config.attempts ?? 3;\n this.initialDelay = config.initialDelay ?? 1000;\n this.maxDelay = config.maxDelay ?? 30000;\n }\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n let lastError: Error | unknown;\n\n for (let attempt = 1; attempt <= this.attempts; attempt++) {\n try {\n const result = await fn();\n\n if (attempt > 1) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n }\n\n return result;\n } catch (error) {\n lastError = error;\n\n // last attempt, rethrow the error\n if (attempt === this.attempts) {\n logger.event()?.setExecution({\n retry_attempts: attempt - 1,\n });\n throw error;\n }\n\n // don't retry if was already aborted\n if (context.signal?.aborted) {\n throw error;\n }\n\n const delay = this.calculateDelay(attempt);\n await this.sleep(delay);\n }\n }\n\n // type guard\n throw lastError;\n }\n\n private calculateDelay(attempt: number): number {\n // exponential backoff\n const delay = this.initialDelay * 2 ** (attempt - 1);\n\n // max delay cap\n return Math.min(delay, this.maxDelay);\n }\n\n private sleep(ms: number): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, ms));\n }\n}\n"],"mappings":";;;AAIA,MAAM,SAAS,aAAa,qBAAqB;AAGjD,IAAa,mBAAb,MAA8D;CAK5D,YAAY,QAAqB;AAC/B,OAAK,WAAW,OAAO,YAAY;AACnC,OAAK,eAAe,OAAO,gBAAgB;AAC3C,OAAK,WAAW,OAAO,YAAY;;CAGrC,MAAM,UACJ,IACA,SACY;EACZ,IAAI;AAEJ,OAAK,IAAI,UAAU,GAAG,WAAW,KAAK,UAAU,UAC9C,KAAI;GACF,MAAM,SAAS,MAAM,IAAI;AAEzB,OAAI,UAAU,EACZ,QAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AAGJ,UAAO;WACA,OAAO;AACd,eAAY;AAGZ,OAAI,YAAY,KAAK,UAAU;AAC7B,WAAO,OAAO,EAAE,aAAa,EAC3B,gBAAgB,UAAU,GAC3B,CAAC;AACF,UAAM;;AAIR,OAAI,QAAQ,QAAQ,QAClB,OAAM;GAGR,MAAM,QAAQ,KAAK,eAAe,QAAQ;AAC1C,SAAM,KAAK,MAAM,MAAM;;AAK3B,QAAM;;CAGR,AAAQ,eAAe,SAAyB;EAE9C,MAAM,QAAQ,KAAK,eAAe,MAAM,UAAU;AAGlD,SAAO,KAAK,IAAI,OAAO,KAAK,SAAS;;CAGvC,AAAQ,MAAM,IAA2B;AACvC,SAAO,IAAI,SAAS,YAAY,WAAW,SAAS,GAAG,CAAC"}
@@ -1,28 +1,43 @@
1
1
  import { SpanStatusCode } from "../../telemetry/index.js";
2
2
 
3
3
  //#region src/plugin/interceptors/telemetry.ts
4
- /**
5
- * Interceptor to automatically instrument plugin executions with telemetry spans.
6
- * Wraps the execution in a span and handles success/error status.
7
- */
8
4
  var TelemetryInterceptor = class {
9
5
  constructor(telemetry, config) {
10
6
  this.telemetry = telemetry;
11
7
  this.config = config;
12
8
  }
13
- async intercept(fn, _context) {
9
+ async intercept(fn, context) {
14
10
  const spanName = this.config?.spanName || "plugin.execute";
11
+ if (context.signal?.aborted) throw new Error("Operation aborted before execution");
15
12
  return this.telemetry.startActiveSpan(spanName, { attributes: this.config?.attributes }, async (span) => {
13
+ let abortHandler;
14
+ let isAborted = false;
15
+ if (context.signal) {
16
+ abortHandler = () => {
17
+ if (!span.isRecording()) return;
18
+ isAborted = true;
19
+ span.setAttribute("cancelled", true);
20
+ span.setStatus({
21
+ code: SpanStatusCode.ERROR,
22
+ message: "Operation cancelled by client"
23
+ });
24
+ span.end();
25
+ };
26
+ context.signal.addEventListener("abort", abortHandler, { once: true });
27
+ }
16
28
  try {
17
29
  const result = await fn();
18
- span.setStatus({ code: SpanStatusCode.OK });
30
+ if (!isAborted) span.setStatus({ code: SpanStatusCode.OK });
19
31
  return result;
20
32
  } catch (error) {
21
- span.recordException(error);
22
- span.setStatus({ code: SpanStatusCode.ERROR });
33
+ if (!isAborted) {
34
+ span.recordException(error);
35
+ span.setStatus({ code: SpanStatusCode.ERROR });
36
+ }
23
37
  throw error;
24
38
  } finally {
25
- span.end();
39
+ if (abortHandler && context.signal) context.signal.removeEventListener("abort", abortHandler);
40
+ if (!isAborted) span.end();
26
41
  }
27
42
  });
28
43
  }
@@ -1 +1 @@
1
- {"version":3,"file":"telemetry.js","names":["telemetry: ITelemetry","config?: TelemetryConfig"],"sources":["../../../src/plugin/interceptors/telemetry.ts"],"sourcesContent":["import type { ITelemetry, Span } from \"../../telemetry\";\nimport { SpanStatusCode } from \"../../telemetry\";\nimport type { TelemetryConfig } from \"shared\";\nimport type { ExecutionContext, ExecutionInterceptor } from \"./types\";\n\n/**\n * Interceptor to automatically instrument plugin executions with telemetry spans.\n * Wraps the execution in a span and handles success/error status.\n */\nexport class TelemetryInterceptor implements ExecutionInterceptor {\n constructor(\n private telemetry: ITelemetry,\n private config?: TelemetryConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n _context: ExecutionContext,\n ): Promise<T> {\n const spanName = this.config?.spanName || \"plugin.execute\";\n return this.telemetry.startActiveSpan(\n spanName,\n { attributes: this.config?.attributes },\n async (span: Span) => {\n try {\n const result = await fn();\n span.setStatus({ code: SpanStatusCode.OK });\n return result;\n } catch (error) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n throw error;\n } finally {\n span.end();\n }\n },\n );\n }\n}\n"],"mappings":";;;;;;;AASA,IAAa,uBAAb,MAAkE;CAChE,YACE,AAAQA,WACR,AAAQC,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,UACY;EACZ,MAAM,WAAW,KAAK,QAAQ,YAAY;AAC1C,SAAO,KAAK,UAAU,gBACpB,UACA,EAAE,YAAY,KAAK,QAAQ,YAAY,EACvC,OAAO,SAAe;AACpB,OAAI;IACF,MAAM,SAAS,MAAM,IAAI;AACzB,SAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAC3C,WAAO;YACA,OAAO;AACd,SAAK,gBAAgB,MAAe;AACpC,SAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;AAC9C,UAAM;aACE;AACR,SAAK,KAAK;;IAGf"}
1
+ {"version":3,"file":"telemetry.js","names":[],"sources":["../../../src/plugin/interceptors/telemetry.ts"],"sourcesContent":["import type { TelemetryConfig } from \"shared\";\nimport type { ITelemetry, Span } from \"../../telemetry\";\nimport { SpanStatusCode } from \"../../telemetry\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nexport class TelemetryInterceptor implements ExecutionInterceptor {\n constructor(\n private telemetry: ITelemetry,\n private config?: TelemetryConfig,\n ) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n const spanName = this.config?.spanName || \"plugin.execute\";\n\n // abort operation if signal is aborted\n if (context.signal?.aborted) {\n throw new Error(\"Operation aborted before execution\");\n }\n\n return this.telemetry.startActiveSpan(\n spanName,\n { attributes: this.config?.attributes },\n async (span: Span) => {\n let abortHandler: (() => void) | undefined;\n let isAborted = false;\n\n if (context.signal) {\n abortHandler = () => {\n // abort span if not recording\n if (!span.isRecording()) return;\n isAborted = true;\n span.setAttribute(\"cancelled\", true);\n span.setStatus({\n code: SpanStatusCode.ERROR,\n message: \"Operation cancelled by client\",\n });\n span.end();\n };\n context.signal.addEventListener(\"abort\", abortHandler, {\n once: true,\n });\n }\n\n try {\n const result = await fn();\n if (!isAborted) {\n span.setStatus({ code: SpanStatusCode.OK });\n }\n return result;\n } catch (error) {\n if (!isAborted) {\n span.recordException(error as Error);\n span.setStatus({ code: SpanStatusCode.ERROR });\n }\n throw error;\n } finally {\n if (abortHandler && context.signal) {\n context.signal.removeEventListener(\"abort\", abortHandler);\n }\n if (!isAborted) {\n span.end();\n }\n }\n },\n );\n }\n}\n"],"mappings":";;;AAKA,IAAa,uBAAb,MAAkE;CAChE,YACE,AAAQ,WACR,AAAQ,QACR;EAFQ;EACA;;CAGV,MAAM,UACJ,IACA,SACY;EACZ,MAAM,WAAW,KAAK,QAAQ,YAAY;AAG1C,MAAI,QAAQ,QAAQ,QAClB,OAAM,IAAI,MAAM,qCAAqC;AAGvD,SAAO,KAAK,UAAU,gBACpB,UACA,EAAE,YAAY,KAAK,QAAQ,YAAY,EACvC,OAAO,SAAe;GACpB,IAAI;GACJ,IAAI,YAAY;AAEhB,OAAI,QAAQ,QAAQ;AAClB,yBAAqB;AAEnB,SAAI,CAAC,KAAK,aAAa,CAAE;AACzB,iBAAY;AACZ,UAAK,aAAa,aAAa,KAAK;AACpC,UAAK,UAAU;MACb,MAAM,eAAe;MACrB,SAAS;MACV,CAAC;AACF,UAAK,KAAK;;AAEZ,YAAQ,OAAO,iBAAiB,SAAS,cAAc,EACrD,MAAM,MACP,CAAC;;AAGJ,OAAI;IACF,MAAM,SAAS,MAAM,IAAI;AACzB,QAAI,CAAC,UACH,MAAK,UAAU,EAAE,MAAM,eAAe,IAAI,CAAC;AAE7C,WAAO;YACA,OAAO;AACd,QAAI,CAAC,WAAW;AACd,UAAK,gBAAgB,MAAe;AACpC,UAAK,UAAU,EAAE,MAAM,eAAe,OAAO,CAAC;;AAEhD,UAAM;aACE;AACR,QAAI,gBAAgB,QAAQ,OAC1B,SAAQ,OAAO,oBAAoB,SAAS,aAAa;AAE3D,QAAI,CAAC,UACH,MAAK,KAAK;;IAIjB"}
@@ -1,9 +1,13 @@
1
+ import { createLogger } from "../../logging/logger.js";
2
+
1
3
  //#region src/plugin/interceptors/timeout.ts
4
+ const logger = createLogger("interceptors:timeout");
2
5
  var TimeoutInterceptor = class {
3
6
  constructor(timeoutMs) {
4
7
  this.timeoutMs = timeoutMs;
5
8
  }
6
9
  async intercept(fn, context) {
10
+ logger.event()?.setExecution({ timeout_ms: this.timeoutMs });
7
11
  const timeoutController = new AbortController();
8
12
  const timeoutId = setTimeout(() => {
9
13
  timeoutController.abort(/* @__PURE__ */ new Error(`Operation timed out after ${this.timeoutMs} ms`));
@@ -1 +1 @@
1
- {"version":3,"file":"timeout.js","names":["timeoutMs: number"],"sources":["../../../src/plugin/interceptors/timeout.ts"],"sourcesContent":["import type { ExecutionContext, ExecutionInterceptor } from \"./types\";\n\n// interceptor to handle timeout logic\nexport class TimeoutInterceptor implements ExecutionInterceptor {\n constructor(private timeoutMs: number) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: ExecutionContext,\n ): Promise<T> {\n // create timeout signal\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => {\n timeoutController.abort(\n new Error(`Operation timed out after ${this.timeoutMs} ms`),\n );\n }, this.timeoutMs);\n\n try {\n // combine user signal (if exists) with timeout signal\n const combinedSignal = context.signal\n ? this._combineSignals([context.signal, timeoutController.signal])\n : timeoutController.signal;\n\n // execute function with combined signal\n context.signal = combinedSignal;\n return await fn();\n } finally {\n // cleanup timeout\n clearTimeout(timeoutId);\n }\n }\n\n private _combineSignals(signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\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"],"mappings":";AAGA,IAAa,qBAAb,MAAgE;CAC9D,YAAY,AAAQA,WAAmB;EAAnB;;CAEpB,MAAM,UACJ,IACA,SACY;EAEZ,MAAM,oBAAoB,IAAI,iBAAiB;EAC/C,MAAM,YAAY,iBAAiB;AACjC,qBAAkB,sBAChB,IAAI,MAAM,6BAA6B,KAAK,UAAU,KAAK,CAC5D;KACA,KAAK,UAAU;AAElB,MAAI;AAOF,WAAQ,SALe,QAAQ,SAC3B,KAAK,gBAAgB,CAAC,QAAQ,QAAQ,kBAAkB,OAAO,CAAC,GAChE,kBAAkB;AAItB,UAAO,MAAM,IAAI;YACT;AAER,gBAAa,UAAU;;;CAI3B,AAAQ,gBAAgB,SAAqC;EAC3D,MAAM,aAAa,IAAI,iBAAiB;AACxC,OAAK,MAAM,UAAU,SAAS;AAC5B,OAAI,OAAO,SAAS;AAClB,eAAW,MAAM,OAAO,OAAO;AAC/B;;AAEF,UAAO,iBACL,eACM;AACJ,eAAW,MAAM,OAAO,OAAO;MAEjC,EAAE,MAAM,MAAM,CACf;;AAEH,SAAO,WAAW"}
1
+ {"version":3,"file":"timeout.js","names":[],"sources":["../../../src/plugin/interceptors/timeout.ts"],"sourcesContent":["import { createLogger } from \"../../logging/logger\";\nimport type { ExecutionInterceptor, InterceptorContext } from \"./types\";\n\nconst logger = createLogger(\"interceptors:timeout\");\n\n// interceptor to handle timeout logic\nexport class TimeoutInterceptor implements ExecutionInterceptor {\n constructor(private timeoutMs: number) {}\n\n async intercept<T>(\n fn: () => Promise<T>,\n context: InterceptorContext,\n ): Promise<T> {\n logger.event()?.setExecution({\n timeout_ms: this.timeoutMs,\n });\n\n // create timeout signal\n const timeoutController = new AbortController();\n const timeoutId = setTimeout(() => {\n timeoutController.abort(\n new Error(`Operation timed out after ${this.timeoutMs} ms`),\n );\n }, this.timeoutMs);\n\n try {\n // combine user signal (if exists) with timeout signal\n const combinedSignal = context.signal\n ? this._combineSignals([context.signal, timeoutController.signal])\n : timeoutController.signal;\n\n // execute function with combined signal\n context.signal = combinedSignal;\n return await fn();\n } finally {\n // cleanup timeout\n clearTimeout(timeoutId);\n }\n }\n\n private _combineSignals(signals: AbortSignal[]): AbortSignal {\n const controller = new AbortController();\n for (const signal of signals) {\n if (signal.aborted) {\n controller.abort(signal.reason);\n break;\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"],"mappings":";;;AAGA,MAAM,SAAS,aAAa,uBAAuB;AAGnD,IAAa,qBAAb,MAAgE;CAC9D,YAAY,AAAQ,WAAmB;EAAnB;;CAEpB,MAAM,UACJ,IACA,SACY;AACZ,SAAO,OAAO,EAAE,aAAa,EAC3B,YAAY,KAAK,WAClB,CAAC;EAGF,MAAM,oBAAoB,IAAI,iBAAiB;EAC/C,MAAM,YAAY,iBAAiB;AACjC,qBAAkB,sBAChB,IAAI,MAAM,6BAA6B,KAAK,UAAU,KAAK,CAC5D;KACA,KAAK,UAAU;AAElB,MAAI;AAOF,WAAQ,SALe,QAAQ,SAC3B,KAAK,gBAAgB,CAAC,QAAQ,QAAQ,kBAAkB,OAAO,CAAC,GAChE,kBAAkB;AAItB,UAAO,MAAM,IAAI;YACT;AAER,gBAAa,UAAU;;;CAI3B,AAAQ,gBAAgB,SAAqC;EAC3D,MAAM,aAAa,IAAI,iBAAiB;AACxC,OAAK,MAAM,UAAU,SAAS;AAC5B,OAAI,OAAO,SAAS;AAClB,eAAW,MAAM,OAAO,OAAO;AAC/B;;AAEF,UAAO,iBACL,eACM;AACJ,eAAW,MAAM,OAAO,OAAO;MAEjC,EAAE,MAAM,MAAM,CACf;;AAEH,SAAO,WAAW"}
@@ -17,8 +17,6 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
17
17
  protected streamManager: StreamManager;
18
18
  protected telemetry: ITelemetry;
19
19
  protected abstract envVars: string[];
20
- /** If the plugin requires the Databricks client to be set in the request context */
21
- requiresDatabricksClient: boolean;
22
20
  /** Registered endpoints for this plugin */
23
21
  private registeredEndpoints;
24
22
  static phase: PluginPhase;
@@ -29,8 +27,44 @@ declare abstract class Plugin<TConfig extends BasePluginConfig = BasePluginConfi
29
27
  setup(): Promise<void>;
30
28
  getEndpoints(): PluginEndpointMap;
31
29
  abortActiveOperations(): void;
32
- protected executeStream<T>(res: IAppResponse, fn: StreamExecuteHandler<T>, options: StreamExecutionSettings, userKey: string): Promise<void>;
33
- protected execute<T>(fn: (signal?: AbortSignal) => Promise<T>, options: PluginExecutionSettings, userKey: string): Promise<T | undefined>;
30
+ /**
31
+ * Execute operations using the user's identity from the request.
32
+ *
33
+ * Returns a scoped instance of this plugin where all method calls
34
+ * will execute with the user's Databricks credentials instead of
35
+ * the service principal.
36
+ *
37
+ * @param req - The Express request containing the user token in headers
38
+ * @returns A scoped plugin instance that executes as the user
39
+ * @throws Error if user token is not available in request headers
40
+ *
41
+ * @example
42
+ * ```typescript
43
+ * // In route handler - execute query as the requesting user
44
+ * router.post('/users/me/query/:key', async (req, res) => {
45
+ * const result = await this.asUser(req).query(req.params.key)
46
+ * res.json(result)
47
+ * })
48
+ *
49
+ * // Mixed execution in same handler
50
+ * router.post('/dashboard', async (req, res) => {
51
+ * const [systemData, userData] = await Promise.all([
52
+ * this.getSystemStats(), // Service principal
53
+ * this.asUser(req).getUserPreferences(), // User context
54
+ * ])
55
+ * res.json({ systemData, userData })
56
+ * })
57
+ * ```
58
+ */
59
+ asUser(req: express.Request): this;
60
+ /**
61
+ * Creates a proxy that wraps method calls in a user context.
62
+ * This allows all plugin methods to automatically use the user's
63
+ * Databricks credentials.
64
+ */
65
+ private createUserContextProxy;
66
+ protected executeStream<T>(res: IAppResponse, fn: StreamExecuteHandler<T>, options: StreamExecutionSettings, userKey?: string): Promise<void>;
67
+ protected execute<T>(fn: (signal?: AbortSignal) => Promise<T>, options: PluginExecutionSettings, userKey?: string): Promise<T | undefined>;
34
68
  protected registerEndpoint(name: string, path: string): void;
35
69
  protected route<_TResponse>(router: express.Router, config: RouteConfig): void;
36
70
  private _buildExecutionConfig;
@@ -1 +1 @@
1
- {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;uBAgCsB,uBACJ,mBAAmB,6BACxB;oBAmBmB;;EArBV,UAAM,KAAA,EAKT,YALS;EAAA,UAAA,GAAA,EAMX,UANW;YACV,aAAA,EAMS,aANT;YAAmB,aAAA,EAOV,aAPU;YAoBL,SAAA,EAZT,UAYS;qBAhBb,OAAA,EAAA,MAAA,EAAA;;0BAEQ,EAAA,OAAA;;UAEJ,mBAAA;SASP,KAAA,EAAA,WAAA;MAGgB,EAAA,MAAA;aAeN,CAAA,MAAA,EAfM,OAeN;aAIb,CAAA,CAAA,EAAA,IAAA;cAEK,CAAA,CAAA,EANA,OAAA,CAAQ,MAMR,CAAA,EAAA,IAAA;OAUT,CAAA,CAAA,EAZI,OAYJ,CAAA,IAAA,CAAA;cACoB,CAAA,CAAA,EAXX,iBAWW;uBAArB,CAAA,CAAA,EAAA,IAAA;YACK,aAAA,CAAA,CAAA,CAAA,CAAA,GAAA,EAFJ,YAEI,EAAA,EAAA,EADL,oBACK,CADgB,CAChB,CAAA,EAAA,OAAA,EAAA,uBAAA,EAAA,OAAA,EAAA,MAAA,CAAA,EACM,OADN,CAAA,IAAA,CAAA;YACM,OAAA,CAAA,CAAA,CAAA,CAAA,EAAA,EAAA,CAAA,MAAA,CAAA,EAwDD,WAxDC,EAAA,GAwDe,OAxDf,CAwDuB,CAxDvB,CAAA,EAAA,OAAA,EAyDN,uBAzDM,EAAA,OAAA,EAAA,MAAA,CAAA,EA2Dd,OA3Dc,CA2DN,CA3DM,GAAA,SAAA,CAAA;YAwDD,gBAAA,CAAA,IAAA,EAAA,MAAA,EAAA,IAAA,EAAA,MAAA,CAAA,EAAA,IAAA;YAAwB,KAAA,CAAA,UAAA,CAAA,CAAA,MAAA,EA0B9B,OAAA,CAAQ,MA1BsB,EAAA,MAAA,EA2B9B,WA3B8B,CAAA,EAAA,IAAA;UAAR,qBAAA;UACrB,kBAAA;UAEA,wBAAA;UAAR,iBAAA"}
1
+ {"version":3,"file":"plugin.d.ts","names":[],"sources":["../../src/plugin/plugin.ts"],"sourcesContent":[],"mappings":";;;;;;;;;;uBA6DsB,uBACJ,mBAAmB,6BACxB;oBAgBmB;;EAlBV,UAAM,KAAA,EAKT,YALS;EAAA,UAAA,GAAA,EAMX,UANW;YACV,aAAA,EAMS,aANT;YAAmB,aAAA,EAOV,aAPU;YAiBL,SAAA,EATT,UASS;qBAbb,OAAA,EAAA,MAAA,EAAA;;UAEQ,mBAAA;SACA,KAAA,EAOX,WAPW;MACJ,EAAA,MAAA;aAMP,CAAA,MAAA,EAGgB,OAHhB;aAGgB,CAAA,CAAA,EAAA,IAAA;cAeN,CAAA,CAAA,EAAR,OAAA,CAAQ,MAAA,CAAA,EAAA,IAAA;OAIb,CAAA,CAAA,EAAA,OAAA,CAAA,IAAA,CAAA;cAEK,CAAA,CAAA,EAAA,iBAAA;uBAqCI,CAAA,CAAA,EAAA,IAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;cAAR,OAAA,CAAQ;;;;;;;kCA6Db,kBACD,qBAAqB,aAChB,4CACO;qCA2DF,gBAAgB,QAAQ,aAC7B,4CAER,QAAQ;;sCA0BD,OAAA,CAAQ,gBACR"}