@databricks/appkit 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +3 -0
- package/DCO +25 -0
- package/LICENSE +203 -0
- package/NOTICE.md +73 -0
- package/README.md +35 -0
- package/bin/setup-claude.js +190 -0
- package/dist/_virtual/rolldown_runtime.js +39 -0
- package/dist/analytics/analytics.d.ts +31 -0
- package/dist/analytics/analytics.d.ts.map +1 -0
- package/dist/analytics/analytics.js +149 -0
- package/dist/analytics/analytics.js.map +1 -0
- package/dist/analytics/defaults.js +17 -0
- package/dist/analytics/defaults.js.map +1 -0
- package/dist/analytics/index.js +3 -0
- package/dist/analytics/query.js +50 -0
- package/dist/analytics/query.js.map +1 -0
- package/dist/analytics/types.d.ts +9 -0
- package/dist/analytics/types.d.ts.map +1 -0
- package/dist/app/index.d.ts +23 -0
- package/dist/app/index.d.ts.map +1 -0
- package/dist/app/index.js +49 -0
- package/dist/app/index.js.map +1 -0
- package/dist/appkit/package.js +7 -0
- package/dist/appkit/package.js.map +1 -0
- package/dist/cache/defaults.js +14 -0
- package/dist/cache/defaults.js.map +1 -0
- package/dist/cache/index.d.ts +119 -0
- package/dist/cache/index.d.ts.map +1 -0
- package/dist/cache/index.js +307 -0
- package/dist/cache/index.js.map +1 -0
- package/dist/cache/storage/defaults.js +16 -0
- package/dist/cache/storage/defaults.js.map +1 -0
- package/dist/cache/storage/index.js +4 -0
- package/dist/cache/storage/memory.js +87 -0
- package/dist/cache/storage/memory.js.map +1 -0
- package/dist/cache/storage/persistent.js +211 -0
- package/dist/cache/storage/persistent.js.map +1 -0
- package/dist/connectors/index.js +6 -0
- package/dist/connectors/lakebase/client.js +348 -0
- package/dist/connectors/lakebase/client.js.map +1 -0
- package/dist/connectors/lakebase/defaults.js +13 -0
- package/dist/connectors/lakebase/defaults.js.map +1 -0
- package/dist/connectors/lakebase/index.js +3 -0
- package/dist/connectors/sql-warehouse/client.js +284 -0
- package/dist/connectors/sql-warehouse/client.js.map +1 -0
- package/dist/connectors/sql-warehouse/defaults.js +12 -0
- package/dist/connectors/sql-warehouse/defaults.js.map +1 -0
- package/dist/connectors/sql-warehouse/index.js +3 -0
- package/dist/core/appkit.d.ts +14 -0
- package/dist/core/appkit.d.ts.map +1 -0
- package/dist/core/appkit.js +66 -0
- package/dist/core/appkit.js.map +1 -0
- package/dist/core/index.js +3 -0
- package/dist/index.d.ts +15 -0
- package/dist/index.js +21 -0
- package/dist/index.js.map +1 -0
- package/dist/plugin/dev-reader.d.ts +20 -0
- package/dist/plugin/dev-reader.d.ts.map +1 -0
- package/dist/plugin/dev-reader.js +63 -0
- package/dist/plugin/dev-reader.js.map +1 -0
- package/dist/plugin/index.js +4 -0
- package/dist/plugin/interceptors/cache.js +15 -0
- package/dist/plugin/interceptors/cache.js.map +1 -0
- package/dist/plugin/interceptors/retry.js +32 -0
- package/dist/plugin/interceptors/retry.js.map +1 -0
- package/dist/plugin/interceptors/telemetry.js +33 -0
- package/dist/plugin/interceptors/telemetry.js.map +1 -0
- package/dist/plugin/interceptors/timeout.js +35 -0
- package/dist/plugin/interceptors/timeout.js.map +1 -0
- package/dist/plugin/plugin.d.ts +43 -0
- package/dist/plugin/plugin.d.ts.map +1 -0
- package/dist/plugin/plugin.js +119 -0
- package/dist/plugin/plugin.js.map +1 -0
- package/dist/plugin/to-plugin.d.ts +7 -0
- package/dist/plugin/to-plugin.d.ts.map +1 -0
- package/dist/plugin/to-plugin.js +12 -0
- package/dist/plugin/to-plugin.js.map +1 -0
- package/dist/server/base-server.js +24 -0
- package/dist/server/base-server.js.map +1 -0
- package/dist/server/index.d.ts +100 -0
- package/dist/server/index.d.ts.map +1 -0
- package/dist/server/index.js +224 -0
- package/dist/server/index.js.map +1 -0
- package/dist/server/remote-tunnel/denied.html +68 -0
- package/dist/server/remote-tunnel/gate.js +51 -0
- package/dist/server/remote-tunnel/gate.js.map +1 -0
- package/dist/server/remote-tunnel/index.html +165 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js +100 -0
- package/dist/server/remote-tunnel/remote-tunnel-controller.js.map +1 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js +320 -0
- package/dist/server/remote-tunnel/remote-tunnel-manager.js.map +1 -0
- package/dist/server/remote-tunnel/wait.html +158 -0
- package/dist/server/static-server.js +47 -0
- package/dist/server/static-server.js.map +1 -0
- package/dist/server/types.d.ts +14 -0
- package/dist/server/types.d.ts.map +1 -0
- package/dist/server/utils.js +70 -0
- package/dist/server/utils.js.map +1 -0
- package/dist/server/vite-dev-server.js +103 -0
- package/dist/server/vite-dev-server.js.map +1 -0
- package/dist/shared/src/cache.d.ts +62 -0
- package/dist/shared/src/cache.d.ts.map +1 -0
- package/dist/shared/src/execute.d.ts +46 -0
- package/dist/shared/src/execute.d.ts.map +1 -0
- package/dist/shared/src/plugin.d.ts +50 -0
- package/dist/shared/src/plugin.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.d.ts +160 -0
- package/dist/shared/src/sql/helpers.d.ts.map +1 -0
- package/dist/shared/src/sql/helpers.js +103 -0
- package/dist/shared/src/sql/helpers.js.map +1 -0
- package/dist/shared/src/sql/types.d.ts +34 -0
- package/dist/shared/src/sql/types.d.ts.map +1 -0
- package/dist/shared/src/tunnel.d.ts +30 -0
- package/dist/shared/src/tunnel.d.ts.map +1 -0
- package/dist/stream/arrow-stream-processor.js +154 -0
- package/dist/stream/arrow-stream-processor.js.map +1 -0
- package/dist/stream/buffers.js +88 -0
- package/dist/stream/buffers.js.map +1 -0
- package/dist/stream/defaults.js +14 -0
- package/dist/stream/defaults.js.map +1 -0
- package/dist/stream/index.js +6 -0
- package/dist/stream/sse-writer.js +61 -0
- package/dist/stream/sse-writer.js.map +1 -0
- package/dist/stream/stream-manager.d.ts +27 -0
- package/dist/stream/stream-manager.d.ts.map +1 -0
- package/dist/stream/stream-manager.js +191 -0
- package/dist/stream/stream-manager.js.map +1 -0
- package/dist/stream/stream-registry.js +54 -0
- package/dist/stream/stream-registry.js.map +1 -0
- package/dist/stream/types.js +14 -0
- package/dist/stream/types.js.map +1 -0
- package/dist/stream/validator.js +25 -0
- package/dist/stream/validator.js.map +1 -0
- package/dist/telemetry/config.js +20 -0
- package/dist/telemetry/config.js.map +1 -0
- package/dist/telemetry/index.d.ts +4 -0
- package/dist/telemetry/index.js +8 -0
- package/dist/telemetry/instrumentations.js +38 -0
- package/dist/telemetry/instrumentations.js.map +1 -0
- package/dist/telemetry/noop.js +54 -0
- package/dist/telemetry/noop.js.map +1 -0
- package/dist/telemetry/telemetry-manager.js +113 -0
- package/dist/telemetry/telemetry-manager.js.map +1 -0
- package/dist/telemetry/telemetry-provider.js +82 -0
- package/dist/telemetry/telemetry-provider.js.map +1 -0
- package/dist/telemetry/types.d.ts +74 -0
- package/dist/telemetry/types.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.d.ts +22 -0
- package/dist/type-generator/vite-plugin.d.ts.map +1 -0
- package/dist/type-generator/vite-plugin.js +49 -0
- package/dist/type-generator/vite-plugin.js.map +1 -0
- package/dist/utils/databricks-client-middleware.d.ts +17 -0
- package/dist/utils/databricks-client-middleware.d.ts.map +1 -0
- package/dist/utils/databricks-client-middleware.js +117 -0
- package/dist/utils/databricks-client-middleware.js.map +1 -0
- package/dist/utils/env-validator.js +14 -0
- package/dist/utils/env-validator.js.map +1 -0
- package/dist/utils/index.js +26 -0
- package/dist/utils/index.js.map +1 -0
- package/dist/utils/merge.js +25 -0
- package/dist/utils/merge.js.map +1 -0
- package/dist/utils/vite-config-merge.js +22 -0
- package/dist/utils/vite-config-merge.js.map +1 -0
- package/llms.txt +193 -0
- package/package.json +70 -0
- package/scripts/postinstall.js +6 -0
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
import { getRequestContext, getWorkspaceClient } from "../utils/databricks-client-middleware.js";
|
|
2
|
+
import { init_utils } from "../utils/index.js";
|
|
3
|
+
import { SQLWarehouseConnector } from "../connectors/sql-warehouse/client.js";
|
|
4
|
+
import "../connectors/index.js";
|
|
5
|
+
import { Plugin } from "../plugin/plugin.js";
|
|
6
|
+
import { toPlugin } from "../plugin/to-plugin.js";
|
|
7
|
+
import "../plugin/index.js";
|
|
8
|
+
import { queryDefaults } from "./defaults.js";
|
|
9
|
+
import { QueryProcessor } from "./query.js";
|
|
10
|
+
|
|
11
|
+
//#region src/analytics/analytics.ts
|
|
12
|
+
init_utils();
|
|
13
|
+
var AnalyticsPlugin = class extends Plugin {
|
|
14
|
+
static {
|
|
15
|
+
this.description = "Analytics plugin for data analysis";
|
|
16
|
+
}
|
|
17
|
+
constructor(config) {
|
|
18
|
+
super(config);
|
|
19
|
+
this.name = "analytics";
|
|
20
|
+
this.envVars = [];
|
|
21
|
+
this.requiresDatabricksClient = true;
|
|
22
|
+
this.config = config;
|
|
23
|
+
this.queryProcessor = new QueryProcessor();
|
|
24
|
+
this.SQLClient = new SQLWarehouseConnector({
|
|
25
|
+
timeout: config.timeout,
|
|
26
|
+
telemetry: config.telemetry
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
injectRoutes(router) {
|
|
30
|
+
this.route(router, {
|
|
31
|
+
name: "arrow",
|
|
32
|
+
method: "get",
|
|
33
|
+
path: "/arrow-result/:jobId",
|
|
34
|
+
handler: async (req, res) => {
|
|
35
|
+
await this._handleArrowRoute(req, res);
|
|
36
|
+
}
|
|
37
|
+
});
|
|
38
|
+
this.route(router, {
|
|
39
|
+
name: "arrowAsUser",
|
|
40
|
+
method: "get",
|
|
41
|
+
path: "/users/me/arrow-result/:jobId",
|
|
42
|
+
handler: async (req, res) => {
|
|
43
|
+
await this._handleArrowRoute(req, res, { asUser: true });
|
|
44
|
+
}
|
|
45
|
+
});
|
|
46
|
+
this.route(router, {
|
|
47
|
+
name: "queryAsUser",
|
|
48
|
+
method: "post",
|
|
49
|
+
path: "/users/me/query/:query_key",
|
|
50
|
+
handler: async (req, res) => {
|
|
51
|
+
await this._handleQueryRoute(req, res, { asUser: true });
|
|
52
|
+
}
|
|
53
|
+
});
|
|
54
|
+
this.route(router, {
|
|
55
|
+
name: "query",
|
|
56
|
+
method: "post",
|
|
57
|
+
path: "/query/:query_key",
|
|
58
|
+
handler: async (req, res) => {
|
|
59
|
+
await this._handleQueryRoute(req, res, { asUser: false });
|
|
60
|
+
}
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
async _handleArrowRoute(req, res, { asUser = false } = {}) {
|
|
64
|
+
try {
|
|
65
|
+
const { jobId } = req.params;
|
|
66
|
+
const workspaceClient = getWorkspaceClient(asUser);
|
|
67
|
+
console.log(`Processing Arrow job request: ${jobId} for plugin: ${this.name}`);
|
|
68
|
+
const result = await this.getArrowData(workspaceClient, jobId);
|
|
69
|
+
res.setHeader("Content-Type", "application/octet-stream");
|
|
70
|
+
res.setHeader("Content-Length", result.data.length.toString());
|
|
71
|
+
res.setHeader("Cache-Control", "public, max-age=3600");
|
|
72
|
+
console.log(`Sending Arrow buffer: ${result.data.length} bytes for job ${jobId}`);
|
|
73
|
+
res.send(Buffer.from(result.data));
|
|
74
|
+
} catch (error) {
|
|
75
|
+
console.error(`Arrow job error for ${this.name}:`, error);
|
|
76
|
+
res.status(404).json({
|
|
77
|
+
error: error instanceof Error ? error.message : "Arrow job not found",
|
|
78
|
+
plugin: this.name
|
|
79
|
+
});
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
async _handleQueryRoute(req, res, { asUser = false } = {}) {
|
|
83
|
+
const { query_key } = req.params;
|
|
84
|
+
const { parameters, format = "JSON" } = req.body;
|
|
85
|
+
const queryParameters = format === "ARROW" ? {
|
|
86
|
+
formatParameters: {
|
|
87
|
+
disposition: "EXTERNAL_LINKS",
|
|
88
|
+
format: "ARROW_STREAM"
|
|
89
|
+
},
|
|
90
|
+
type: "arrow"
|
|
91
|
+
} : { type: "result" };
|
|
92
|
+
const requestContext = getRequestContext();
|
|
93
|
+
const userKey = asUser ? requestContext.userId : requestContext.serviceUserId;
|
|
94
|
+
if (!query_key) {
|
|
95
|
+
res.status(400).json({ error: "query_key is required" });
|
|
96
|
+
return;
|
|
97
|
+
}
|
|
98
|
+
const query = await this.app.getAppQuery(query_key, req, this.devFileReader);
|
|
99
|
+
if (!query) {
|
|
100
|
+
res.status(404).json({ error: "Query not found" });
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
const hashedQuery = this.queryProcessor.hashQuery(query);
|
|
104
|
+
const streamExecutionSettings = { default: {
|
|
105
|
+
...queryDefaults,
|
|
106
|
+
cache: {
|
|
107
|
+
...queryDefaults.cache,
|
|
108
|
+
cacheKey: [
|
|
109
|
+
"analytics:query",
|
|
110
|
+
query_key,
|
|
111
|
+
JSON.stringify(parameters),
|
|
112
|
+
JSON.stringify(format),
|
|
113
|
+
hashedQuery,
|
|
114
|
+
userKey
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
} };
|
|
118
|
+
await this.executeStream(res, async (signal) => {
|
|
119
|
+
const processedParams = await this.queryProcessor.processQueryParams(query, parameters);
|
|
120
|
+
const result = await this.query(query, processedParams, queryParameters.formatParameters, signal, { asUser });
|
|
121
|
+
return {
|
|
122
|
+
type: queryParameters.type,
|
|
123
|
+
...result
|
|
124
|
+
};
|
|
125
|
+
}, streamExecutionSettings, userKey);
|
|
126
|
+
}
|
|
127
|
+
async query(query, parameters, formatParameters, signal, { asUser = false } = {}) {
|
|
128
|
+
const requestContext = getRequestContext();
|
|
129
|
+
const workspaceClient = getWorkspaceClient(asUser);
|
|
130
|
+
const { statement, parameters: sqlParameters } = this.queryProcessor.convertToSQLParameters(query, parameters);
|
|
131
|
+
return (await this.SQLClient.executeStatement(workspaceClient, {
|
|
132
|
+
statement,
|
|
133
|
+
warehouse_id: await requestContext.warehouseId,
|
|
134
|
+
parameters: sqlParameters,
|
|
135
|
+
...formatParameters
|
|
136
|
+
}, signal)).result;
|
|
137
|
+
}
|
|
138
|
+
async getArrowData(workspaceClient, jobId, signal) {
|
|
139
|
+
return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);
|
|
140
|
+
}
|
|
141
|
+
async shutdown() {
|
|
142
|
+
this.streamManager.abortAll();
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
const analytics = toPlugin(AnalyticsPlugin, "analytics");
|
|
146
|
+
|
|
147
|
+
//#endregion
|
|
148
|
+
export { AnalyticsPlugin, analytics };
|
|
149
|
+
//# sourceMappingURL=analytics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"analytics.js","names":["streamExecutionSettings: StreamExecutionSettings"],"sources":["../../src/analytics/analytics.ts"],"sourcesContent":["import type { WorkspaceClient } from \"@databricks/sdk-experimental\";\nimport type {\n IAppRouter,\n PluginExecuteConfig,\n SQLTypeMarker,\n StreamExecutionSettings,\n} from \"shared\";\nimport { SQLWarehouseConnector } from \"../connectors\";\nimport { Plugin, toPlugin } from \"../plugin\";\nimport type { Request, Response } from \"../utils\";\nimport { getRequestContext, getWorkspaceClient } from \"../utils\";\nimport { queryDefaults } from \"./defaults\";\nimport { QueryProcessor } from \"./query\";\nimport type {\n AnalyticsQueryResponse,\n IAnalyticsConfig,\n IAnalyticsQueryRequest,\n} from \"./types\";\n\nexport class AnalyticsPlugin extends Plugin {\n name = \"analytics\";\n envVars = [];\n requiresDatabricksClient = true;\n\n protected static description = \"Analytics plugin for data analysis\";\n protected declare config: IAnalyticsConfig;\n\n // analytics services\n private SQLClient: SQLWarehouseConnector;\n private queryProcessor: QueryProcessor;\n\n constructor(config: IAnalyticsConfig) {\n super(config);\n this.config = config;\n this.queryProcessor = new QueryProcessor();\n\n this.SQLClient = new SQLWarehouseConnector({\n timeout: config.timeout,\n telemetry: config.telemetry,\n });\n }\n\n injectRoutes(router: IAppRouter) {\n this.route(router, {\n name: \"arrow\",\n method: \"get\",\n path: \"/arrow-result/:jobId\",\n handler: async (req: Request, res: Response) => {\n await this._handleArrowRoute(req, res);\n },\n });\n\n this.route(router, {\n name: \"arrowAsUser\",\n method: \"get\",\n path: \"/users/me/arrow-result/:jobId\",\n handler: async (req: Request, res: Response) => {\n await this._handleArrowRoute(req, res, { asUser: true });\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"queryAsUser\",\n method: \"post\",\n path: \"/users/me/query/:query_key\",\n handler: async (req: Request, res: Response) => {\n await this._handleQueryRoute(req, res, { asUser: true });\n },\n });\n\n this.route<AnalyticsQueryResponse>(router, {\n name: \"query\",\n method: \"post\",\n path: \"/query/:query_key\",\n handler: async (req: Request, res: Response) => {\n await this._handleQueryRoute(req, res, { asUser: false });\n },\n });\n }\n\n private async _handleArrowRoute(\n req: Request,\n res: Response,\n { asUser = false }: { asUser?: boolean } = {},\n ): Promise<void> {\n try {\n const { jobId } = req.params;\n\n const workspaceClient = getWorkspaceClient(asUser);\n\n console.log(\n `Processing Arrow job request: ${jobId} for plugin: ${this.name}`,\n );\n\n const result = await this.getArrowData(workspaceClient, jobId);\n\n res.setHeader(\"Content-Type\", \"application/octet-stream\");\n res.setHeader(\"Content-Length\", result.data.length.toString());\n res.setHeader(\"Cache-Control\", \"public, max-age=3600\");\n\n console.log(\n `Sending Arrow buffer: ${result.data.length} bytes for job ${jobId}`,\n );\n res.send(Buffer.from(result.data));\n } catch (error) {\n console.error(`Arrow job error for ${this.name}:`, error);\n res.status(404).json({\n error: error instanceof Error ? error.message : \"Arrow job not found\",\n plugin: this.name,\n });\n }\n }\n\n private async _handleQueryRoute(\n req: Request,\n res: Response,\n { asUser = false }: { asUser?: boolean } = {},\n ): Promise<void> {\n const { query_key } = req.params;\n const { parameters, format = \"JSON\" } = req.body as IAnalyticsQueryRequest;\n const queryParameters =\n format === \"ARROW\"\n ? {\n formatParameters: {\n disposition: \"EXTERNAL_LINKS\",\n format: \"ARROW_STREAM\",\n },\n type: \"arrow\",\n }\n : {\n type: \"result\",\n };\n\n const requestContext = getRequestContext();\n const userKey = asUser\n ? requestContext.userId\n : requestContext.serviceUserId;\n\n if (!query_key) {\n res.status(400).json({ error: \"query_key is required\" });\n return;\n }\n\n const query = await this.app.getAppQuery(\n query_key,\n req,\n this.devFileReader,\n );\n\n if (!query) {\n res.status(404).json({ error: \"Query not found\" });\n return;\n }\n\n const hashedQuery = this.queryProcessor.hashQuery(query);\n\n const defaultConfig: PluginExecuteConfig = {\n ...queryDefaults,\n cache: {\n ...queryDefaults.cache,\n cacheKey: [\n \"analytics:query\",\n query_key,\n JSON.stringify(parameters),\n JSON.stringify(format),\n hashedQuery,\n userKey,\n ],\n },\n };\n\n const streamExecutionSettings: StreamExecutionSettings = {\n default: defaultConfig,\n };\n\n await this.executeStream(\n res,\n async (signal) => {\n const processedParams = await this.queryProcessor.processQueryParams(\n query,\n parameters,\n );\n\n const result = await this.query(\n query,\n processedParams,\n queryParameters.formatParameters,\n signal,\n {\n asUser,\n },\n );\n\n return { type: queryParameters.type, ...result };\n },\n streamExecutionSettings,\n userKey,\n );\n }\n\n async query(\n query: string,\n parameters?: Record<string, SQLTypeMarker | null | undefined>,\n formatParameters?: Record<string, any>,\n signal?: AbortSignal,\n { asUser = false }: { asUser?: boolean } = {},\n ): Promise<any> {\n const requestContext = getRequestContext();\n const workspaceClient = getWorkspaceClient(asUser);\n\n const { statement, parameters: sqlParameters } =\n this.queryProcessor.convertToSQLParameters(query, parameters);\n\n const response = await this.SQLClient.executeStatement(\n workspaceClient,\n {\n statement,\n warehouse_id: await requestContext.warehouseId,\n parameters: sqlParameters,\n ...formatParameters,\n },\n signal,\n );\n\n return response.result;\n }\n\n // If we need arrow stream in more plugins we can define this as a base method in the core plugin class\n // and have a generic endpoint for each plugin that consumes this arrow data.\n protected async getArrowData(\n workspaceClient: WorkspaceClient,\n jobId: string,\n signal?: AbortSignal,\n ): Promise<ReturnType<typeof this.SQLClient.getArrowData>> {\n return await this.SQLClient.getArrowData(workspaceClient, jobId, signal);\n }\n\n async shutdown(): Promise<void> {\n this.streamManager.abortAll();\n }\n}\n\nexport const analytics = toPlugin<\n typeof AnalyticsPlugin,\n IAnalyticsConfig,\n \"analytics\"\n>(AnalyticsPlugin, \"analytics\");\n"],"mappings":"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"}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
//#region src/analytics/defaults.ts
|
|
2
|
+
const queryDefaults = {
|
|
3
|
+
cache: {
|
|
4
|
+
enabled: true,
|
|
5
|
+
ttl: 3600
|
|
6
|
+
},
|
|
7
|
+
retry: {
|
|
8
|
+
enabled: true,
|
|
9
|
+
initialDelay: 1500,
|
|
10
|
+
attempts: 3
|
|
11
|
+
},
|
|
12
|
+
timeout: 18e3
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
//#endregion
|
|
16
|
+
export { queryDefaults };
|
|
17
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","names":["queryDefaults: PluginExecuteConfig"],"sources":["../../src/analytics/defaults.ts"],"sourcesContent":["import type { PluginExecuteConfig } from \"shared\";\n\nexport const queryDefaults: PluginExecuteConfig = {\n cache: {\n enabled: true,\n ttl: 3600,\n },\n retry: {\n enabled: true,\n initialDelay: 1500,\n attempts: 3,\n },\n timeout: 18000,\n};\n"],"mappings":";AAEA,MAAaA,gBAAqC;CAChD,OAAO;EACL,SAAS;EACT,KAAK;EACN;CACD,OAAO;EACL,SAAS;EACT,cAAc;EACd,UAAU;EACX;CACD,SAAS;CACV"}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { isSQLTypeMarker, sql } from "../shared/src/sql/helpers.js";
|
|
2
|
+
import { getRequestContext } from "../utils/databricks-client-middleware.js";
|
|
3
|
+
import { init_utils } from "../utils/index.js";
|
|
4
|
+
import { createHash } from "node:crypto";
|
|
5
|
+
|
|
6
|
+
//#region src/analytics/query.ts
|
|
7
|
+
init_utils();
|
|
8
|
+
var QueryProcessor = class {
|
|
9
|
+
async processQueryParams(query, parameters) {
|
|
10
|
+
const processed = { ...parameters };
|
|
11
|
+
const paramMatches = query.matchAll(/:([a-zA-Z_]\w*)/g);
|
|
12
|
+
if (new Set(Array.from(paramMatches, (m) => m[1])).has("workspaceId") && !processed.workspaceId) {
|
|
13
|
+
const workspaceId = await getRequestContext().workspaceId;
|
|
14
|
+
if (workspaceId) processed.workspaceId = sql.string(workspaceId);
|
|
15
|
+
}
|
|
16
|
+
return processed;
|
|
17
|
+
}
|
|
18
|
+
hashQuery(query) {
|
|
19
|
+
return createHash("md5").update(query).digest("hex");
|
|
20
|
+
}
|
|
21
|
+
convertToSQLParameters(query, parameters) {
|
|
22
|
+
const sqlParameters = [];
|
|
23
|
+
if (parameters) {
|
|
24
|
+
const queryParamMatches = query.matchAll(/:([a-zA-Z_]\w*)/g);
|
|
25
|
+
const queryParams = new Set(Array.from(queryParamMatches, (m) => m[1]));
|
|
26
|
+
for (const key of Object.keys(parameters)) if (!queryParams.has(key)) throw new Error(`Parameter "${key}" not found in query. Valid parameters: ${Array.from(queryParams).join(", ") || "none"}`);
|
|
27
|
+
for (const [key, value] of Object.entries(parameters)) {
|
|
28
|
+
const parameter = this._createParameter(key, value);
|
|
29
|
+
if (parameter) sqlParameters.push(parameter);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return {
|
|
33
|
+
statement: query,
|
|
34
|
+
parameters: sqlParameters
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
_createParameter(key, value) {
|
|
38
|
+
if (value === null || value === void 0) return null;
|
|
39
|
+
if (!isSQLTypeMarker(value)) throw new Error(`Parameter "${key}" must be a SQL type. Use sql.string(), sql.number(), sql.date(), sql.timestamp(), or sql.boolean().`);
|
|
40
|
+
return {
|
|
41
|
+
name: key,
|
|
42
|
+
value: value.value,
|
|
43
|
+
type: value.__sql_type
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
};
|
|
47
|
+
|
|
48
|
+
//#endregion
|
|
49
|
+
export { QueryProcessor };
|
|
50
|
+
//# sourceMappingURL=query.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"query.js","names":["sqlHelpers","sqlParameters: sql.StatementParameterListItem[]"],"sources":["../../src/analytics/query.ts"],"sourcesContent":["import { createHash } from \"node:crypto\";\nimport type { sql } from \"@databricks/sdk-experimental\";\nimport { isSQLTypeMarker, type SQLTypeMarker, sql as sqlHelpers } from \"shared\";\nimport { getRequestContext } from \"../utils\";\n\ntype SQLParameterValue = SQLTypeMarker | null | undefined;\n\nexport class QueryProcessor {\n async processQueryParams(\n query: string,\n parameters?: Record<string, SQLParameterValue>,\n ): Promise<Record<string, SQLParameterValue>> {\n const processed = { ...parameters };\n\n // extract all params from the query\n const paramMatches = query.matchAll(/:([a-zA-Z_]\\w*)/g);\n const queryParams = new Set(Array.from(paramMatches, (m) => m[1]));\n\n // auto-inject workspaceId if needed and not provided\n if (queryParams.has(\"workspaceId\") && !processed.workspaceId) {\n const requestContext = getRequestContext();\n const workspaceId = await requestContext.workspaceId;\n if (workspaceId) {\n processed.workspaceId = sqlHelpers.string(workspaceId);\n }\n }\n\n return processed;\n }\n\n hashQuery(query: string): string {\n return createHash(\"md5\").update(query).digest(\"hex\");\n }\n\n convertToSQLParameters(\n query: string,\n parameters?: Record<string, SQLParameterValue>,\n ): { statement: string; parameters: sql.StatementParameterListItem[] } {\n const sqlParameters: sql.StatementParameterListItem[] = [];\n\n if (parameters) {\n // extract all params from the query\n const queryParamMatches = query.matchAll(/:([a-zA-Z_]\\w*)/g);\n const queryParams = new Set(Array.from(queryParamMatches, (m) => m[1]));\n\n // only allow parameters that exist in the query\n for (const key of Object.keys(parameters)) {\n if (!queryParams.has(key)) {\n throw new Error(\n `Parameter \"${key}\" not found in query. Valid parameters: ${\n Array.from(queryParams).join(\", \") || \"none\"\n }`,\n );\n }\n }\n\n // convert parameters to SQL parameters\n for (const [key, value] of Object.entries(parameters)) {\n const parameter = this._createParameter(key, value);\n if (parameter) {\n sqlParameters.push(parameter);\n }\n }\n }\n\n return { statement: query, parameters: sqlParameters };\n }\n\n private _createParameter(\n key: string,\n value: SQLParameterValue,\n ): sql.StatementParameterListItem | null {\n if (value === null || value === undefined) {\n return null;\n }\n\n if (!isSQLTypeMarker(value)) {\n throw new Error(\n `Parameter \"${key}\" must be a SQL type. Use sql.string(), sql.number(), sql.date(), sql.timestamp(), or sql.boolean().`,\n );\n }\n\n return {\n name: key,\n value: value.value,\n type: value.__sql_type,\n };\n }\n}\n"],"mappings":";;;;;;YAG6C;AAI7C,IAAa,iBAAb,MAA4B;CAC1B,MAAM,mBACJ,OACA,YAC4C;EAC5C,MAAM,YAAY,EAAE,GAAG,YAAY;EAGnC,MAAM,eAAe,MAAM,SAAS,mBAAmB;AAIvD,MAHoB,IAAI,IAAI,MAAM,KAAK,eAAe,MAAM,EAAE,GAAG,CAAC,CAGlD,IAAI,cAAc,IAAI,CAAC,UAAU,aAAa;GAE5D,MAAM,cAAc,MADG,mBAAmB,CACD;AACzC,OAAI,YACF,WAAU,cAAcA,IAAW,OAAO,YAAY;;AAI1D,SAAO;;CAGT,UAAU,OAAuB;AAC/B,SAAO,WAAW,MAAM,CAAC,OAAO,MAAM,CAAC,OAAO,MAAM;;CAGtD,uBACE,OACA,YACqE;EACrE,MAAMC,gBAAkD,EAAE;AAE1D,MAAI,YAAY;GAEd,MAAM,oBAAoB,MAAM,SAAS,mBAAmB;GAC5D,MAAM,cAAc,IAAI,IAAI,MAAM,KAAK,oBAAoB,MAAM,EAAE,GAAG,CAAC;AAGvE,QAAK,MAAM,OAAO,OAAO,KAAK,WAAW,CACvC,KAAI,CAAC,YAAY,IAAI,IAAI,CACvB,OAAM,IAAI,MACR,cAAc,IAAI,0CAChB,MAAM,KAAK,YAAY,CAAC,KAAK,KAAK,IAAI,SAEzC;AAKL,QAAK,MAAM,CAAC,KAAK,UAAU,OAAO,QAAQ,WAAW,EAAE;IACrD,MAAM,YAAY,KAAK,iBAAiB,KAAK,MAAM;AACnD,QAAI,UACF,eAAc,KAAK,UAAU;;;AAKnC,SAAO;GAAE,WAAW;GAAO,YAAY;GAAe;;CAGxD,AAAQ,iBACN,KACA,OACuC;AACvC,MAAI,UAAU,QAAQ,UAAU,OAC9B,QAAO;AAGT,MAAI,CAAC,gBAAgB,MAAM,CACzB,OAAM,IAAI,MACR,cAAc,IAAI,sGACnB;AAGH,SAAO;GACL,MAAM;GACN,OAAO,MAAM;GACb,MAAM,MAAM;GACb"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","names":[],"sources":["../../src/analytics/types.ts"],"sourcesContent":[],"mappings":";;;UAEiB,gBAAA,SAAyB;;AAA1C"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
//#region src/app/index.d.ts
|
|
2
|
+
interface RequestLike {
|
|
3
|
+
query?: Record<string, any>;
|
|
4
|
+
headers: Record<string, string | string[] | undefined>;
|
|
5
|
+
}
|
|
6
|
+
interface DevFileReader {
|
|
7
|
+
readFile(filePath: string, req: RequestLike): Promise<string>;
|
|
8
|
+
}
|
|
9
|
+
declare class AppManager {
|
|
10
|
+
/**
|
|
11
|
+
* Retrieves a query file by key from the queries directory
|
|
12
|
+
* In dev mode with a request context, reads from local filesystem via WebSocket
|
|
13
|
+
* @param queryKey - The query file name (without extension)
|
|
14
|
+
* @param req - Optional request object to detect dev mode
|
|
15
|
+
* @param devFileReader - Optional DevFileReader instance to read files from local filesystem
|
|
16
|
+
* @returns The query content as a string
|
|
17
|
+
* @throws Error if query key is invalid or file not found
|
|
18
|
+
*/
|
|
19
|
+
getAppQuery(queryKey: string, req?: RequestLike, devFileReader?: DevFileReader): Promise<string | null>;
|
|
20
|
+
}
|
|
21
|
+
//#endregion
|
|
22
|
+
export { AppManager };
|
|
23
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/app/index.ts"],"sourcesContent":[],"mappings":";UAGU,WAAA;EAAA,KAAA,CAAA,EACA,MADW,CAAA,MAAA,EAAA,GAAA,CAAA;EAAA,OAAA,EAEV,MAFU,CAAA,MAAA,EAAA,MAAA,GAAA,MAAA,EAAA,GAAA,SAAA,CAAA;;UAKX,aAAA,CAHC;EAAM,QAAA,CAAA,QAAA,EAAA,MAAA,EAAA,GAAA,EAIiB,WAJjB,CAAA,EAI+B,OAJ/B,CAAA,MAAA,CAAA;AAAA;AAGM,cAIV,UAAA,CAJU;;;;AAIvB;;;;;;sCAYU,6BACU,gBACf"}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import fs from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
|
|
4
|
+
//#region src/app/index.ts
|
|
5
|
+
var AppManager = class {
|
|
6
|
+
/**
|
|
7
|
+
* Retrieves a query file by key from the queries directory
|
|
8
|
+
* In dev mode with a request context, reads from local filesystem via WebSocket
|
|
9
|
+
* @param queryKey - The query file name (without extension)
|
|
10
|
+
* @param req - Optional request object to detect dev mode
|
|
11
|
+
* @param devFileReader - Optional DevFileReader instance to read files from local filesystem
|
|
12
|
+
* @returns The query content as a string
|
|
13
|
+
* @throws Error if query key is invalid or file not found
|
|
14
|
+
*/
|
|
15
|
+
async getAppQuery(queryKey, req, devFileReader) {
|
|
16
|
+
if (!queryKey || !/^[a-zA-Z0-9_-]+$/.test(queryKey)) {
|
|
17
|
+
console.error(`Invalid query key format: "${queryKey}". Only alphanumeric characters, underscores, and hyphens are allowed.`);
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
const queryFilePath = path.join(process.cwd(), "config/queries", `${queryKey}.sql`);
|
|
21
|
+
const resolvedPath = path.resolve(queryFilePath);
|
|
22
|
+
const queriesDir = path.resolve(process.cwd(), "config/queries");
|
|
23
|
+
if (!resolvedPath.startsWith(queriesDir)) {
|
|
24
|
+
console.error(`Invalid query path: path traversal detected`);
|
|
25
|
+
return null;
|
|
26
|
+
}
|
|
27
|
+
if (req?.query?.dev !== void 0 && devFileReader && req) try {
|
|
28
|
+
const relativePath = path.relative(process.cwd(), resolvedPath);
|
|
29
|
+
return await devFileReader.readFile(relativePath, req);
|
|
30
|
+
} catch (error) {
|
|
31
|
+
console.error(`Failed to read query "${queryKey}" from dev tunnel: ${error.message}`);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
try {
|
|
35
|
+
return await fs.readFile(resolvedPath, "utf8");
|
|
36
|
+
} catch (error) {
|
|
37
|
+
if (error.code === "ENOENT") {
|
|
38
|
+
console.error(`Query "${queryKey}" not found at path: ${resolvedPath}`);
|
|
39
|
+
return null;
|
|
40
|
+
}
|
|
41
|
+
console.error(`Failed to read query "${queryKey}" from server filesystem: ${error.message}`);
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
//#endregion
|
|
48
|
+
export { AppManager };
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","names":[],"sources":["../../src/app/index.ts"],"sourcesContent":["import fs from \"node:fs/promises\";\nimport path from \"node:path\";\n\ninterface RequestLike {\n query?: Record<string, any>;\n headers: Record<string, string | string[] | undefined>;\n}\n\ninterface DevFileReader {\n readFile(filePath: string, req: RequestLike): Promise<string>;\n}\n\nexport class AppManager {\n /**\n * Retrieves a query file by key from the queries directory\n * In dev mode with a request context, reads from local filesystem via WebSocket\n * @param queryKey - The query file name (without extension)\n * @param req - Optional request object to detect dev mode\n * @param devFileReader - Optional DevFileReader instance to read files from local filesystem\n * @returns The query content as a string\n * @throws Error if query key is invalid or file not found\n */\n async getAppQuery(\n queryKey: string,\n req?: RequestLike,\n devFileReader?: DevFileReader,\n ): Promise<string | null> {\n // Security: Sanitize query key to prevent path traversal\n if (!queryKey || !/^[a-zA-Z0-9_-]+$/.test(queryKey)) {\n console.error(\n `Invalid query key format: \"${queryKey}\". Only alphanumeric characters, underscores, and hyphens are allowed.`,\n );\n return null;\n }\n\n const queryFilePath = path.join(\n process.cwd(),\n \"config/queries\",\n `${queryKey}.sql`,\n );\n\n // Security: Validate resolved path is within queries directory\n const resolvedPath = path.resolve(queryFilePath);\n const queriesDir = path.resolve(process.cwd(), \"config/queries\");\n\n if (!resolvedPath.startsWith(queriesDir)) {\n console.error(`Invalid query path: path traversal detected`);\n return null;\n }\n\n // Check if we're in dev mode and should use WebSocket\n const isDevMode = req?.query?.dev !== undefined;\n\n if (isDevMode && devFileReader && req) {\n try {\n // Read from local filesystem via WebSocket tunnel\n const relativePath = path.relative(process.cwd(), resolvedPath);\n return await devFileReader.readFile(relativePath, req);\n } catch (error) {\n console.error(\n `Failed to read query \"${queryKey}\" from dev tunnel: ${(error as Error).message}`,\n );\n return null;\n }\n }\n\n // Production mode: read from server filesystem\n try {\n const query = await fs.readFile(resolvedPath, \"utf8\");\n return query;\n } catch (error) {\n if ((error as NodeJS.ErrnoException).code === \"ENOENT\") {\n console.error(`Query \"${queryKey}\" not found at path: ${resolvedPath}`);\n return null;\n }\n console.error(\n `Failed to read query \"${queryKey}\" from server filesystem: ${(error as Error).message}`,\n );\n return null;\n }\n }\n}\n\nexport type { DevFileReader, RequestLike };\n"],"mappings":";;;;AAYA,IAAa,aAAb,MAAwB;;;;;;;;;;CAUtB,MAAM,YACJ,UACA,KACA,eACwB;AAExB,MAAI,CAAC,YAAY,CAAC,mBAAmB,KAAK,SAAS,EAAE;AACnD,WAAQ,MACN,8BAA8B,SAAS,wEACxC;AACD,UAAO;;EAGT,MAAM,gBAAgB,KAAK,KACzB,QAAQ,KAAK,EACb,kBACA,GAAG,SAAS,MACb;EAGD,MAAM,eAAe,KAAK,QAAQ,cAAc;EAChD,MAAM,aAAa,KAAK,QAAQ,QAAQ,KAAK,EAAE,iBAAiB;AAEhE,MAAI,CAAC,aAAa,WAAW,WAAW,EAAE;AACxC,WAAQ,MAAM,8CAA8C;AAC5D,UAAO;;AAMT,MAFkB,KAAK,OAAO,QAAQ,UAErB,iBAAiB,IAChC,KAAI;GAEF,MAAM,eAAe,KAAK,SAAS,QAAQ,KAAK,EAAE,aAAa;AAC/D,UAAO,MAAM,cAAc,SAAS,cAAc,IAAI;WAC/C,OAAO;AACd,WAAQ,MACN,yBAAyB,SAAS,qBAAsB,MAAgB,UACzE;AACD,UAAO;;AAKX,MAAI;AAEF,UADc,MAAM,GAAG,SAAS,cAAc,OAAO;WAE9C,OAAO;AACd,OAAK,MAAgC,SAAS,UAAU;AACtD,YAAQ,MAAM,UAAU,SAAS,uBAAuB,eAAe;AACvE,WAAO;;AAET,WAAQ,MACN,yBAAyB,SAAS,4BAA6B,MAAgB,UAChF;AACD,UAAO"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.js","names":[],"sources":["../../package.json"],"sourcesContent":[""],"mappings":""}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//#region src/cache/defaults.ts
|
|
2
|
+
/** Default configuration for cache */
|
|
3
|
+
const cacheDefaults = {
|
|
4
|
+
enabled: true,
|
|
5
|
+
ttl: 3600,
|
|
6
|
+
maxSize: 1e3,
|
|
7
|
+
cacheKey: [],
|
|
8
|
+
cleanupProbability: .01,
|
|
9
|
+
strictPersistence: false
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
//#endregion
|
|
13
|
+
export { cacheDefaults };
|
|
14
|
+
//# sourceMappingURL=defaults.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"defaults.js","names":["cacheDefaults: CacheConfig"],"sources":["../../src/cache/defaults.ts"],"sourcesContent":["import type { CacheConfig } from \"shared\";\n\n/** Default configuration for cache */\nexport const cacheDefaults: CacheConfig = {\n enabled: true,\n ttl: 3600, // 1 hour\n maxSize: 1000, // 1000 entries\n cacheKey: [], // no cache key by default\n cleanupProbability: 0.01, // 1% probability of triggering cleanup on each get operation\n strictPersistence: false, // if false, use in-memory storage if lakebase is unavailable\n};\n"],"mappings":";;AAGA,MAAaA,gBAA6B;CACxC,SAAS;CACT,KAAK;CACL,SAAS;CACT,UAAU,EAAE;CACZ,oBAAoB;CACpB,mBAAmB;CACpB"}
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { CacheConfig } from "../shared/src/cache.js";
|
|
2
|
+
|
|
3
|
+
//#region src/cache/index.d.ts
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Cache manager class to handle cache operations.
|
|
7
|
+
* Can be used with in-memory storage or persistent storage (Lakebase).
|
|
8
|
+
*
|
|
9
|
+
* The cache is automatically initialized by AppKit. Use `getInstanceSync()` to access
|
|
10
|
+
* the singleton instance after initialization.
|
|
11
|
+
*
|
|
12
|
+
* @example
|
|
13
|
+
* ```typescript
|
|
14
|
+
* const cache = CacheManager.getInstanceSync();
|
|
15
|
+
* const result = await cache.getOrExecute(["users", userId], () => fetchUser(userId), userKey);
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
declare class CacheManager {
|
|
19
|
+
private static readonly MIN_CLEANUP_INTERVAL_MS;
|
|
20
|
+
private readonly name;
|
|
21
|
+
private static instance;
|
|
22
|
+
private static initPromise;
|
|
23
|
+
private storage;
|
|
24
|
+
private config;
|
|
25
|
+
private inFlightRequests;
|
|
26
|
+
private cleanupInProgress;
|
|
27
|
+
private lastCleanupAttempt;
|
|
28
|
+
private telemetry;
|
|
29
|
+
private telemetryMetrics;
|
|
30
|
+
private constructor();
|
|
31
|
+
/**
|
|
32
|
+
* Get the singleton instance of the cache manager (sync version).
|
|
33
|
+
*
|
|
34
|
+
* Throws if not initialized - ensure AppKit.create() has completed first.
|
|
35
|
+
* @returns CacheManager instance
|
|
36
|
+
*/
|
|
37
|
+
static getInstanceSync(): CacheManager;
|
|
38
|
+
/**
|
|
39
|
+
* Initialize and get the singleton instance of the cache manager.
|
|
40
|
+
* Called internally by AppKit - prefer `getInstanceSync()` for plugin access.
|
|
41
|
+
* @param userConfig - User configuration for the cache manager
|
|
42
|
+
* @returns CacheManager instance
|
|
43
|
+
* @internal
|
|
44
|
+
*/
|
|
45
|
+
static getInstance(userConfig?: Partial<CacheConfig>): Promise<CacheManager>;
|
|
46
|
+
/**
|
|
47
|
+
* Create a new cache manager instance
|
|
48
|
+
*
|
|
49
|
+
* Storage selection logic:
|
|
50
|
+
* 1. If `storage` provided and healthy → use provided storage
|
|
51
|
+
* 2. If `storage` provided but unhealthy → fallback to InMemory (or disable if strictPersistence)
|
|
52
|
+
* 3. If no `storage` provided and Lakebase available → use Lakebase
|
|
53
|
+
* 4. If no `storage` provided and Lakebase unavailable → fallback to InMemory (or disable if strictPersistence)
|
|
54
|
+
*
|
|
55
|
+
* @param userConfig - User configuration for the cache manager
|
|
56
|
+
* @returns CacheManager instance
|
|
57
|
+
*/
|
|
58
|
+
private static create;
|
|
59
|
+
/**
|
|
60
|
+
* Get or execute a function and cache the result
|
|
61
|
+
* @param key - Cache key
|
|
62
|
+
* @param fn - Function to execute
|
|
63
|
+
* @param userKey - User key
|
|
64
|
+
* @param options - Options for the cache
|
|
65
|
+
* @returns Promise of the result
|
|
66
|
+
*/
|
|
67
|
+
getOrExecute<T>(key: (string | number | object)[], fn: () => Promise<T>, userKey: string, options?: {
|
|
68
|
+
ttl?: number;
|
|
69
|
+
}): Promise<T>;
|
|
70
|
+
/**
|
|
71
|
+
* Get a cached value
|
|
72
|
+
* @param key - Cache key
|
|
73
|
+
* @returns Promise of the value or null if not found or expired
|
|
74
|
+
*/
|
|
75
|
+
get<T>(key: string): Promise<T | null>;
|
|
76
|
+
/** Probabilistically trigger cleanup of expired entries (fire-and-forget) */
|
|
77
|
+
private maybeCleanup;
|
|
78
|
+
/**
|
|
79
|
+
* Set a value in the cache
|
|
80
|
+
* @param key - Cache key
|
|
81
|
+
* @param value - Value to set
|
|
82
|
+
* @param options - Options for the cache
|
|
83
|
+
* @returns Promise of the result
|
|
84
|
+
*/
|
|
85
|
+
set<T>(key: string, value: T, options?: {
|
|
86
|
+
ttl?: number;
|
|
87
|
+
}): Promise<void>;
|
|
88
|
+
/**
|
|
89
|
+
* Delete a value from the cache
|
|
90
|
+
* @param key - Cache key
|
|
91
|
+
* @returns Promise of the result
|
|
92
|
+
*/
|
|
93
|
+
delete(key: string): Promise<void>;
|
|
94
|
+
/** Clear the cache */
|
|
95
|
+
clear(): Promise<void>;
|
|
96
|
+
/**
|
|
97
|
+
* Check if a value exists in the cache
|
|
98
|
+
* @param key - Cache key
|
|
99
|
+
* @returns Promise of true if the value exists, false otherwise
|
|
100
|
+
*/
|
|
101
|
+
has(key: string): Promise<boolean>;
|
|
102
|
+
/**
|
|
103
|
+
* Generate a cache key
|
|
104
|
+
* @param parts - Parts of the key
|
|
105
|
+
* @param userKey - User key
|
|
106
|
+
* @returns Cache key
|
|
107
|
+
*/
|
|
108
|
+
generateKey(parts: (string | number | object)[], userKey: string): string;
|
|
109
|
+
/** Close the cache */
|
|
110
|
+
close(): Promise<void>;
|
|
111
|
+
/**
|
|
112
|
+
* Check if the storage is healthy
|
|
113
|
+
* @returns Promise of true if the storage is healthy, false otherwise
|
|
114
|
+
*/
|
|
115
|
+
isStorageHealthy(): Promise<boolean>;
|
|
116
|
+
}
|
|
117
|
+
//#endregion
|
|
118
|
+
export { CacheManager };
|
|
119
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cache/index.ts"],"sourcesContent":[],"mappings":";;;;;;AAuBA;;;;;;;;;;;AAwQqC,cAxQxB,YAAA,CAwQwB;0BAAR,uBAAA;mBAkDlB,IAAA;iBAEN,QAAA;iBAawB,WAAA;UAMZ,OAAA;UAUS,MAAA;UA0BT,gBAAA;UAQW,iBAAA;EAAO,QAAA,kBAAA;;;;;;;;;;4BA3UP;;;;;;;;kCAkBX,QAAQ,eACpB,QAAQ;;;;;;;;;;;;;;;;;;;;;;+DAuGC,QAAQ;;MAGjB,QAAQ;;;;;;uBA2FgB,QAAQ;;;;;;;;;;6BAkD1B;;MAEN;;;;;;uBAawB;;WAMZ;;;;;;oBAUS;;;;;;;;;WA0BT;;;;;sBAQW"}
|