@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.
- package/AGENTS.md +89 -12
- package/CLAUDE.md +89 -12
- package/NOTICE.md +4 -0
- package/README.md +21 -15
- package/bin/appkit-lint.js +129 -0
- package/dist/analytics/analytics.d.ts +33 -8
- package/dist/analytics/analytics.d.ts.map +1 -1
- package/dist/analytics/analytics.js +67 -27
- package/dist/analytics/analytics.js.map +1 -1
- package/dist/analytics/defaults.js.map +1 -1
- package/dist/analytics/query.js +12 -6
- package/dist/analytics/query.js.map +1 -1
- package/dist/app/index.d.ts.map +1 -1
- package/dist/app/index.js +7 -5
- package/dist/app/index.js.map +1 -1
- package/dist/appkit/package.js +1 -1
- package/dist/cache/defaults.js.map +1 -1
- package/dist/cache/index.d.ts +1 -0
- package/dist/cache/index.d.ts.map +1 -1
- package/dist/cache/index.js +25 -5
- package/dist/cache/index.js.map +1 -1
- package/dist/cache/storage/memory.js.map +1 -1
- package/dist/cache/storage/persistent.js +12 -6
- package/dist/cache/storage/persistent.js.map +1 -1
- package/dist/connectors/lakebase/client.js +31 -21
- package/dist/connectors/lakebase/client.js.map +1 -1
- package/dist/connectors/lakebase/defaults.js.map +1 -1
- package/dist/connectors/sql-warehouse/client.js +68 -28
- package/dist/connectors/sql-warehouse/client.js.map +1 -1
- package/dist/connectors/sql-warehouse/defaults.js.map +1 -1
- package/dist/context/execution-context.js +75 -0
- package/dist/context/execution-context.js.map +1 -0
- package/dist/context/index.js +27 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/service-context.js +154 -0
- package/dist/context/service-context.js.map +1 -0
- package/dist/context/user-context.js +15 -0
- package/dist/context/user-context.js.map +1 -0
- package/dist/core/appkit.d.ts +3 -0
- package/dist/core/appkit.d.ts.map +1 -1
- package/dist/core/appkit.js +7 -0
- package/dist/core/appkit.js.map +1 -1
- package/dist/errors/authentication.d.ts +38 -0
- package/dist/errors/authentication.d.ts.map +1 -0
- package/dist/errors/authentication.js +48 -0
- package/dist/errors/authentication.js.map +1 -0
- package/dist/errors/base.d.ts +58 -0
- package/dist/errors/base.d.ts.map +1 -0
- package/dist/errors/base.js +70 -0
- package/dist/errors/base.js.map +1 -0
- package/dist/errors/configuration.d.ts +38 -0
- package/dist/errors/configuration.d.ts.map +1 -0
- package/dist/errors/configuration.js +45 -0
- package/dist/errors/configuration.js.map +1 -0
- package/dist/errors/connection.d.ts +42 -0
- package/dist/errors/connection.d.ts.map +1 -0
- package/dist/errors/connection.js +54 -0
- package/dist/errors/connection.js.map +1 -0
- package/dist/errors/execution.d.ts +42 -0
- package/dist/errors/execution.d.ts.map +1 -0
- package/dist/errors/execution.js +51 -0
- package/dist/errors/execution.js.map +1 -0
- package/dist/errors/index.js +28 -0
- package/dist/errors/index.js.map +1 -0
- package/dist/errors/initialization.d.ts +34 -0
- package/dist/errors/initialization.d.ts.map +1 -0
- package/dist/errors/initialization.js +42 -0
- package/dist/errors/initialization.js.map +1 -0
- package/dist/errors/server.d.ts +38 -0
- package/dist/errors/server.d.ts.map +1 -0
- package/dist/errors/server.js +45 -0
- package/dist/errors/server.js.map +1 -0
- package/dist/errors/tunnel.d.ts +38 -0
- package/dist/errors/tunnel.d.ts.map +1 -0
- package/dist/errors/tunnel.js +51 -0
- package/dist/errors/tunnel.js.map +1 -0
- package/dist/errors/validation.d.ts +36 -0
- package/dist/errors/validation.d.ts.map +1 -0
- package/dist/errors/validation.js +45 -0
- package/dist/errors/validation.js.map +1 -0
- package/dist/index.d.ts +12 -4
- package/dist/index.js +12 -4
- package/dist/index.js.map +1 -1
- package/dist/logging/logger.js +179 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/sampling.js +56 -0
- package/dist/logging/sampling.js.map +1 -0
- package/dist/logging/wide-event-emitter.js +108 -0
- package/dist/logging/wide-event-emitter.js.map +1 -0
- package/dist/logging/wide-event.js +167 -0
- package/dist/logging/wide-event.js.map +1 -0
- package/dist/plugin/dev-reader.d.ts.map +1 -1
- package/dist/plugin/dev-reader.js +8 -3
- package/dist/plugin/dev-reader.js.map +1 -1
- package/dist/plugin/interceptors/cache.js.map +1 -1
- package/dist/plugin/interceptors/retry.js +10 -2
- package/dist/plugin/interceptors/retry.js.map +1 -1
- package/dist/plugin/interceptors/telemetry.js +24 -9
- package/dist/plugin/interceptors/telemetry.js.map +1 -1
- package/dist/plugin/interceptors/timeout.js +4 -0
- package/dist/plugin/interceptors/timeout.js.map +1 -1
- package/dist/plugin/plugin.d.ts +38 -4
- package/dist/plugin/plugin.d.ts.map +1 -1
- package/dist/plugin/plugin.js +86 -5
- package/dist/plugin/plugin.js.map +1 -1
- package/dist/plugin/to-plugin.d.ts +4 -0
- package/dist/plugin/to-plugin.d.ts.map +1 -1
- package/dist/plugin/to-plugin.js +3 -0
- package/dist/plugin/to-plugin.js.map +1 -1
- package/dist/server/index.d.ts +3 -0
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js +25 -21
- package/dist/server/index.js.map +1 -1
- package/dist/server/remote-tunnel/remote-tunnel-controller.js +4 -2
- package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -1
- package/dist/server/remote-tunnel/remote-tunnel-manager.js +10 -8
- package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -1
- package/dist/server/utils.js.map +1 -1
- package/dist/server/vite-dev-server.js +8 -5
- package/dist/server/vite-dev-server.js.map +1 -1
- package/dist/shared/src/sql/helpers.js.map +1 -1
- package/dist/stream/arrow-stream-processor.js +13 -6
- package/dist/stream/arrow-stream-processor.js.map +1 -1
- package/dist/stream/buffers.js +5 -1
- package/dist/stream/buffers.js.map +1 -1
- package/dist/stream/sse-writer.js.map +1 -1
- package/dist/stream/stream-manager.d.ts.map +1 -1
- package/dist/stream/stream-manager.js +47 -36
- package/dist/stream/stream-manager.js.map +1 -1
- package/dist/stream/stream-registry.js.map +1 -1
- package/dist/stream/types.js.map +1 -1
- package/dist/telemetry/index.d.ts +2 -2
- package/dist/telemetry/index.js +2 -2
- package/dist/telemetry/instrumentations.js +14 -10
- package/dist/telemetry/instrumentations.js.map +1 -1
- package/dist/telemetry/telemetry-manager.js +8 -6
- package/dist/telemetry/telemetry-manager.js.map +1 -1
- package/dist/telemetry/trace-sampler.js +33 -0
- package/dist/telemetry/trace-sampler.js.map +1 -0
- package/dist/type-generator/index.js +4 -2
- package/dist/type-generator/index.js.map +1 -1
- package/dist/type-generator/query-registry.js +4 -2
- package/dist/type-generator/query-registry.js.map +1 -1
- package/dist/type-generator/types.js.map +1 -1
- package/dist/type-generator/vite-plugin.d.ts.map +1 -1
- package/dist/type-generator/vite-plugin.js +5 -3
- package/dist/type-generator/vite-plugin.js.map +1 -1
- package/dist/utils/env-validator.js +5 -5
- package/dist/utils/env-validator.js.map +1 -1
- package/dist/utils/merge.js +1 -5
- package/dist/utils/merge.js.map +1 -1
- package/dist/utils/path-exclusions.js +66 -0
- package/dist/utils/path-exclusions.js.map +1 -0
- package/dist/utils/vite-config-merge.js +1 -5
- package/dist/utils/vite-config-merge.js.map +1 -1
- package/llms.txt +89 -12
- package/package.json +6 -1
- package/dist/utils/databricks-client-middleware.d.ts +0 -17
- package/dist/utils/databricks-client-middleware.d.ts.map +0 -1
- package/dist/utils/databricks-client-middleware.js +0 -117
- package/dist/utils/databricks-client-middleware.js.map +0 -1
- package/dist/utils/index.js +0 -26
- package/dist/utils/index.js.map +0 -1
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { AppKitError, init_base } from "./base.js";
|
|
3
|
+
|
|
4
|
+
//#region src/errors/server.ts
|
|
5
|
+
var ServerError;
|
|
6
|
+
var init_server = __esmMin((() => {
|
|
7
|
+
init_base();
|
|
8
|
+
ServerError = class ServerError extends AppKitError {
|
|
9
|
+
constructor(..._args) {
|
|
10
|
+
super(..._args);
|
|
11
|
+
this.code = "SERVER_ERROR";
|
|
12
|
+
this.statusCode = 500;
|
|
13
|
+
this.isRetryable = false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a server error for autoStart conflict
|
|
17
|
+
*/
|
|
18
|
+
static autoStartConflict(operation) {
|
|
19
|
+
return new ServerError(`Cannot ${operation} when autoStart is true`, { context: { operation } });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a server error for server not started
|
|
23
|
+
*/
|
|
24
|
+
static notStarted() {
|
|
25
|
+
return new ServerError("Server not started. Please start the server first by calling the start() method");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a server error for Vite dev server not initialized
|
|
29
|
+
*/
|
|
30
|
+
static viteNotInitialized() {
|
|
31
|
+
return new ServerError("Vite dev server not initialized");
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a server error for missing client directory
|
|
35
|
+
*/
|
|
36
|
+
static clientDirectoryNotFound(searchedPaths) {
|
|
37
|
+
return new ServerError(`Could not find client directory. Searched for vite.config.ts/js + index.html in: ${searchedPaths.join(", ")}`, { context: { searchedPaths } });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
init_server();
|
|
44
|
+
export { ServerError, init_server };
|
|
45
|
+
//# sourceMappingURL=server.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"server.js","names":[],"sources":["../../src/errors/server.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when server lifecycle operations fail.\n * Use for server start/stop issues, configuration conflicts, etc.\n *\n * @example\n * ```typescript\n * throw new ServerError(\"Cannot get server when autoStart is true\");\n * throw new ServerError(\"Server not started\");\n * ```\n */\nexport class ServerError extends AppKitError {\n readonly code = \"SERVER_ERROR\";\n readonly statusCode = 500;\n readonly isRetryable = false;\n\n /**\n * Create a server error for autoStart conflict\n */\n static autoStartConflict(operation: string): ServerError {\n return new ServerError(`Cannot ${operation} when autoStart is true`, {\n context: { operation },\n });\n }\n\n /**\n * Create a server error for server not started\n */\n static notStarted(): ServerError {\n return new ServerError(\n \"Server not started. Please start the server first by calling the start() method\",\n );\n }\n\n /**\n * Create a server error for Vite dev server not initialized\n */\n static viteNotInitialized(): ServerError {\n return new ServerError(\"Vite dev server not initialized\");\n }\n\n /**\n * Create a server error for missing client directory\n */\n static clientDirectoryNotFound(searchedPaths: string[]): ServerError {\n return new ServerError(\n `Could not find client directory. Searched for vite.config.ts/js + index.html in: ${searchedPaths.join(\", \")}`,\n { context: { searchedPaths } },\n );\n }\n}\n"],"mappings":";;;;;;YAAqC;CAYxB,cAAb,MAAa,oBAAoB,YAAY;;;eAC3B;qBACM;sBACC;;;;;EAKvB,OAAO,kBAAkB,WAAgC;AACvD,UAAO,IAAI,YAAY,UAAU,UAAU,0BAA0B,EACnE,SAAS,EAAE,WAAW,EACvB,CAAC;;;;;EAMJ,OAAO,aAA0B;AAC/B,UAAO,IAAI,YACT,kFACD;;;;;EAMH,OAAO,qBAAkC;AACvC,UAAO,IAAI,YAAY,kCAAkC;;;;;EAM3D,OAAO,wBAAwB,eAAsC;AACnE,UAAO,IAAI,YACT,oFAAoF,cAAc,KAAK,KAAK,IAC5G,EAAE,SAAS,EAAE,eAAe,EAAE,CAC/B"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { AppKitError } from "./base.js";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/tunnel.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when remote tunnel operations fail.
|
|
7
|
+
* Use for tunnel connection issues, message parsing failures, etc.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* throw new TunnelError("No tunnel connection available");
|
|
12
|
+
* throw new TunnelError("Failed to parse WebSocket message", { cause: parseError });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare class TunnelError extends AppKitError {
|
|
16
|
+
readonly code = "TUNNEL_ERROR";
|
|
17
|
+
readonly statusCode = 502;
|
|
18
|
+
readonly isRetryable = true;
|
|
19
|
+
/**
|
|
20
|
+
* Create a tunnel error for missing tunnel getter
|
|
21
|
+
*/
|
|
22
|
+
static getterNotRegistered(): TunnelError;
|
|
23
|
+
/**
|
|
24
|
+
* Create a tunnel error for no available connection
|
|
25
|
+
*/
|
|
26
|
+
static noConnection(): TunnelError;
|
|
27
|
+
/**
|
|
28
|
+
* Create a tunnel error for asset fetch failure
|
|
29
|
+
*/
|
|
30
|
+
static fetchFailed(path: string, cause?: Error): TunnelError;
|
|
31
|
+
/**
|
|
32
|
+
* Create a tunnel error for message parsing failure
|
|
33
|
+
*/
|
|
34
|
+
static parseError(messageType: string, cause?: Error): TunnelError;
|
|
35
|
+
}
|
|
36
|
+
//#endregion
|
|
37
|
+
export { TunnelError };
|
|
38
|
+
//# sourceMappingURL=tunnel.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.d.ts","names":[],"sources":["../../src/errors/tunnel.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;;;;;;;;AAAiC,cAApB,WAAA,SAAoB,WAAA,CAAA;EAAW,SAAA,IAAA,GAAA,cAAA;;;;;;gCAQZ;;;;yBASP;;;;2CAOkB,QAAQ;;;;iDAUF,QAAQ"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { AppKitError, init_base } from "./base.js";
|
|
3
|
+
|
|
4
|
+
//#region src/errors/tunnel.ts
|
|
5
|
+
var TunnelError;
|
|
6
|
+
var init_tunnel = __esmMin((() => {
|
|
7
|
+
init_base();
|
|
8
|
+
TunnelError = class TunnelError extends AppKitError {
|
|
9
|
+
constructor(..._args) {
|
|
10
|
+
super(..._args);
|
|
11
|
+
this.code = "TUNNEL_ERROR";
|
|
12
|
+
this.statusCode = 502;
|
|
13
|
+
this.isRetryable = true;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a tunnel error for missing tunnel getter
|
|
17
|
+
*/
|
|
18
|
+
static getterNotRegistered() {
|
|
19
|
+
return new TunnelError("Tunnel getter not registered for DevFileReader singleton");
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a tunnel error for no available connection
|
|
23
|
+
*/
|
|
24
|
+
static noConnection() {
|
|
25
|
+
return new TunnelError("No tunnel connection available for file read");
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Create a tunnel error for asset fetch failure
|
|
29
|
+
*/
|
|
30
|
+
static fetchFailed(path, cause) {
|
|
31
|
+
return new TunnelError("Failed to fetch asset", {
|
|
32
|
+
cause,
|
|
33
|
+
context: { path }
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Create a tunnel error for message parsing failure
|
|
38
|
+
*/
|
|
39
|
+
static parseError(messageType, cause) {
|
|
40
|
+
return new TunnelError(`Failed to parse ${messageType} message`, {
|
|
41
|
+
cause,
|
|
42
|
+
context: { messageType }
|
|
43
|
+
});
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
}));
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
init_tunnel();
|
|
50
|
+
export { TunnelError, init_tunnel };
|
|
51
|
+
//# sourceMappingURL=tunnel.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tunnel.js","names":[],"sources":["../../src/errors/tunnel.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when remote tunnel operations fail.\n * Use for tunnel connection issues, message parsing failures, etc.\n *\n * @example\n * ```typescript\n * throw new TunnelError(\"No tunnel connection available\");\n * throw new TunnelError(\"Failed to parse WebSocket message\", { cause: parseError });\n * ```\n */\nexport class TunnelError extends AppKitError {\n readonly code = \"TUNNEL_ERROR\";\n readonly statusCode = 502;\n readonly isRetryable = true;\n\n /**\n * Create a tunnel error for missing tunnel getter\n */\n static getterNotRegistered(): TunnelError {\n return new TunnelError(\n \"Tunnel getter not registered for DevFileReader singleton\",\n );\n }\n\n /**\n * Create a tunnel error for no available connection\n */\n static noConnection(): TunnelError {\n return new TunnelError(\"No tunnel connection available for file read\");\n }\n\n /**\n * Create a tunnel error for asset fetch failure\n */\n static fetchFailed(path: string, cause?: Error): TunnelError {\n return new TunnelError(\"Failed to fetch asset\", {\n cause,\n context: { path },\n });\n }\n\n /**\n * Create a tunnel error for message parsing failure\n */\n static parseError(messageType: string, cause?: Error): TunnelError {\n return new TunnelError(`Failed to parse ${messageType} message`, {\n cause,\n context: { messageType },\n });\n }\n}\n"],"mappings":";;;;;;YAAqC;CAYxB,cAAb,MAAa,oBAAoB,YAAY;;;eAC3B;qBACM;sBACC;;;;;EAKvB,OAAO,sBAAmC;AACxC,UAAO,IAAI,YACT,2DACD;;;;;EAMH,OAAO,eAA4B;AACjC,UAAO,IAAI,YAAY,+CAA+C;;;;;EAMxE,OAAO,YAAY,MAAc,OAA4B;AAC3D,UAAO,IAAI,YAAY,yBAAyB;IAC9C;IACA,SAAS,EAAE,MAAM;IAClB,CAAC;;;;;EAMJ,OAAO,WAAW,aAAqB,OAA4B;AACjE,UAAO,IAAI,YAAY,mBAAmB,YAAY,WAAW;IAC/D;IACA,SAAS,EAAE,aAAa;IACzB,CAAC"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { AppKitError } from "./base.js";
|
|
2
|
+
|
|
3
|
+
//#region src/errors/validation.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Error thrown when input validation fails.
|
|
7
|
+
* Use for invalid parameters, missing required fields, or type mismatches.
|
|
8
|
+
*
|
|
9
|
+
* @example
|
|
10
|
+
* ```typescript
|
|
11
|
+
* throw new ValidationError("Statement is required", { context: { field: "statement" } });
|
|
12
|
+
* throw new ValidationError("maxPoolSize must be at least 1", { context: { value: config.maxPoolSize } });
|
|
13
|
+
* ```
|
|
14
|
+
*/
|
|
15
|
+
declare class ValidationError extends AppKitError {
|
|
16
|
+
readonly code = "VALIDATION_ERROR";
|
|
17
|
+
readonly statusCode = 400;
|
|
18
|
+
readonly isRetryable = false;
|
|
19
|
+
/**
|
|
20
|
+
* Create a validation error for a missing required field
|
|
21
|
+
*/
|
|
22
|
+
static missingField(fieldName: string): ValidationError;
|
|
23
|
+
/**
|
|
24
|
+
* Create a validation error for an invalid field value.
|
|
25
|
+
* Note: The actual value is not stored in context for security reasons.
|
|
26
|
+
* Only the value's type is recorded.
|
|
27
|
+
*/
|
|
28
|
+
static invalidValue(fieldName: string, value: unknown, expected?: string): ValidationError;
|
|
29
|
+
/**
|
|
30
|
+
* Create a validation error for missing environment variables
|
|
31
|
+
*/
|
|
32
|
+
static missingEnvVars(vars: string[]): ValidationError;
|
|
33
|
+
}
|
|
34
|
+
//#endregion
|
|
35
|
+
export { ValidationError };
|
|
36
|
+
//# sourceMappingURL=validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.d.ts","names":[],"sources":["../../src/errors/validation.ts"],"sourcesContent":[],"mappings":";;;;;;AAYA;;;;;;;;cAAa,eAAA,SAAwB,WAAA;;;;;;;0CAQK;;;;;;6EAerC;;;;yCAgBoC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { __esmMin } from "../_virtual/rolldown_runtime.js";
|
|
2
|
+
import { AppKitError, init_base } from "./base.js";
|
|
3
|
+
|
|
4
|
+
//#region src/errors/validation.ts
|
|
5
|
+
var ValidationError;
|
|
6
|
+
var init_validation = __esmMin((() => {
|
|
7
|
+
init_base();
|
|
8
|
+
ValidationError = class ValidationError extends AppKitError {
|
|
9
|
+
constructor(..._args) {
|
|
10
|
+
super(..._args);
|
|
11
|
+
this.code = "VALIDATION_ERROR";
|
|
12
|
+
this.statusCode = 400;
|
|
13
|
+
this.isRetryable = false;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Create a validation error for a missing required field
|
|
17
|
+
*/
|
|
18
|
+
static missingField(fieldName) {
|
|
19
|
+
return new ValidationError(`Missing required field: ${fieldName}`, { context: { field: fieldName } });
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Create a validation error for an invalid field value.
|
|
23
|
+
* Note: The actual value is not stored in context for security reasons.
|
|
24
|
+
* Only the value's type is recorded.
|
|
25
|
+
*/
|
|
26
|
+
static invalidValue(fieldName, value, expected) {
|
|
27
|
+
return new ValidationError(expected ? `Invalid value for ${fieldName}: expected ${expected}` : `Invalid value for ${fieldName}`, { context: {
|
|
28
|
+
field: fieldName,
|
|
29
|
+
valueType: value === null ? "null" : typeof value,
|
|
30
|
+
expected
|
|
31
|
+
} });
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a validation error for missing environment variables
|
|
35
|
+
*/
|
|
36
|
+
static missingEnvVars(vars) {
|
|
37
|
+
return new ValidationError(`Missing required environment variables: ${vars.join(", ")}`, { context: { missingVars: vars } });
|
|
38
|
+
}
|
|
39
|
+
};
|
|
40
|
+
}));
|
|
41
|
+
|
|
42
|
+
//#endregion
|
|
43
|
+
init_validation();
|
|
44
|
+
export { ValidationError, init_validation };
|
|
45
|
+
//# sourceMappingURL=validation.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validation.js","names":[],"sources":["../../src/errors/validation.ts"],"sourcesContent":["import { AppKitError } from \"./base\";\n\n/**\n * Error thrown when input validation fails.\n * Use for invalid parameters, missing required fields, or type mismatches.\n *\n * @example\n * ```typescript\n * throw new ValidationError(\"Statement is required\", { context: { field: \"statement\" } });\n * throw new ValidationError(\"maxPoolSize must be at least 1\", { context: { value: config.maxPoolSize } });\n * ```\n */\nexport class ValidationError extends AppKitError {\n readonly code = \"VALIDATION_ERROR\";\n readonly statusCode = 400;\n readonly isRetryable = false;\n\n /**\n * Create a validation error for a missing required field\n */\n static missingField(fieldName: string): ValidationError {\n return new ValidationError(`Missing required field: ${fieldName}`, {\n context: { field: fieldName },\n });\n }\n\n /**\n * Create a validation error for an invalid field value.\n * Note: The actual value is not stored in context for security reasons.\n * Only the value's type is recorded.\n */\n static invalidValue(\n fieldName: string,\n value: unknown,\n expected?: string,\n ): ValidationError {\n const msg = expected\n ? `Invalid value for ${fieldName}: expected ${expected}`\n : `Invalid value for ${fieldName}`;\n return new ValidationError(msg, {\n context: {\n field: fieldName,\n valueType: value === null ? \"null\" : typeof value,\n expected,\n },\n });\n }\n\n /**\n * Create a validation error for missing environment variables\n */\n static missingEnvVars(vars: string[]): ValidationError {\n return new ValidationError(\n `Missing required environment variables: ${vars.join(\", \")}`,\n { context: { missingVars: vars } },\n );\n }\n}\n"],"mappings":";;;;;;YAAqC;CAYxB,kBAAb,MAAa,wBAAwB,YAAY;;;eAC/B;qBACM;sBACC;;;;;EAKvB,OAAO,aAAa,WAAoC;AACtD,UAAO,IAAI,gBAAgB,2BAA2B,aAAa,EACjE,SAAS,EAAE,OAAO,WAAW,EAC9B,CAAC;;;;;;;EAQJ,OAAO,aACL,WACA,OACA,UACiB;AAIjB,UAAO,IAAI,gBAHC,WACR,qBAAqB,UAAU,aAAa,aAC5C,qBAAqB,aACO,EAC9B,SAAS;IACP,OAAO;IACP,WAAW,UAAU,OAAO,SAAS,OAAO;IAC5C;IACD,EACF,CAAC;;;;;EAMJ,OAAO,eAAe,MAAiC;AACrD,UAAO,IAAI,gBACT,2CAA2C,KAAK,KAAK,KAAK,IAC1D,EAAE,SAAS,EAAE,aAAa,MAAM,EAAE,CACnC"}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,15 +1,23 @@
|
|
|
1
1
|
import { BasePluginConfig, IAppRouter } from "./shared/src/plugin.js";
|
|
2
|
+
import { CacheConfig } from "./shared/src/cache.js";
|
|
2
3
|
import { StreamExecutionSettings } from "./shared/src/execute.js";
|
|
3
|
-
import { SQLTypeMarker } from "./shared/src/sql/types.js";
|
|
4
4
|
import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
|
|
5
5
|
import { CacheManager } from "./cache/index.js";
|
|
6
|
-
import { ITelemetry } from "./telemetry/types.js";
|
|
6
|
+
import { ITelemetry, TelemetryConfig } from "./telemetry/types.js";
|
|
7
7
|
import { Counter, Histogram, SeverityNumber, Span, SpanStatusCode } from "./telemetry/index.js";
|
|
8
8
|
import { Plugin } from "./plugin/plugin.js";
|
|
9
9
|
import { toPlugin } from "./plugin/to-plugin.js";
|
|
10
10
|
import { analytics } from "./analytics/analytics.js";
|
|
11
11
|
import { createApp } from "./core/appkit.js";
|
|
12
|
+
import { AppKitError } from "./errors/base.js";
|
|
13
|
+
import { AuthenticationError } from "./errors/authentication.js";
|
|
14
|
+
import { ConfigurationError } from "./errors/configuration.js";
|
|
15
|
+
import { ConnectionError } from "./errors/connection.js";
|
|
16
|
+
import { ExecutionError } from "./errors/execution.js";
|
|
17
|
+
import { InitializationError } from "./errors/initialization.js";
|
|
18
|
+
import { ServerError } from "./errors/server.js";
|
|
19
|
+
import { TunnelError } from "./errors/tunnel.js";
|
|
20
|
+
import { ValidationError } from "./errors/validation.js";
|
|
12
21
|
import { server } from "./server/index.js";
|
|
13
22
|
import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
|
|
14
|
-
|
|
15
|
-
export { type BasePluginConfig, CacheManager, type Counter, type Histogram, type IAppRouter, type ITelemetry, Plugin, type SQLTypeMarker, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, analytics, appKitTypesPlugin, createApp, getRequestContext, isSQLTypeMarker, server, sql, toPlugin };
|
|
23
|
+
export { AppKitError, AuthenticationError, type BasePluginConfig, type CacheConfig, CacheManager, ConfigurationError, ConnectionError, type Counter, ExecutionError, type Histogram, type IAppRouter, type ITelemetry, InitializationError, Plugin, ServerError, SeverityNumber, type Span, SpanStatusCode, type StreamExecutionSettings, type TelemetryConfig, TunnelError, ValidationError, analytics, appKitTypesPlugin, createApp, isSQLTypeMarker, server, sql, toPlugin };
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,15 @@
|
|
|
1
1
|
import { isSQLTypeMarker, sql } from "./shared/src/sql/helpers.js";
|
|
2
2
|
import { SeverityNumber, SpanStatusCode } from "./telemetry/index.js";
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
3
|
+
import { AppKitError } from "./errors/base.js";
|
|
4
|
+
import { AuthenticationError } from "./errors/authentication.js";
|
|
5
|
+
import { ConfigurationError } from "./errors/configuration.js";
|
|
6
|
+
import { ConnectionError } from "./errors/connection.js";
|
|
7
|
+
import { ExecutionError } from "./errors/execution.js";
|
|
8
|
+
import { InitializationError } from "./errors/initialization.js";
|
|
9
|
+
import { ServerError } from "./errors/server.js";
|
|
10
|
+
import { TunnelError } from "./errors/tunnel.js";
|
|
11
|
+
import { ValidationError } from "./errors/validation.js";
|
|
12
|
+
import { init_errors } from "./errors/index.js";
|
|
5
13
|
import { CacheManager } from "./cache/index.js";
|
|
6
14
|
import { Plugin } from "./plugin/plugin.js";
|
|
7
15
|
import { toPlugin } from "./plugin/to-plugin.js";
|
|
@@ -14,8 +22,8 @@ import { appKitTypesPlugin } from "./type-generator/vite-plugin.js";
|
|
|
14
22
|
import { server } from "./server/index.js";
|
|
15
23
|
|
|
16
24
|
//#region src/index.ts
|
|
17
|
-
|
|
25
|
+
init_errors();
|
|
18
26
|
|
|
19
27
|
//#endregion
|
|
20
|
-
export { CacheManager, Plugin, SeverityNumber, SpanStatusCode, analytics, appKitTypesPlugin, createApp,
|
|
28
|
+
export { AppKitError, AuthenticationError, CacheManager, ConfigurationError, ConnectionError, ExecutionError, InitializationError, Plugin, ServerError, SeverityNumber, SpanStatusCode, TunnelError, ValidationError, analytics, appKitTypesPlugin, createApp, isSQLTypeMarker, server, sql, toPlugin };
|
|
21
29
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../src/index.ts"],"sourcesContent":["// Types from shared\nexport type {\n BasePluginConfig,\n CacheConfig,\n IAppRouter,\n StreamExecutionSettings,\n} from \"shared\";\nexport { isSQLTypeMarker, sql } from \"shared\";\nexport { analytics } from \"./analytics\";\nexport { CacheManager } from \"./cache\";\nexport { createApp } from \"./core\";\n// Errors\nexport {\n AppKitError,\n AuthenticationError,\n ConfigurationError,\n ConnectionError,\n ExecutionError,\n InitializationError,\n ServerError,\n TunnelError,\n ValidationError,\n} from \"./errors\";\n// Plugin authoring\nexport { Plugin, toPlugin } from \"./plugin\";\nexport { server } from \"./server\";\n// Telemetry (for advanced custom telemetry)\nexport {\n type Counter,\n type Histogram,\n type ITelemetry,\n SeverityNumber,\n type Span,\n SpanStatusCode,\n type TelemetryConfig,\n} from \"./telemetry\";\n\n// Vite plugin\nexport { appKitTypesPlugin } from \"./type-generator/vite-plugin\";\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;aAsBkB"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
import { DEFAULT_SAMPLING_CONFIG, shouldSample } from "./sampling.js";
|
|
2
|
+
import { WideEvent } from "./wide-event.js";
|
|
3
|
+
import { WideEventEmitter } from "./wide-event-emitter.js";
|
|
4
|
+
import { trace } from "@opentelemetry/api";
|
|
5
|
+
import { AsyncLocalStorage } from "node:async_hooks";
|
|
6
|
+
import { format } from "node:util";
|
|
7
|
+
import { createDebug } from "obug";
|
|
8
|
+
|
|
9
|
+
//#region src/logging/logger.ts
|
|
10
|
+
const eventStorage = new AsyncLocalStorage();
|
|
11
|
+
const eventsByRequest = /* @__PURE__ */ new WeakMap();
|
|
12
|
+
const emitter = new WideEventEmitter();
|
|
13
|
+
const MAX_REQUEST_ID_LENGTH = 128;
|
|
14
|
+
/**
|
|
15
|
+
* Sanitize a request ID from user headers
|
|
16
|
+
*/
|
|
17
|
+
function sanitizeRequestId(id) {
|
|
18
|
+
return id.replace(/[^a-zA-Z0-9_.-]/g, "").slice(0, MAX_REQUEST_ID_LENGTH);
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Generate a request ID from the request
|
|
22
|
+
*/
|
|
23
|
+
function generateRequestId(req) {
|
|
24
|
+
const existingId = req.headers["x-request-id"] || req.headers["x-correlation-id"] || req.headers["x-amzn-trace-id"];
|
|
25
|
+
if (existingId && typeof existingId === "string" && existingId.length > 0) {
|
|
26
|
+
const sanitized = sanitizeRequestId(existingId);
|
|
27
|
+
if (sanitized.length > 0) return sanitized;
|
|
28
|
+
}
|
|
29
|
+
return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Create a WideEvent for a request
|
|
33
|
+
*/
|
|
34
|
+
function createEventForRequest(req) {
|
|
35
|
+
const requestId = generateRequestId(req);
|
|
36
|
+
const wideEvent = new WideEvent(requestId);
|
|
37
|
+
const path = (req.path || req.url || req.originalUrl)?.split("?")[0];
|
|
38
|
+
wideEvent.set("method", req.method).set("path", path);
|
|
39
|
+
const rawUserId = req.headers["x-forwarded-user"];
|
|
40
|
+
if (rawUserId && typeof rawUserId === "string" && rawUserId.length > 0) {
|
|
41
|
+
const userId = rawUserId.replace(/[^a-zA-Z0-9_@.-]/g, "").slice(0, 128);
|
|
42
|
+
if (userId.length > 0) wideEvent.setUser({ id: userId });
|
|
43
|
+
}
|
|
44
|
+
const spanContext = trace.getActiveSpan()?.spanContext();
|
|
45
|
+
if (spanContext?.traceId) {
|
|
46
|
+
wideEvent.set("trace_id", spanContext.traceId);
|
|
47
|
+
createDebug("appkit:logger:event", { useColors: true })("WideEvent created: %s %s (reqId: %s, traceId: %s)", req.method, path, requestId.substring(0, 8), spanContext.traceId.substring(0, 8));
|
|
48
|
+
}
|
|
49
|
+
if (wideEvent.data.service) wideEvent.data.service = {
|
|
50
|
+
...wideEvent.data.service,
|
|
51
|
+
name: "appkit"
|
|
52
|
+
};
|
|
53
|
+
return wideEvent;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Setup response lifecycle handlers for WideEvent finalization
|
|
57
|
+
*/
|
|
58
|
+
function setupResponseHandlers(req, wideEvent) {
|
|
59
|
+
const res = req.res;
|
|
60
|
+
if (!res) return;
|
|
61
|
+
res.once("finish", () => {
|
|
62
|
+
const finalizedData = wideEvent.finalize(res.statusCode || 200);
|
|
63
|
+
if (shouldSample(finalizedData, DEFAULT_SAMPLING_CONFIG)) emitter.emit(finalizedData);
|
|
64
|
+
eventsByRequest.delete(req);
|
|
65
|
+
});
|
|
66
|
+
res.once("close", () => {
|
|
67
|
+
if (!res.writableFinished) eventsByRequest.delete(req);
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Get or create a WideEvent for the given request.
|
|
72
|
+
* If called within wideEventMiddleware context, returns the event from AsyncLocalStorage.
|
|
73
|
+
* Otherwise creates a new event for the request.
|
|
74
|
+
*/
|
|
75
|
+
function getOrCreateEvent(req) {
|
|
76
|
+
let wideEvent = eventsByRequest.get(req);
|
|
77
|
+
if (!wideEvent) {
|
|
78
|
+
const alsEvent = eventStorage.getStore();
|
|
79
|
+
if (alsEvent) {
|
|
80
|
+
eventsByRequest.set(req, alsEvent);
|
|
81
|
+
return alsEvent;
|
|
82
|
+
}
|
|
83
|
+
wideEvent = createEventForRequest(req);
|
|
84
|
+
eventsByRequest.set(req, wideEvent);
|
|
85
|
+
setupResponseHandlers(req, wideEvent);
|
|
86
|
+
}
|
|
87
|
+
return wideEvent;
|
|
88
|
+
}
|
|
89
|
+
/**
|
|
90
|
+
* Get current WideEvent from AsyncLocalStorage or request
|
|
91
|
+
*/
|
|
92
|
+
function getCurrentEvent(req) {
|
|
93
|
+
if (req) return getOrCreateEvent(req);
|
|
94
|
+
return eventStorage.getStore();
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Check if the first argument is an Express Request
|
|
98
|
+
*/
|
|
99
|
+
function isRequest(arg) {
|
|
100
|
+
return typeof arg === "object" && arg !== null && "method" in arg && "path" in arg && typeof arg.method === "string";
|
|
101
|
+
}
|
|
102
|
+
/**
|
|
103
|
+
* Create a logger instance for a specific scope
|
|
104
|
+
* @param scope - The scope identifier (e.g., "connectors:lakebase")
|
|
105
|
+
* @returns Logger instance with debug, info, warn, and error methods
|
|
106
|
+
*
|
|
107
|
+
* @example
|
|
108
|
+
* ```typescript
|
|
109
|
+
* const logger = createLogger("connectors:lakebase");
|
|
110
|
+
*
|
|
111
|
+
* // Regular logging (no request tracking)
|
|
112
|
+
* logger.debug("Connection established with pool size: %d", poolSize);
|
|
113
|
+
* logger.info("Server started on port %d", port);
|
|
114
|
+
*
|
|
115
|
+
* // Request-scoped logging (tracks in WideEvent)
|
|
116
|
+
* logger.debug(req, "Processing query: %s", queryId);
|
|
117
|
+
* logger.error(req, "Query failed: %O", error);
|
|
118
|
+
*
|
|
119
|
+
* // Get WideEvent - works in route handlers (with req) or interceptors (from context)
|
|
120
|
+
* const event = logger.event(req); // In route handler
|
|
121
|
+
* const event = logger.event(); // In interceptor (gets from AsyncLocalStorage)
|
|
122
|
+
* event?.setComponent("analytics", "executeQuery");
|
|
123
|
+
* ```
|
|
124
|
+
*/
|
|
125
|
+
function createLogger(scope) {
|
|
126
|
+
const debug = createDebug(`appkit:${scope}`, { useColors: true });
|
|
127
|
+
const prefix = `[appkit:${scope}]`;
|
|
128
|
+
function debugLog(reqOrMessage, ...args) {
|
|
129
|
+
if (isRequest(reqOrMessage)) {
|
|
130
|
+
const req = reqOrMessage;
|
|
131
|
+
const message = args[0];
|
|
132
|
+
const logArgs = args.slice(1);
|
|
133
|
+
const formatted = format(message, ...logArgs);
|
|
134
|
+
debug(message, ...logArgs);
|
|
135
|
+
getOrCreateEvent(req).addLog("debug", formatted);
|
|
136
|
+
} else debug(reqOrMessage, ...args);
|
|
137
|
+
}
|
|
138
|
+
function infoLog(reqOrMessage, ...args) {
|
|
139
|
+
if (isRequest(reqOrMessage)) {
|
|
140
|
+
const req = reqOrMessage;
|
|
141
|
+
const message = args[0];
|
|
142
|
+
const formatted = format(message, ...args.slice(1));
|
|
143
|
+
console.log(prefix, formatted);
|
|
144
|
+
getOrCreateEvent(req).addLog("info", formatted);
|
|
145
|
+
} else console.log(prefix, format(reqOrMessage, ...args));
|
|
146
|
+
}
|
|
147
|
+
function warnLog(reqOrMessage, ...args) {
|
|
148
|
+
if (isRequest(reqOrMessage)) {
|
|
149
|
+
const req = reqOrMessage;
|
|
150
|
+
const message = args[0];
|
|
151
|
+
const formatted = format(message, ...args.slice(1));
|
|
152
|
+
console.warn(prefix, formatted);
|
|
153
|
+
getOrCreateEvent(req).addLog("warn", formatted);
|
|
154
|
+
} else console.warn(prefix, format(reqOrMessage, ...args));
|
|
155
|
+
}
|
|
156
|
+
function errorLog(reqOrMessage, ...args) {
|
|
157
|
+
if (isRequest(reqOrMessage)) {
|
|
158
|
+
const req = reqOrMessage;
|
|
159
|
+
const message = args[0];
|
|
160
|
+
const formatted = format(message, ...args.slice(1));
|
|
161
|
+
console.error(prefix, formatted);
|
|
162
|
+
getOrCreateEvent(req).addLog("error", formatted);
|
|
163
|
+
} else console.error(prefix, format(reqOrMessage, ...args));
|
|
164
|
+
}
|
|
165
|
+
function event(req) {
|
|
166
|
+
return getCurrentEvent(req);
|
|
167
|
+
}
|
|
168
|
+
return {
|
|
169
|
+
debug: debugLog,
|
|
170
|
+
info: infoLog,
|
|
171
|
+
warn: warnLog,
|
|
172
|
+
error: errorLog,
|
|
173
|
+
event
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
//#endregion
|
|
178
|
+
export { createLogger };
|
|
179
|
+
//# sourceMappingURL=logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"logger.js","names":["createObug"],"sources":["../../src/logging/logger.ts"],"sourcesContent":["import { AsyncLocalStorage } from \"node:async_hooks\";\nimport { format } from \"node:util\";\nimport { trace } from \"@opentelemetry/api\";\nimport type { NextFunction, Request, Response } from \"express\";\nimport { createDebug as createObug } from \"obug\";\nimport { DEFAULT_SAMPLING_CONFIG, shouldSample } from \"./sampling\";\nimport { WideEvent } from \"./wide-event\";\nimport { WideEventEmitter } from \"./wide-event-emitter\";\n\n/**\n * Logger interface for AppKit components\n */\nexport interface Logger {\n /** Debug output (disabled by default, enable via DEBUG env var) */\n debug(message: string, ...args: unknown[]): void;\n debug(req: Request, message: string, ...args: unknown[]): void;\n\n /** Info output (always visible, for operational messages) */\n info(message: string, ...args: unknown[]): void;\n info(req: Request, message: string, ...args: unknown[]): void;\n\n /** Warning output (always visible, for degraded states) */\n warn(message: string, ...args: unknown[]): void;\n warn(req: Request, message: string, ...args: unknown[]): void;\n\n /** Error output (always visible, for failures) */\n error(message: string, ...args: unknown[]): void;\n error(req: Request, message: string, ...args: unknown[]): void;\n\n /** Get request-scoped WideEvent (from AsyncLocalStorage or explicit req) */\n event(req?: Request): WideEvent | undefined;\n}\n\n// AsyncLocalStorage for WideEvent context propagation\nconst eventStorage = new AsyncLocalStorage<WideEvent>();\n\n// WeakMap to store WideEvent per request (for explicit req usage)\nconst eventsByRequest = new WeakMap<Request, WideEvent>();\n\n// Global emitter instance\nconst emitter = new WideEventEmitter();\n\nconst MAX_REQUEST_ID_LENGTH = 128;\n\n/**\n * Sanitize a request ID from user headers\n */\nfunction sanitizeRequestId(id: string): string {\n const sanitized = id.replace(/[^a-zA-Z0-9_.-]/g, \"\");\n return sanitized.slice(0, MAX_REQUEST_ID_LENGTH);\n}\n\n/**\n * Generate a request ID from the request\n */\nfunction generateRequestId(req: Request): string {\n const existingId =\n req.headers[\"x-request-id\"] ||\n req.headers[\"x-correlation-id\"] ||\n req.headers[\"x-amzn-trace-id\"];\n\n if (existingId && typeof existingId === \"string\" && existingId.length > 0) {\n const sanitized = sanitizeRequestId(existingId);\n if (sanitized.length > 0) {\n return sanitized;\n }\n }\n\n return `req_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`;\n}\n\n/**\n * Create a WideEvent for a request\n */\nfunction createEventForRequest(req: Request): WideEvent {\n const requestId = generateRequestId(req);\n const wideEvent = new WideEvent(requestId);\n\n // extract path from request (strip query string)\n const rawPath = req.path || req.url || req.originalUrl;\n const path = rawPath?.split(\"?\")[0];\n wideEvent.set(\"method\", req.method).set(\"path\", path);\n\n // extract user id from request headers (sanitized)\n const rawUserId = req.headers[\"x-forwarded-user\"];\n if (rawUserId && typeof rawUserId === \"string\" && rawUserId.length > 0) {\n const userId = rawUserId.replace(/[^a-zA-Z0-9_@.-]/g, \"\").slice(0, 128);\n if (userId.length > 0) {\n wideEvent.setUser({ id: userId });\n }\n }\n\n // extract trace id from active span for distributed tracing\n const currentSpan = trace.getActiveSpan();\n const spanContext = currentSpan?.spanContext();\n if (spanContext?.traceId) {\n wideEvent.set(\"trace_id\", spanContext.traceId);\n\n const debugLogger = createObug(\"appkit:logger:event\", { useColors: true });\n debugLogger(\n \"WideEvent created: %s %s (reqId: %s, traceId: %s)\",\n req.method,\n path,\n requestId.substring(0, 8),\n spanContext.traceId.substring(0, 8),\n );\n }\n\n // Update service scope\n if (wideEvent.data.service) {\n wideEvent.data.service = {\n ...wideEvent.data.service,\n name: \"appkit\",\n };\n }\n\n return wideEvent;\n}\n\n/**\n * Setup response lifecycle handlers for WideEvent finalization\n */\nfunction setupResponseHandlers(req: Request, wideEvent: WideEvent): void {\n const res = req.res as Response | undefined;\n if (!res) return;\n\n res.once(\"finish\", () => {\n // finalize the event with status code\n const finalizedData = wideEvent.finalize(res.statusCode || 200);\n\n // emit to OpenTelemetry if sampled\n if (shouldSample(finalizedData, DEFAULT_SAMPLING_CONFIG)) {\n emitter.emit(finalizedData);\n }\n\n // clean up the WeakMap\n eventsByRequest.delete(req);\n });\n\n res.once(\"close\", () => {\n if (!res.writableFinished) {\n // request was aborted - just cleanup\n eventsByRequest.delete(req);\n }\n });\n}\n\n/**\n * Express middleware that establishes AsyncLocalStorage context for WideEvent.\n * This properly scopes the context to the entire request lifecycle using run().\n *\n * @example\n * ```typescript\n * import { wideEventMiddleware } from \"@databricks/appkit\";\n *\n * app.use(wideEventMiddleware);\n * ```\n */\nexport function wideEventMiddleware(\n req: Request,\n _res: Response,\n next: NextFunction,\n): void {\n const wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n\n // run() scopes the context to this request's entire async chain\n eventStorage.run(wideEvent, next);\n}\n\n/**\n * Get or create a WideEvent for the given request.\n * If called within wideEventMiddleware context, returns the event from AsyncLocalStorage.\n * Otherwise creates a new event for the request.\n */\nfunction getOrCreateEvent(req: Request): WideEvent {\n // first check if we already have an event\n let wideEvent = eventsByRequest.get(req);\n\n if (!wideEvent) {\n // check if we are in a middleware context\n const alsEvent = eventStorage.getStore();\n if (alsEvent) {\n // store the event in the WeakMap\n eventsByRequest.set(req, alsEvent);\n return alsEvent;\n }\n\n // no middleware context - create event directly\n wideEvent = createEventForRequest(req);\n eventsByRequest.set(req, wideEvent);\n setupResponseHandlers(req, wideEvent);\n }\n\n return wideEvent;\n}\n\n/**\n * Get current WideEvent from AsyncLocalStorage or request\n */\nfunction getCurrentEvent(req?: Request): WideEvent | undefined {\n // if req provided, use it\n if (req) {\n return getOrCreateEvent(req);\n }\n\n // otherwise, get from AsyncLocalStorage\n return eventStorage.getStore();\n}\n\n/**\n * Check if the first argument is an Express Request\n */\nfunction isRequest(arg: unknown): arg is Request {\n return (\n typeof arg === \"object\" &&\n arg !== null &&\n \"method\" in arg &&\n \"path\" in arg &&\n typeof (arg as Request).method === \"string\"\n );\n}\n\n/**\n * Create a logger instance for a specific scope\n * @param scope - The scope identifier (e.g., \"connectors:lakebase\")\n * @returns Logger instance with debug, info, warn, and error methods\n *\n * @example\n * ```typescript\n * const logger = createLogger(\"connectors:lakebase\");\n *\n * // Regular logging (no request tracking)\n * logger.debug(\"Connection established with pool size: %d\", poolSize);\n * logger.info(\"Server started on port %d\", port);\n *\n * // Request-scoped logging (tracks in WideEvent)\n * logger.debug(req, \"Processing query: %s\", queryId);\n * logger.error(req, \"Query failed: %O\", error);\n *\n * // Get WideEvent - works in route handlers (with req) or interceptors (from context)\n * const event = logger.event(req); // In route handler\n * const event = logger.event(); // In interceptor (gets from AsyncLocalStorage)\n * event?.setComponent(\"analytics\", \"executeQuery\");\n * ```\n */\nexport function createLogger(scope: string): Logger {\n const debug = createObug(`appkit:${scope}`, { useColors: true });\n const prefix = `[appkit:${scope}]`;\n\n function debugLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n debug(message, ...logArgs);\n getOrCreateEvent(req).addLog(\"debug\", formatted);\n } else {\n debug(reqOrMessage, ...args);\n }\n }\n\n function infoLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.log(prefix, formatted);\n getOrCreateEvent(req).addLog(\"info\", formatted);\n } else {\n console.log(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function warnLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.warn(prefix, formatted);\n getOrCreateEvent(req).addLog(\"warn\", formatted);\n } else {\n console.warn(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function errorLog(reqOrMessage: Request | string, ...args: unknown[]): void {\n if (isRequest(reqOrMessage)) {\n const req = reqOrMessage;\n const message = args[0] as string;\n const logArgs = args.slice(1);\n const formatted = format(message, ...logArgs);\n\n console.error(prefix, formatted);\n getOrCreateEvent(req).addLog(\"error\", formatted);\n } else {\n console.error(prefix, format(reqOrMessage, ...args));\n }\n }\n\n function event(req?: Request): WideEvent | undefined {\n return getCurrentEvent(req);\n }\n\n return {\n debug: debugLog as Logger[\"debug\"],\n info: infoLog as Logger[\"info\"],\n warn: warnLog as Logger[\"warn\"],\n error: errorLog as Logger[\"error\"],\n event,\n };\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { shouldExcludePath } from "../utils/path-exclusions.js";
|
|
2
|
+
|
|
3
|
+
//#region src/logging/sampling.ts
|
|
4
|
+
/**
|
|
5
|
+
* Get sample rate from environment variable or default to 1.0 (100%)
|
|
6
|
+
*/
|
|
7
|
+
function getSampleRate() {
|
|
8
|
+
const envRate = process.env.APPKIT_SAMPLE_RATE;
|
|
9
|
+
if (envRate) {
|
|
10
|
+
const parsed = parseFloat(envRate);
|
|
11
|
+
if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) return parsed;
|
|
12
|
+
}
|
|
13
|
+
return 1;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Default sampling configuration
|
|
17
|
+
*/
|
|
18
|
+
const DEFAULT_SAMPLING_CONFIG = {
|
|
19
|
+
alwaysSampleIf: {
|
|
20
|
+
hasErrors: true,
|
|
21
|
+
statusCodeGte: 400,
|
|
22
|
+
durationGte: 5e3,
|
|
23
|
+
hasCacheInfo: true
|
|
24
|
+
},
|
|
25
|
+
sampleRate: getSampleRate()
|
|
26
|
+
};
|
|
27
|
+
/**
|
|
28
|
+
* Simple hash function for deterministic sampling
|
|
29
|
+
*/
|
|
30
|
+
function hashString(str) {
|
|
31
|
+
let hash = 0;
|
|
32
|
+
for (let i = 0; i < str.length; i++) {
|
|
33
|
+
const char = str.charCodeAt(i);
|
|
34
|
+
hash = (hash << 5) - hash + char;
|
|
35
|
+
hash = hash & hash;
|
|
36
|
+
}
|
|
37
|
+
return Math.abs(hash);
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* Determine if a WideEvent should be sampled based on configuration.
|
|
41
|
+
* Uses shared path exclusions from utils/path-exclusions.ts.
|
|
42
|
+
*/
|
|
43
|
+
function shouldSample(event, config = DEFAULT_SAMPLING_CONFIG) {
|
|
44
|
+
if (shouldExcludePath(event.path)) return false;
|
|
45
|
+
if (config.alwaysSampleIf.hasErrors && event.error) return true;
|
|
46
|
+
if (config.alwaysSampleIf.statusCodeGte && event.status_code && event.status_code >= config.alwaysSampleIf.statusCodeGte) return true;
|
|
47
|
+
if (config.alwaysSampleIf.durationGte && event.duration_ms && event.duration_ms >= config.alwaysSampleIf.durationGte) return true;
|
|
48
|
+
if (config.alwaysSampleIf.hasCacheInfo && event.execution?.cache_hit !== void 0) return true;
|
|
49
|
+
if (config.sampleRate >= 1) return true;
|
|
50
|
+
if (config.sampleRate <= 0) return false;
|
|
51
|
+
return hashString(event.request_id) % 100 < config.sampleRate * 100;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
//#endregion
|
|
55
|
+
export { DEFAULT_SAMPLING_CONFIG, shouldSample };
|
|
56
|
+
//# sourceMappingURL=sampling.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"sampling.js","names":[],"sources":["../../src/logging/sampling.ts"],"sourcesContent":["import { shouldExcludePath } from \"../utils/path-exclusions\";\nimport type { WideEventData } from \"./wide-event\";\n\n/**\n * Sampling configuration for WideEvents\n */\nexport interface SamplingConfig {\n /** Always sample if any of these conditions are true */\n alwaysSampleIf: {\n /** Sample if event has errors */\n hasErrors: boolean;\n /** Sample if status code >= this value (e.g., 400) */\n statusCodeGte: number;\n /** Sample if duration >= this value in ms (e.g., 5000) */\n durationGte: number;\n /** Sample if cache was used (hit or miss tracked) */\n hasCacheInfo: boolean;\n };\n\n /** Sample rate for normal requests (0-1, e.g., 0.1 = 10%) */\n sampleRate: number;\n}\n\n/**\n * Get sample rate from environment variable or default to 1.0 (100%)\n */\nfunction getSampleRate(): number {\n const envRate = process.env.APPKIT_SAMPLE_RATE;\n if (envRate) {\n const parsed = parseFloat(envRate);\n if (!Number.isNaN(parsed) && parsed >= 0 && parsed <= 1) {\n return parsed;\n }\n }\n return 1;\n}\n\n/**\n * Default sampling configuration\n */\nexport const DEFAULT_SAMPLING_CONFIG: SamplingConfig = {\n alwaysSampleIf: {\n hasErrors: true,\n statusCodeGte: 400,\n durationGte: 5000, // 5 seconds\n hasCacheInfo: true, // Always sample requests with cache info (hit or miss)\n },\n sampleRate: getSampleRate(),\n};\n\n/**\n * Simple hash function for deterministic sampling\n */\nfunction hashString(str: string): number {\n let hash = 0;\n for (let i = 0; i < str.length; i++) {\n const char = str.charCodeAt(i);\n hash = (hash << 5) - hash + char;\n hash = hash & hash; // Convert to 32-bit integer\n }\n return Math.abs(hash);\n}\n\n/**\n * Determine if a WideEvent should be sampled based on configuration.\n * Uses shared path exclusions from utils/path-exclusions.ts.\n */\nexport function shouldSample(\n event: WideEventData,\n config: SamplingConfig = DEFAULT_SAMPLING_CONFIG,\n): boolean {\n // Check exclusions first using shared path exclusions\n if (shouldExcludePath(event.path)) {\n return false;\n }\n\n // Always sample if has errors\n if (config.alwaysSampleIf.hasErrors && event.error) {\n return true;\n }\n\n // Always sample if status code >= threshold\n if (\n config.alwaysSampleIf.statusCodeGte &&\n event.status_code &&\n event.status_code >= config.alwaysSampleIf.statusCodeGte\n ) {\n return true;\n }\n\n // Always sample if duration >= threshold\n if (\n config.alwaysSampleIf.durationGte &&\n event.duration_ms &&\n event.duration_ms >= config.alwaysSampleIf.durationGte\n ) {\n return true;\n }\n\n // Always sample if cache info is present (cache hit or miss)\n if (\n config.alwaysSampleIf.hasCacheInfo &&\n event.execution?.cache_hit !== undefined\n ) {\n return true;\n }\n\n // Sample based on sample rate\n if (config.sampleRate >= 1) {\n return true;\n }\n\n if (config.sampleRate <= 0) {\n return false;\n }\n\n // Deterministic sampling based on request ID\n const hash = hashString(event.request_id);\n return hash % 100 < config.sampleRate * 100;\n}\n"],"mappings":";;;;;;AA0BA,SAAS,gBAAwB;CAC/B,MAAM,UAAU,QAAQ,IAAI;AAC5B,KAAI,SAAS;EACX,MAAM,SAAS,WAAW,QAAQ;AAClC,MAAI,CAAC,OAAO,MAAM,OAAO,IAAI,UAAU,KAAK,UAAU,EACpD,QAAO;;AAGX,QAAO;;;;;AAMT,MAAa,0BAA0C;CACrD,gBAAgB;EACd,WAAW;EACX,eAAe;EACf,aAAa;EACb,cAAc;EACf;CACD,YAAY,eAAe;CAC5B;;;;AAKD,SAAS,WAAW,KAAqB;CACvC,IAAI,OAAO;AACX,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK;EACnC,MAAM,OAAO,IAAI,WAAW,EAAE;AAC9B,UAAQ,QAAQ,KAAK,OAAO;AAC5B,SAAO,OAAO;;AAEhB,QAAO,KAAK,IAAI,KAAK;;;;;;AAOvB,SAAgB,aACd,OACA,SAAyB,yBAChB;AAET,KAAI,kBAAkB,MAAM,KAAK,CAC/B,QAAO;AAIT,KAAI,OAAO,eAAe,aAAa,MAAM,MAC3C,QAAO;AAIT,KACE,OAAO,eAAe,iBACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,cAE3C,QAAO;AAIT,KACE,OAAO,eAAe,eACtB,MAAM,eACN,MAAM,eAAe,OAAO,eAAe,YAE3C,QAAO;AAIT,KACE,OAAO,eAAe,gBACtB,MAAM,WAAW,cAAc,OAE/B,QAAO;AAIT,KAAI,OAAO,cAAc,EACvB,QAAO;AAGT,KAAI,OAAO,cAAc,EACvB,QAAO;AAKT,QADa,WAAW,MAAM,WAAW,GAC3B,MAAM,OAAO,aAAa"}
|